1/*
2 * Copyright (c) 1995, 2015, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package sun.applet;
27
28import java.lang.NullPointerException;
29import java.net.URL;
30import java.net.URLClassLoader;
31import java.net.SocketPermission;
32import java.net.URLConnection;
33import java.net.MalformedURLException;
34import java.net.InetAddress;
35import java.net.UnknownHostException;
36import java.io.EOFException;
37import java.io.File;
38import java.io.FilePermission;
39import java.io.IOException;
40import java.io.BufferedInputStream;
41import java.io.InputStream;
42import java.util.Enumeration;
43import java.util.HashMap;
44import java.util.NoSuchElementException;
45import java.security.AccessController;
46import java.security.AccessControlContext;
47import java.security.PrivilegedAction;
48import java.security.PrivilegedExceptionAction;
49import java.security.PrivilegedActionException;
50import java.security.CodeSource;
51import java.security.Permission;
52import java.security.PermissionCollection;
53import sun.awt.AppContext;
54import sun.awt.SunToolkit;
55import sun.net.www.ParseUtil;
56import sun.security.util.SecurityConstants;
57
58/**
59 * This class defines the class loader for loading applet classes and
60 * resources. It extends URLClassLoader to search the applet code base
61 * for the class or resource after checking any loaded JAR files.
62 */
63public class AppletClassLoader extends URLClassLoader {
64    private URL base;   /* applet code base URL */
65    private CodeSource codesource; /* codesource for the base URL */
66    private AccessControlContext acc;
67    private boolean exceptionStatus = false;
68
69    private final Object threadGroupSynchronizer = new Object();
70    private final Object grabReleaseSynchronizer = new Object();
71
72    private boolean codebaseLookup = true;
73    private volatile boolean allowRecursiveDirectoryRead = true;
74
75    /*
76     * Creates a new AppletClassLoader for the specified base URL.
77     */
78    protected AppletClassLoader(URL base) {
79        super(new URL[0]);
80        this.base = base;
81        this.codesource =
82            new CodeSource(base, (java.security.cert.Certificate[]) null);
83        acc = AccessController.getContext();
84    }
85
86    public void disableRecursiveDirectoryRead() {
87        allowRecursiveDirectoryRead = false;
88    }
89
90
91    /**
92     * Set the codebase lookup flag.
93     */
94    void setCodebaseLookup(boolean codebaseLookup)  {
95        this.codebaseLookup = codebaseLookup;
96    }
97
98    /*
99     * Returns the applet code base URL.
100     */
101    URL getBaseURL() {
102        return base;
103    }
104
105    /*
106     * Returns the URLs used for loading classes and resources.
107     */
108    public URL[] getURLs() {
109        URL[] jars = super.getURLs();
110        URL[] urls = new URL[jars.length + 1];
111        System.arraycopy(jars, 0, urls, 0, jars.length);
112        urls[urls.length - 1] = base;
113        return urls;
114    }
115
116    /*
117     * Adds the specified JAR file to the search path of loaded JAR files.
118     * Changed modifier to protected in order to be able to overwrite addJar()
119     * in PluginClassLoader.java
120     */
121    protected void addJar(String name) throws IOException {
122        URL url;
123        try {
124            url = new URL(base, name);
125        } catch (MalformedURLException e) {
126            throw new IllegalArgumentException("name");
127        }
128        addURL(url);
129        // DEBUG
130        //URL[] urls = getURLs();
131        //for (int i = 0; i < urls.length; i++) {
132        //    System.out.println("url[" + i + "] = " + urls[i]);
133        //}
134    }
135
136    /*
137     * Override loadClass so that class loading errors can be caught in
138     * order to print better error messages.
139     */
140    public synchronized Class<?> loadClass(String name, boolean resolve)
141        throws ClassNotFoundException
142    {
143        // First check if we have permission to access the package. This
144        // should go away once we've added support for exported packages.
145        int i = name.lastIndexOf('.');
146        if (i != -1) {
147            SecurityManager sm = System.getSecurityManager();
148            if (sm != null)
149                sm.checkPackageAccess(name.substring(0, i));
150        }
151        try {
152            return super.loadClass(name, resolve);
153        } catch (ClassNotFoundException e) {
154            //printError(name, e.getException());
155            throw e;
156        } catch (RuntimeException e) {
157            //printError(name, e);
158            throw e;
159        } catch (Error e) {
160            //printError(name, e);
161            throw e;
162        }
163    }
164
165    /*
166     * Finds the applet class with the specified name. First searches
167     * loaded JAR files then the applet code base for the class.
168     */
169    protected Class<?> findClass(String name) throws ClassNotFoundException {
170
171        int index = name.indexOf(';');
172        String cookie = "";
173        if(index != -1) {
174                cookie = name.substring(index, name.length());
175                name = name.substring(0, index);
176        }
177
178        // check loaded JAR files
179        try {
180            return super.findClass(name);
181        } catch (ClassNotFoundException e) {
182        }
183
184        // Otherwise, try loading the class from the code base URL
185
186        // 4668479: Option to turn off codebase lookup in AppletClassLoader
187        // during resource requests. [stanley.ho]
188        if (codebaseLookup == false)
189            throw new ClassNotFoundException(name);
190
191//      final String path = name.replace('.', '/').concat(".class").concat(cookie);
192        String encodedName = ParseUtil.encodePath(name.replace('.', '/'), false);
193        final String path = (new StringBuffer(encodedName)).append(".class").append(cookie).toString();
194        try {
195            byte[] b = AccessController.doPrivileged(
196                               new PrivilegedExceptionAction<byte[]>() {
197                public byte[] run() throws IOException {
198                   try {
199                        URL finalURL = new URL(base, path);
200
201                        // Make sure the codebase won't be modified
202                        if (base.getProtocol().equals(finalURL.getProtocol()) &&
203                            base.getHost().equals(finalURL.getHost()) &&
204                            base.getPort() == finalURL.getPort()) {
205                            return getBytes(finalURL);
206                        }
207                        else {
208                            return null;
209                        }
210                    } catch (Exception e) {
211                        return null;
212                    }
213                }
214            }, acc);
215
216            if (b != null) {
217                return defineClass(name, b, 0, b.length, codesource);
218            } else {
219                throw new ClassNotFoundException(name);
220            }
221        } catch (PrivilegedActionException e) {
222            throw new ClassNotFoundException(name, e.getException());
223        }
224    }
225
226    /**
227     * Returns the permissions for the given codesource object.
228     * The implementation of this method first calls super.getPermissions,
229     * to get the permissions
230     * granted by the super class, and then adds additional permissions
231     * based on the URL of the codesource.
232     * <p>
233     * If the protocol is "file"
234     * and the path specifies a file, permission is granted to read all files
235     * and (recursively) all files and subdirectories contained in
236     * that directory. This is so applets with a codebase of
237     * file:/blah/some.jar can read in file:/blah/, which is needed to
238     * be backward compatible. We also add permission to connect back to
239     * the "localhost".
240     *
241     * @param codesource the codesource
242     * @throws NullPointerException if {@code codesource} is {@code null}.
243     * @return the permissions granted to the codesource
244     */
245    protected PermissionCollection getPermissions(CodeSource codesource)
246    {
247        final PermissionCollection perms = super.getPermissions(codesource);
248
249        URL url = codesource.getLocation();
250
251        String path = null;
252        Permission p;
253
254        try {
255            p = url.openConnection().getPermission();
256        } catch (java.io.IOException ioe) {
257            p = null;
258        }
259
260        if (p instanceof FilePermission) {
261            path = p.getName();
262        } else if ((p == null) && (url.getProtocol().equals("file"))) {
263            path = url.getFile().replace('/', File.separatorChar);
264            path = ParseUtil.decode(path);
265        }
266
267        if (path != null) {
268            final String rawPath = path;
269            if (!path.endsWith(File.separator)) {
270                int endIndex = path.lastIndexOf(File.separatorChar);
271                if (endIndex != -1) {
272                        path = path.substring(0, endIndex + 1) + "-";
273                        perms.add(new FilePermission(path,
274                            SecurityConstants.FILE_READ_ACTION));
275                }
276            }
277            final File f = new File(rawPath);
278            final boolean isDirectory = f.isDirectory();
279            // grant codebase recursive read permission
280            // this should only be granted to non-UNC file URL codebase and
281            // the codesource path must either be a directory, or a file
282            // that ends with .jar or .zip
283            if (allowRecursiveDirectoryRead && (isDirectory ||
284                    rawPath.toLowerCase().endsWith(".jar") ||
285                    rawPath.toLowerCase().endsWith(".zip"))) {
286
287            Permission bperm;
288                try {
289                    bperm = base.openConnection().getPermission();
290                } catch (java.io.IOException ioe) {
291                    bperm = null;
292                }
293                if (bperm instanceof FilePermission) {
294                    String bpath = bperm.getName();
295                    if (bpath.endsWith(File.separator)) {
296                        bpath += "-";
297                    }
298                    perms.add(new FilePermission(bpath,
299                        SecurityConstants.FILE_READ_ACTION));
300                } else if ((bperm == null) && (base.getProtocol().equals("file"))) {
301                    String bpath = base.getFile().replace('/', File.separatorChar);
302                    bpath = ParseUtil.decode(bpath);
303                    if (bpath.endsWith(File.separator)) {
304                        bpath += "-";
305                    }
306                    perms.add(new FilePermission(bpath, SecurityConstants.FILE_READ_ACTION));
307                }
308
309            }
310        }
311        return perms;
312    }
313
314    /*
315     * Returns the contents of the specified URL as an array of bytes.
316     */
317    private static byte[] getBytes(URL url) throws IOException {
318        URLConnection uc = url.openConnection();
319        if (uc instanceof java.net.HttpURLConnection) {
320            java.net.HttpURLConnection huc = (java.net.HttpURLConnection) uc;
321            int code = huc.getResponseCode();
322            if (code >= java.net.HttpURLConnection.HTTP_BAD_REQUEST) {
323                throw new IOException("open HTTP connection failed.");
324            }
325        }
326        int len = uc.getContentLength();
327
328        // Fixed #4507227: Slow performance to load
329        // class and resources. [stanleyh]
330        //
331        // Use buffered input stream [stanleyh]
332        InputStream in = new BufferedInputStream(uc.getInputStream());
333
334        byte[] b;
335        try {
336            b = in.readAllBytes();
337            if (len != -1 && b.length != len)
338                throw new EOFException("Expected:" + len + ", read:" + b.length);
339        } finally {
340            in.close();
341        }
342        return b;
343    }
344
345    // Object for synchronization around getResourceAsStream()
346    private Object syncResourceAsStream = new Object();
347    private Object syncResourceAsStreamFromJar = new Object();
348
349    // Flag to indicate getResourceAsStream() is in call
350    private boolean resourceAsStreamInCall = false;
351    private boolean resourceAsStreamFromJarInCall = false;
352
353    /**
354     * Returns an input stream for reading the specified resource.
355     *
356     * The search order is described in the documentation for {@link
357     * #getResource(String)}.<p>
358     *
359     * @param  name the resource name
360     * @return an input stream for reading the resource, or {@code null}
361     *         if the resource could not be found
362     * @since  1.1
363     */
364    public InputStream getResourceAsStream(String name)
365    {
366
367        if (name == null) {
368            throw new NullPointerException("name");
369        }
370
371        try
372        {
373            InputStream is = null;
374
375            // Fixed #4507227: Slow performance to load
376            // class and resources. [stanleyh]
377            //
378            // The following is used to avoid calling
379            // AppletClassLoader.findResource() in
380            // super.getResourceAsStream(). Otherwise,
381            // unnecessary connection will be made.
382            //
383            synchronized(syncResourceAsStream)
384            {
385                resourceAsStreamInCall = true;
386
387                // Call super class
388                is = super.getResourceAsStream(name);
389
390                resourceAsStreamInCall = false;
391            }
392
393            // 4668479: Option to turn off codebase lookup in AppletClassLoader
394            // during resource requests. [stanley.ho]
395            if (codebaseLookup == true && is == null)
396            {
397                // If resource cannot be obtained,
398                // try to download it from codebase
399                URL url = new URL(base, ParseUtil.encodePath(name, false));
400                is = url.openStream();
401            }
402
403            return is;
404        }
405        catch (Exception e)
406        {
407            return null;
408        }
409    }
410
411
412    /**
413     * Returns an input stream for reading the specified resource from the
414     * the loaded jar files.
415     *
416     * The search order is described in the documentation for {@link
417     * #getResource(String)}.<p>
418     *
419     * @param  name the resource name
420     * @return an input stream for reading the resource, or {@code null}
421     *         if the resource could not be found
422     * @since  1.1
423     */
424    public InputStream getResourceAsStreamFromJar(String name) {
425
426        if (name == null) {
427            throw new NullPointerException("name");
428        }
429
430        try {
431            InputStream is = null;
432            synchronized(syncResourceAsStreamFromJar) {
433                resourceAsStreamFromJarInCall = true;
434                // Call super class
435                is = super.getResourceAsStream(name);
436                resourceAsStreamFromJarInCall = false;
437            }
438
439            return is;
440        } catch (Exception e) {
441            return null;
442        }
443    }
444
445
446    /*
447     * Finds the applet resource with the specified name. First checks
448     * loaded JAR files then the applet code base for the resource.
449     */
450    public URL findResource(String name) {
451        // check loaded JAR files
452        URL url = super.findResource(name);
453
454        // 6215746:  Disable META-INF/* lookup from codebase in
455        // applet/plugin classloader. [stanley.ho]
456        if (name.startsWith("META-INF/"))
457            return url;
458
459        // 4668479: Option to turn off codebase lookup in AppletClassLoader
460        // during resource requests. [stanley.ho]
461        if (codebaseLookup == false)
462            return url;
463
464        if (url == null)
465        {
466            //#4805170, if it is a call from Applet.getImage()
467            //we should check for the image only in the archives
468            boolean insideGetResourceAsStreamFromJar = false;
469                synchronized(syncResourceAsStreamFromJar) {
470                insideGetResourceAsStreamFromJar = resourceAsStreamFromJarInCall;
471            }
472
473            if (insideGetResourceAsStreamFromJar) {
474                return null;
475            }
476
477            // Fixed #4507227: Slow performance to load
478            // class and resources. [stanleyh]
479            //
480            // Check if getResourceAsStream is called.
481            //
482            boolean insideGetResourceAsStream = false;
483
484            synchronized(syncResourceAsStream)
485            {
486                insideGetResourceAsStream = resourceAsStreamInCall;
487            }
488
489            // If getResourceAsStream is called, don't
490            // trigger the following code. Otherwise,
491            // unnecessary connection will be made.
492            //
493            if (insideGetResourceAsStream == false)
494            {
495                // otherwise, try the code base
496                try {
497                    url = new URL(base, ParseUtil.encodePath(name, false));
498                    // check if resource exists
499                    if(!resourceExists(url))
500                        url = null;
501                } catch (Exception e) {
502                    // all exceptions, including security exceptions, are caught
503                    url = null;
504                }
505            }
506        }
507        return url;
508    }
509
510
511    private boolean resourceExists(URL url) {
512        // Check if the resource exists.
513        // It almost works to just try to do an openConnection() but
514        // HttpURLConnection will return true on HTTP_BAD_REQUEST
515        // when the requested name ends in ".html", ".htm", and ".txt"
516        // and we want to be able to handle these
517        //
518        // Also, cannot just open a connection for things like FileURLConnection,
519        // because they succeed when connecting to a nonexistent file.
520        // So, in those cases we open and close an input stream.
521        boolean ok = true;
522        try {
523            URLConnection conn = url.openConnection();
524            if (conn instanceof java.net.HttpURLConnection) {
525                java.net.HttpURLConnection hconn =
526                    (java.net.HttpURLConnection) conn;
527
528                // To reduce overhead, using http HEAD method instead of GET method
529                hconn.setRequestMethod("HEAD");
530
531                int code = hconn.getResponseCode();
532                if (code == java.net.HttpURLConnection.HTTP_OK) {
533                    return true;
534                }
535                if (code >= java.net.HttpURLConnection.HTTP_BAD_REQUEST) {
536                    return false;
537                }
538            } else {
539                /**
540                 * Fix for #4182052 - stanleyh
541                 *
542                 * The same connection should be reused to avoid multiple
543                 * HTTP connections
544                 */
545
546                // our best guess for the other cases
547                InputStream is = conn.getInputStream();
548                is.close();
549            }
550        } catch (Exception ex) {
551            ok = false;
552        }
553        return ok;
554    }
555
556    /*
557     * Returns an enumeration of all the applet resources with the specified
558     * name. First checks loaded JAR files then the applet code base for all
559     * available resources.
560     */
561    @Override
562    public Enumeration<URL> findResources(String name) throws IOException {
563
564        final Enumeration<URL> e = super.findResources(name);
565
566        // 6215746:  Disable META-INF/* lookup from codebase in
567        // applet/plugin classloader. [stanley.ho]
568        if (name.startsWith("META-INF/"))
569            return e;
570
571        // 4668479: Option to turn off codebase lookup in AppletClassLoader
572        // during resource requests. [stanley.ho]
573        if (codebaseLookup == false)
574            return e;
575
576        URL u = new URL(base, ParseUtil.encodePath(name, false));
577        if (!resourceExists(u)) {
578            u = null;
579        }
580
581        final URL url = u;
582        return new Enumeration<URL>() {
583            private boolean done;
584            public URL nextElement() {
585                if (!done) {
586                    if (e.hasMoreElements()) {
587                        return e.nextElement();
588                    }
589                    done = true;
590                    if (url != null) {
591                        return url;
592                    }
593                }
594                throw new NoSuchElementException();
595            }
596            public boolean hasMoreElements() {
597                return !done && (e.hasMoreElements() || url != null);
598            }
599        };
600    }
601
602    /*
603     * Load and resolve the file specified by the applet tag CODE
604     * attribute. The argument can either be the relative path
605     * of the class file itself or just the name of the class.
606     */
607    Class<?> loadCode(String name) throws ClassNotFoundException {
608        // first convert any '/' or native file separator to .
609        name = name.replace('/', '.');
610        name = name.replace(File.separatorChar, '.');
611
612        // deal with URL rewriting
613        String cookie = null;
614        int index = name.indexOf(';');
615        if(index != -1) {
616                cookie = name.substring(index, name.length());
617                name = name.substring(0, index);
618        }
619
620        // save that name for later
621        String fullName = name;
622        // then strip off any suffixes
623        if (name.endsWith(".class") || name.endsWith(".java")) {
624            name = name.substring(0, name.lastIndexOf('.'));
625        }
626        try {
627                if(cookie != null)
628                        name = (new StringBuffer(name)).append(cookie).toString();
629            return loadClass(name);
630        } catch (ClassNotFoundException e) {
631        }
632        // then if it didn't end with .java or .class, or in the
633        // really pathological case of a class named class or java
634        if(cookie != null)
635                fullName = (new StringBuffer(fullName)).append(cookie).toString();
636
637        return loadClass(fullName);
638    }
639
640    /*
641     * The threadgroup that the applets loaded by this classloader live
642     * in. In the sun.* implementation of applets, the security manager's
643     * (AppletSecurity) getThreadGroup returns the thread group of the
644     * first applet on the stack, which is the applet's thread group.
645     */
646    private AppletThreadGroup threadGroup;
647    private AppContext appContext;
648
649    public ThreadGroup getThreadGroup() {
650      synchronized (threadGroupSynchronizer) {
651        if (threadGroup == null || threadGroup.isDestroyed()) {
652            AccessController.doPrivileged(new PrivilegedAction<Object>() {
653                public Object run() {
654                    threadGroup = new AppletThreadGroup(base + "-threadGroup");
655                    // threadGroup.setDaemon(true);
656                    // threadGroup is now destroyed by AppContext.dispose()
657
658                    // Create the new AppContext from within a Thread belonging
659                    // to the newly created ThreadGroup, and wait for the
660                    // creation to complete before returning from this method.
661                    AppContextCreator creatorThread = new AppContextCreator(threadGroup);
662
663                    // Since this thread will later be used to launch the
664                    // applet's AWT-event dispatch thread and we want the applet
665                    // code executing the AWT callbacks to use their own class
666                    // loader rather than the system class loader, explicitly
667                    // set the context class loader to the AppletClassLoader.
668                    creatorThread.setContextClassLoader(AppletClassLoader.this);
669
670                    creatorThread.start();
671                    try {
672                        synchronized(creatorThread.syncObject) {
673                            while (!creatorThread.created) {
674                                creatorThread.syncObject.wait();
675                            }
676                        }
677                    } catch (InterruptedException e) { }
678                    appContext = creatorThread.appContext;
679                    return null;
680                }
681            });
682        }
683        return threadGroup;
684      }
685    }
686
687    /*
688     * Get the AppContext, if any, corresponding to this AppletClassLoader.
689     */
690    public AppContext getAppContext()  {
691        return appContext;
692    }
693
694    int usageCount = 0;
695
696    /**
697     * Grab this AppletClassLoader and its ThreadGroup/AppContext, so they
698     * won't be destroyed.
699     */
700public     void grab() {
701        synchronized(grabReleaseSynchronizer) {
702            usageCount++;
703        }
704        getThreadGroup(); // Make sure ThreadGroup/AppContext exist
705    }
706
707    protected void setExceptionStatus()
708    {
709        exceptionStatus = true;
710    }
711
712    public boolean getExceptionStatus()
713    {
714        return exceptionStatus;
715    }
716
717    /**
718     * Release this AppletClassLoader and its ThreadGroup/AppContext.
719     * If nothing else has grabbed this AppletClassLoader, its ThreadGroup
720     * and AppContext will be destroyed.
721     *
722     * Because this method may destroy the AppletClassLoader's ThreadGroup,
723     * this method should NOT be called from within the AppletClassLoader's
724     * ThreadGroup.
725     *
726     * Changed modifier to protected in order to be able to overwrite this
727     * function in PluginClassLoader.java
728     */
729    protected void release() {
730
731        AppContext tempAppContext = null;
732
733        synchronized(grabReleaseSynchronizer) {
734            if (usageCount > 1)  {
735                --usageCount;
736            } else {
737                synchronized(threadGroupSynchronizer) {
738                    tempAppContext = resetAppContext();
739                }
740            }
741        }
742
743        // Dispose appContext outside any sync block to
744        // prevent potential deadlock.
745        if (tempAppContext != null)  {
746            try {
747                tempAppContext.dispose(); // nuke the world!
748            } catch (IllegalThreadStateException e) { }
749        }
750    }
751
752    /*
753     * reset classloader's AppContext and ThreadGroup
754     * This method is for subclass PluginClassLoader to
755     * reset superclass's AppContext and ThreadGroup but do
756     * not dispose the AppContext. PluginClassLoader does not
757     * use UsageCount to decide whether to dispose AppContext
758     *
759     * @return previous AppContext
760     */
761    protected AppContext resetAppContext() {
762        AppContext tempAppContext = null;
763
764        synchronized(threadGroupSynchronizer) {
765            // Store app context in temp variable
766            tempAppContext = appContext;
767            usageCount = 0;
768            appContext = null;
769            threadGroup = null;
770        }
771        return tempAppContext;
772    }
773
774
775    // Hash map to store applet compatibility info
776    private HashMap<String, Boolean> jdk11AppletInfo = new HashMap<>();
777    private HashMap<String, Boolean> jdk12AppletInfo = new HashMap<>();
778
779    /**
780     * Set applet target level as JDK 1.1.
781     *
782     * @param clazz Applet class.
783     * @param bool true if JDK is targeted for JDK 1.1;
784     *             false otherwise.
785     */
786    void setJDK11Target(Class<?> clazz, boolean bool)
787    {
788         jdk11AppletInfo.put(clazz.toString(), Boolean.valueOf(bool));
789    }
790
791    /**
792     * Set applet target level as JDK 1.2.
793     *
794     * @param clazz Applet class.
795     * @param bool true if JDK is targeted for JDK 1.2;
796     *             false otherwise.
797     */
798    void setJDK12Target(Class<?> clazz, boolean bool)
799    {
800        jdk12AppletInfo.put(clazz.toString(), Boolean.valueOf(bool));
801    }
802
803    /**
804     * Determine if applet is targeted for JDK 1.1.
805     *
806     * @param  clazz Applet class.
807     * @return TRUE if applet is targeted for JDK 1.1;
808     *         FALSE if applet is not;
809     *         null if applet is unknown.
810     */
811    Boolean isJDK11Target(Class<?> clazz)
812    {
813        return jdk11AppletInfo.get(clazz.toString());
814    }
815
816    /**
817     * Determine if applet is targeted for JDK 1.2.
818     *
819     * @param  clazz Applet class.
820     * @return TRUE if applet is targeted for JDK 1.2;
821     *         FALSE if applet is not;
822     *         null if applet is unknown.
823     */
824    Boolean isJDK12Target(Class<?> clazz)
825    {
826        return jdk12AppletInfo.get(clazz.toString());
827    }
828
829    private static AppletMessageHandler mh =
830        new AppletMessageHandler("appletclassloader");
831
832    /*
833     * Prints a class loading error message.
834     */
835    private static void printError(String name, Throwable e) {
836        String s = null;
837        if (e == null) {
838            s = mh.getMessage("filenotfound", name);
839        } else if (e instanceof IOException) {
840            s = mh.getMessage("fileioexception", name);
841        } else if (e instanceof ClassFormatError) {
842            s = mh.getMessage("fileformat", name);
843        } else if (e instanceof ThreadDeath) {
844            s = mh.getMessage("filedeath", name);
845        } else if (e instanceof Error) {
846            s = mh.getMessage("fileerror", e.toString(), name);
847        }
848        if (s != null) {
849            System.err.println(s);
850        }
851    }
852}
853
854/*
855 * The AppContextCreator class is used to create an AppContext from within
856 * a Thread belonging to the new AppContext's ThreadGroup.  To wait for
857 * this operation to complete before continuing, wait for the notifyAll()
858 * operation on the syncObject to occur.
859 */
860class AppContextCreator extends Thread {
861    Object syncObject = new Object();
862    AppContext appContext = null;
863    volatile boolean created = false;
864
865    /**
866     * Must call the 5-args super-class constructor to erase locals.
867     */
868    private AppContextCreator() {
869        throw new UnsupportedOperationException("Must erase locals");
870    }
871
872    AppContextCreator(ThreadGroup group)  {
873        super(group, null, "AppContextCreator", 0, false);
874    }
875
876    public void run()  {
877        appContext = SunToolkit.createNewAppContext();
878        created = true;
879        synchronized(syncObject) {
880            syncObject.notifyAll();
881        }
882    } // run()
883
884} // class AppContextCreator
885