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