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