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