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