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