View Javadoc
1   /*
2    * Copyright (c) 1995, 2013, 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  
26  package sun.applet;
27  
28  import java.lang.NullPointerException;
29  import java.net.URL;
30  import java.net.URLClassLoader;
31  import java.net.SocketPermission;
32  import java.net.URLConnection;
33  import java.net.MalformedURLException;
34  import java.net.InetAddress;
35  import java.net.UnknownHostException;
36  import java.io.File;
37  import java.io.FilePermission;
38  import java.io.IOException;
39  import java.io.BufferedInputStream;
40  import java.io.InputStream;
41  import java.util.Enumeration;
42  import java.util.HashMap;
43  import java.util.NoSuchElementException;
44  import java.security.AccessController;
45  import java.security.AccessControlContext;
46  import java.security.PrivilegedAction;
47  import java.security.PrivilegedExceptionAction;
48  import java.security.PrivilegedActionException;
49  import java.security.CodeSource;
50  import java.security.Permission;
51  import java.security.PermissionCollection;
52  import sun.awt.AppContext;
53  import sun.awt.SunToolkit;
54  import sun.misc.IOUtils;
55  import sun.net.www.ParseUtil;
56  import 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   */
63  public 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 = (byte[]) AccessController.doPrivileged(
196                                new PrivilegedExceptionAction() {
197                 public Object 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 = IOUtils.readFully(in, len, true);
337         } finally {
338             in.close();
339         }
340         return b;
341     }
342 
343     // Object for synchronization around getResourceAsStream()
344     private Object syncResourceAsStream = new Object();
345     private Object syncResourceAsStreamFromJar = new Object();
346 
347     // Flag to indicate getResourceAsStream() is in call
348     private boolean resourceAsStreamInCall = false;
349     private boolean resourceAsStreamFromJarInCall = false;
350 
351     /**
352      * Returns an input stream for reading the specified resource.
353      *
354      * The search order is described in the documentation for {@link
355      * #getResource(String)}.<p>
356      *
357      * @param  name the resource name
358      * @return an input stream for reading the resource, or <code>null</code>
359      *         if the resource could not be found
360      * @since  JDK1.1
361      */
362     public InputStream getResourceAsStream(String name)
363     {
364 
365         if (name == null) {
366             throw new NullPointerException("name");
367         }
368 
369         try
370         {
371             InputStream is = null;
372 
373             // Fixed #4507227: Slow performance to load
374             // class and resources. [stanleyh]
375             //
376             // The following is used to avoid calling
377             // AppletClassLoader.findResource() in
378             // super.getResourceAsStream(). Otherwise,
379             // unnecessary connection will be made.
380             //
381             synchronized(syncResourceAsStream)
382             {
383                 resourceAsStreamInCall = true;
384 
385                 // Call super class
386                 is = super.getResourceAsStream(name);
387 
388                 resourceAsStreamInCall = false;
389             }
390 
391             // 4668479: Option to turn off codebase lookup in AppletClassLoader
392             // during resource requests. [stanley.ho]
393             if (codebaseLookup == true && is == null)
394             {
395                 // If resource cannot be obtained,
396                 // try to download it from codebase
397                 URL url = new URL(base, ParseUtil.encodePath(name, false));
398                 is = url.openStream();
399             }
400 
401             return is;
402         }
403         catch (Exception e)
404         {
405             return null;
406         }
407     }
408 
409 
410     /**
411      * Returns an input stream for reading the specified resource from the
412      * the loaded jar files.
413      *
414      * The search order is described in the documentation for {@link
415      * #getResource(String)}.<p>
416      *
417      * @param  name the resource name
418      * @return an input stream for reading the resource, or <code>null</code>
419      *         if the resource could not be found
420      * @since  JDK1.1
421      */
422     public InputStream getResourceAsStreamFromJar(String name) {
423 
424         if (name == null) {
425             throw new NullPointerException("name");
426         }
427 
428         try {
429             InputStream is = null;
430             synchronized(syncResourceAsStreamFromJar) {
431                 resourceAsStreamFromJarInCall = true;
432                 // Call super class
433                 is = super.getResourceAsStream(name);
434                 resourceAsStreamFromJarInCall = false;
435             }
436 
437             return is;
438         } catch (Exception e) {
439             return null;
440         }
441     }
442 
443 
444     /*
445      * Finds the applet resource with the specified name. First checks
446      * loaded JAR files then the applet code base for the resource.
447      */
448     public URL findResource(String name) {
449         // check loaded JAR files
450         URL url = super.findResource(name);
451 
452         // 6215746:  Disable META-INF/* lookup from codebase in
453         // applet/plugin classloader. [stanley.ho]
454         if (name.startsWith("META-INF/"))
455             return url;
456 
457         // 4668479: Option to turn off codebase lookup in AppletClassLoader
458         // during resource requests. [stanley.ho]
459         if (codebaseLookup == false)
460             return url;
461 
462         if (url == null)
463         {
464             //#4805170, if it is a call from Applet.getImage()
465             //we should check for the image only in the archives
466             boolean insideGetResourceAsStreamFromJar = false;
467                 synchronized(syncResourceAsStreamFromJar) {
468                 insideGetResourceAsStreamFromJar = resourceAsStreamFromJarInCall;
469             }
470 
471             if (insideGetResourceAsStreamFromJar) {
472                 return null;
473             }
474 
475             // Fixed #4507227: Slow performance to load
476             // class and resources. [stanleyh]
477             //
478             // Check if getResourceAsStream is called.
479             //
480             boolean insideGetResourceAsStream = false;
481 
482             synchronized(syncResourceAsStream)
483             {
484                 insideGetResourceAsStream = resourceAsStreamInCall;
485             }
486 
487             // If getResourceAsStream is called, don't
488             // trigger the following code. Otherwise,
489             // unnecessary connection will be made.
490             //
491             if (insideGetResourceAsStream == false)
492             {
493                 // otherwise, try the code base
494                 try {
495                     url = new URL(base, ParseUtil.encodePath(name, false));
496                     // check if resource exists
497                     if(!resourceExists(url))
498                         url = null;
499                 } catch (Exception e) {
500                     // all exceptions, including security exceptions, are caught
501                     url = null;
502                 }
503             }
504         }
505         return url;
506     }
507 
508 
509     private boolean resourceExists(URL url) {
510         // Check if the resource exists.
511         // It almost works to just try to do an openConnection() but
512         // HttpURLConnection will return true on HTTP_BAD_REQUEST
513         // when the requested name ends in ".html", ".htm", and ".txt"
514         // and we want to be able to handle these
515         //
516         // Also, cannot just open a connection for things like FileURLConnection,
517         // because they succeed when connecting to a nonexistent file.
518         // So, in those cases we open and close an input stream.
519         boolean ok = true;
520         try {
521             URLConnection conn = url.openConnection();
522             if (conn instanceof java.net.HttpURLConnection) {
523                 java.net.HttpURLConnection hconn =
524                     (java.net.HttpURLConnection) conn;
525 
526                 // To reduce overhead, using http HEAD method instead of GET method
527                 hconn.setRequestMethod("HEAD");
528 
529                 int code = hconn.getResponseCode();
530                 if (code == java.net.HttpURLConnection.HTTP_OK) {
531                     return true;
532                 }
533                 if (code >= java.net.HttpURLConnection.HTTP_BAD_REQUEST) {
534                     return false;
535                 }
536             } else {
537                 /**
538                  * Fix for #4182052 - stanleyh
539                  *
540                  * The same connection should be reused to avoid multiple
541                  * HTTP connections
542                  */
543 
544                 // our best guess for the other cases
545                 InputStream is = conn.getInputStream();
546                 is.close();
547             }
548         } catch (Exception ex) {
549             ok = false;
550         }
551         return ok;
552     }
553 
554     /*
555      * Returns an enumeration of all the applet resources with the specified
556      * name. First checks loaded JAR files then the applet code base for all
557      * available resources.
558      */
559     public Enumeration findResources(String name) throws IOException {
560 
561         final Enumeration e = super.findResources(name);
562 
563         // 6215746:  Disable META-INF/* lookup from codebase in
564         // applet/plugin classloader. [stanley.ho]
565         if (name.startsWith("META-INF/"))
566             return e;
567 
568         // 4668479: Option to turn off codebase lookup in AppletClassLoader
569         // during resource requests. [stanley.ho]
570         if (codebaseLookup == false)
571             return e;
572 
573         URL u = new URL(base, ParseUtil.encodePath(name, false));
574         if (!resourceExists(u)) {
575             u = null;
576         }
577 
578         final URL url = u;
579         return new Enumeration() {
580             private boolean done;
581             public Object nextElement() {
582                 if (!done) {
583                     if (e.hasMoreElements()) {
584                         return e.nextElement();
585                     }
586                     done = true;
587                     if (url != null) {
588                         return url;
589                     }
590                 }
591                 throw new NoSuchElementException();
592             }
593             public boolean hasMoreElements() {
594                 return !done && (e.hasMoreElements() || url != null);
595             }
596         };
597     }
598 
599     /*
600      * Load and resolve the file specified by the applet tag CODE
601      * attribute. The argument can either be the relative path
602      * of the class file itself or just the name of the class.
603      */
604     Class loadCode(String name) throws ClassNotFoundException {
605         // first convert any '/' or native file separator to .
606         name = name.replace('/', '.');
607         name = name.replace(File.separatorChar, '.');
608 
609         // deal with URL rewriting
610         String cookie = null;
611         int index = name.indexOf(";");
612         if(index != -1) {
613                 cookie = name.substring(index, name.length());
614                 name = name.substring(0, index);
615         }
616 
617         // save that name for later
618         String fullName = name;
619         // then strip off any suffixes
620         if (name.endsWith(".class") || name.endsWith(".java")) {
621             name = name.substring(0, name.lastIndexOf('.'));
622         }
623         try {
624                 if(cookie != null)
625                         name = (new StringBuffer(name)).append(cookie).toString();
626             return loadClass(name);
627         } catch (ClassNotFoundException e) {
628         }
629         // then if it didn't end with .java or .class, or in the
630         // really pathological case of a class named class or java
631         if(cookie != null)
632                 fullName = (new StringBuffer(fullName)).append(cookie).toString();
633 
634         return loadClass(fullName);
635     }
636 
637     /*
638      * The threadgroup that the applets loaded by this classloader live
639      * in. In the sun.* implementation of applets, the security manager's
640      * (AppletSecurity) getThreadGroup returns the thread group of the
641      * first applet on the stack, which is the applet's thread group.
642      */
643     private AppletThreadGroup threadGroup;
644     private AppContext appContext;
645 
646     public ThreadGroup getThreadGroup() {
647       synchronized (threadGroupSynchronizer) {
648         if (threadGroup == null || threadGroup.isDestroyed()) {
649             AccessController.doPrivileged(new PrivilegedAction() {
650                 public Object run() {
651                     threadGroup = new AppletThreadGroup(base + "-threadGroup");
652                     // threadGroup.setDaemon(true);
653                     // threadGroup is now destroyed by AppContext.dispose()
654 
655                     // Create the new AppContext from within a Thread belonging
656                     // to the newly created ThreadGroup, and wait for the
657                     // creation to complete before returning from this method.
658                     AppContextCreator creatorThread = new AppContextCreator(threadGroup);
659 
660                     // Since this thread will later be used to launch the
661                     // applet's AWT-event dispatch thread and we want the applet
662                     // code executing the AWT callbacks to use their own class
663                     // loader rather than the system class loader, explicitly
664                     // set the context class loader to the AppletClassLoader.
665                     creatorThread.setContextClassLoader(AppletClassLoader.this);
666 
667                     creatorThread.start();
668                     try {
669                         synchronized(creatorThread.syncObject) {
670                             while (!creatorThread.created) {
671                                 creatorThread.syncObject.wait();
672                             }
673                         }
674                     } catch (InterruptedException e) { }
675                     appContext = creatorThread.appContext;
676                     return null;
677                 }
678             });
679         }
680         return threadGroup;
681       }
682     }
683 
684     /*
685      * Get the AppContext, if any, corresponding to this AppletClassLoader.
686      */
687     public AppContext getAppContext()  {
688         return appContext;
689     }
690 
691     int usageCount = 0;
692 
693     /**
694      * Grab this AppletClassLoader and its ThreadGroup/AppContext, so they
695      * won't be destroyed.
696      */
697 public     void grab() {
698         synchronized(grabReleaseSynchronizer) {
699             usageCount++;
700         }
701         getThreadGroup(); // Make sure ThreadGroup/AppContext exist
702     }
703 
704     protected void setExceptionStatus()
705     {
706         exceptionStatus = true;
707     }
708 
709     public boolean getExceptionStatus()
710     {
711         return exceptionStatus;
712     }
713 
714     /**
715      * Release this AppletClassLoader and its ThreadGroup/AppContext.
716      * If nothing else has grabbed this AppletClassLoader, its ThreadGroup
717      * and AppContext will be destroyed.
718      *
719      * Because this method may destroy the AppletClassLoader's ThreadGroup,
720      * this method should NOT be called from within the AppletClassLoader's
721      * ThreadGroup.
722      *
723      * Changed modifier to protected in order to be able to overwrite this
724      * function in PluginClassLoader.java
725      */
726     protected void release() {
727 
728         AppContext tempAppContext = null;
729 
730         synchronized(grabReleaseSynchronizer) {
731             if (usageCount > 1)  {
732                 --usageCount;
733             } else {
734                 synchronized(threadGroupSynchronizer) {
735                     tempAppContext = resetAppContext();
736                 }
737             }
738         }
739 
740         // Dispose appContext outside any sync block to
741         // prevent potential deadlock.
742         if (tempAppContext != null)  {
743             try {
744                 tempAppContext.dispose(); // nuke the world!
745             } catch (IllegalThreadStateException e) { }
746         }
747     }
748 
749     /*
750      * reset classloader's AppContext and ThreadGroup
751      * This method is for subclass PluginClassLoader to
752      * reset superclass's AppContext and ThreadGroup but do
753      * not dispose the AppContext. PluginClassLoader does not
754      * use UsageCount to decide whether to dispose AppContext
755      *
756      * @return previous AppContext
757      */
758     protected AppContext resetAppContext() {
759         AppContext tempAppContext = null;
760 
761         synchronized(threadGroupSynchronizer) {
762             // Store app context in temp variable
763             tempAppContext = appContext;
764             usageCount = 0;
765             appContext = null;
766             threadGroup = null;
767         }
768         return tempAppContext;
769     }
770 
771 
772     // Hash map to store applet compatibility info
773     private HashMap jdk11AppletInfo = new HashMap();
774     private HashMap jdk12AppletInfo = new HashMap();
775 
776     /**
777      * Set applet target level as JDK 1.1.
778      *
779      * @param clazz Applet class.
780      * @param bool true if JDK is targeted for JDK 1.1;
781      *             false otherwise.
782      */
783     void setJDK11Target(Class clazz, boolean bool)
784     {
785          jdk11AppletInfo.put(clazz.toString(), Boolean.valueOf(bool));
786     }
787 
788     /**
789      * Set applet target level as JDK 1.2.
790      *
791      * @param clazz Applet class.
792      * @param bool true if JDK is targeted for JDK 1.2;
793      *             false otherwise.
794      */
795     void setJDK12Target(Class clazz, boolean bool)
796     {
797         jdk12AppletInfo.put(clazz.toString(), Boolean.valueOf(bool));
798     }
799 
800     /**
801      * Determine if applet is targeted for JDK 1.1.
802      *
803      * @param applet Applet class.
804      * @return TRUE if applet is targeted for JDK 1.1;
805      *         FALSE if applet is not;
806      *         null if applet is unknown.
807      */
808     Boolean isJDK11Target(Class clazz)
809     {
810         return (Boolean) jdk11AppletInfo.get(clazz.toString());
811     }
812 
813     /**
814      * Determine if applet is targeted for JDK 1.2.
815      *
816      * @param applet Applet class.
817      * @return TRUE if applet is targeted for JDK 1.2;
818      *         FALSE if applet is not;
819      *         null if applet is unknown.
820      */
821     Boolean isJDK12Target(Class clazz)
822     {
823         return (Boolean) jdk12AppletInfo.get(clazz.toString());
824     }
825 
826     private static AppletMessageHandler mh =
827         new AppletMessageHandler("appletclassloader");
828 
829     /*
830      * Prints a class loading error message.
831      */
832     private static void printError(String name, Throwable e) {
833         String s = null;
834         if (e == null) {
835             s = mh.getMessage("filenotfound", name);
836         } else if (e instanceof IOException) {
837             s = mh.getMessage("fileioexception", name);
838         } else if (e instanceof ClassFormatError) {
839             s = mh.getMessage("fileformat", name);
840         } else if (e instanceof ThreadDeath) {
841             s = mh.getMessage("filedeath", name);
842         } else if (e instanceof Error) {
843             s = mh.getMessage("fileerror", e.toString(), name);
844         }
845         if (s != null) {
846             System.err.println(s);
847         }
848     }
849 }
850 
851 /*
852  * The AppContextCreator class is used to create an AppContext from within
853  * a Thread belonging to the new AppContext's ThreadGroup.  To wait for
854  * this operation to complete before continuing, wait for the notifyAll()
855  * operation on the syncObject to occur.
856  */
857 class AppContextCreator extends Thread  {
858     Object syncObject = new Object();
859     AppContext appContext = null;
860     volatile boolean created = false;
861 
862     AppContextCreator(ThreadGroup group)  {
863         super(group, "AppContextCreator");
864     }
865 
866     public void run()  {
867         appContext = SunToolkit.createNewAppContext();
868         created = true;
869         synchronized(syncObject) {
870             syncObject.notifyAll();
871         }
872     } // run()
873 
874 } // class AppContextCreator