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