jdk/src/share/classes/sun/applet/AppletClassLoader.java
changeset 2 90ce3da70b43
child 3111 fefdeafb7ab9
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/sun/applet/AppletClassLoader.java	Sat Dec 01 00:00:00 2007 +0000
@@ -0,0 +1,861 @@
+/*
+ * Copyright 1995-2005 Sun Microsystems, Inc.  All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package sun.applet;
+
+import java.lang.NullPointerException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.net.SocketPermission;
+import java.net.URLConnection;
+import java.net.MalformedURLException;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.io.File;
+import java.io.FilePermission;
+import java.io.IOException;
+import java.io.BufferedInputStream;
+import java.io.InputStream;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.NoSuchElementException;
+import java.security.AccessController;
+import java.security.AccessControlContext;
+import java.security.PrivilegedAction;
+import java.security.PrivilegedExceptionAction;
+import java.security.PrivilegedActionException;
+import java.security.CodeSource;
+import java.security.Permission;
+import java.security.PermissionCollection;
+import sun.awt.AppContext;
+import sun.awt.SunToolkit;
+import sun.net.www.ParseUtil;
+import sun.security.util.SecurityConstants;
+
+/**
+ * This class defines the class loader for loading applet classes and
+ * resources. It extends URLClassLoader to search the applet code base
+ * for the class or resource after checking any loaded JAR files.
+ */
+public class AppletClassLoader extends URLClassLoader {
+    private URL base;   /* applet code base URL */
+    private CodeSource codesource; /* codesource for the base URL */
+    private AccessControlContext acc;
+    private boolean exceptionStatus = false;
+
+    private final Object threadGroupSynchronizer = new Object();
+    private final Object grabReleaseSynchronizer = new Object();
+
+    private boolean codebaseLookup = true;
+
+    /*
+     * Creates a new AppletClassLoader for the specified base URL.
+     */
+    protected AppletClassLoader(URL base) {
+        super(new URL[0]);
+        this.base = base;
+        this.codesource =
+            new CodeSource(base, (java.security.cert.Certificate[]) null);
+        acc = AccessController.getContext();
+    }
+
+    /**
+     * Set the codebase lookup flag.
+     */
+    void setCodebaseLookup(boolean codebaseLookup)  {
+        this.codebaseLookup = codebaseLookup;
+    }
+
+    /*
+     * Returns the applet code base URL.
+     */
+    URL getBaseURL() {
+        return base;
+    }
+
+    /*
+     * Returns the URLs used for loading classes and resources.
+     */
+    public URL[] getURLs() {
+        URL[] jars = super.getURLs();
+        URL[] urls = new URL[jars.length + 1];
+        System.arraycopy(jars, 0, urls, 0, jars.length);
+        urls[urls.length - 1] = base;
+        return urls;
+    }
+
+    /*
+     * Adds the specified JAR file to the search path of loaded JAR files.
+     * Changed modifier to protected in order to be able to overwrite addJar()
+     * in PluginClassLoader.java
+     */
+    protected void addJar(String name) throws IOException {
+        URL url;
+        try {
+            url = new URL(base, name);
+        } catch (MalformedURLException e) {
+            throw new IllegalArgumentException("name");
+        }
+        addURL(url);
+        // DEBUG
+        //URL[] urls = getURLs();
+        //for (int i = 0; i < urls.length; i++) {
+        //    System.out.println("url[" + i + "] = " + urls[i]);
+        //}
+    }
+
+    /*
+     * Override loadClass so that class loading errors can be caught in
+     * order to print better error messages.
+     */
+    public synchronized Class loadClass(String name, boolean resolve)
+        throws ClassNotFoundException
+    {
+        // First check if we have permission to access the package. This
+        // should go away once we've added support for exported packages.
+        int i = name.lastIndexOf('.');
+        if (i != -1) {
+            SecurityManager sm = System.getSecurityManager();
+            if (sm != null)
+                sm.checkPackageAccess(name.substring(0, i));
+        }
+        try {
+            return super.loadClass(name, resolve);
+        } catch (ClassNotFoundException e) {
+            //printError(name, e.getException());
+            throw e;
+        } catch (RuntimeException e) {
+            //printError(name, e);
+            throw e;
+        } catch (Error e) {
+            //printError(name, e);
+            throw e;
+        }
+    }
+
+    /*
+     * Finds the applet class with the specified name. First searches
+     * loaded JAR files then the applet code base for the class.
+     */
+    protected Class findClass(String name) throws ClassNotFoundException {
+
+        int index = name.indexOf(";");
+        String cookie = "";
+        if(index != -1) {
+                cookie = name.substring(index, name.length());
+                name = name.substring(0, index);
+        }
+
+        // check loaded JAR files
+        try {
+            return super.findClass(name);
+        } catch (ClassNotFoundException e) {
+        }
+
+        // Otherwise, try loading the class from the code base URL
+
+        // 4668479: Option to turn off codebase lookup in AppletClassLoader
+        // during resource requests. [stanley.ho]
+        if (codebaseLookup == false)
+            throw new ClassNotFoundException(name);
+
+//      final String path = name.replace('.', '/').concat(".class").concat(cookie);
+        String encodedName = ParseUtil.encodePath(name.replace('.', '/'), false);
+        final String path = (new StringBuffer(encodedName)).append(".class").append(cookie).toString();
+        try {
+            byte[] b = (byte[]) AccessController.doPrivileged(
+                               new PrivilegedExceptionAction() {
+                public Object run() throws IOException {
+                    return getBytes(new URL(base, path));
+                }
+            }, acc);
+
+            if (b != null) {
+                return defineClass(name, b, 0, b.length, codesource);
+            } else {
+                throw new ClassNotFoundException(name);
+            }
+        } catch (PrivilegedActionException e) {
+            throw new ClassNotFoundException(name, e.getException());
+        }
+    }
+
+    /**
+     * Returns the permissions for the given codesource object.
+     * The implementation of this method first calls super.getPermissions,
+     * to get the permissions
+     * granted by the super class, and then adds additional permissions
+     * based on the URL of the codesource.
+     * <p>
+     * If the protocol is "file"
+     * and the path specifies a file, permission is granted to read all files
+     * and (recursively) all files and subdirectories contained in
+     * that directory. This is so applets with a codebase of
+     * file:/blah/some.jar can read in file:/blah/, which is needed to
+     * be backward compatible. We also add permission to connect back to
+     * the "localhost".
+     *
+     * @param codesource the codesource
+     * @return the permissions granted to the codesource
+     */
+    protected PermissionCollection getPermissions(CodeSource codesource)
+    {
+        final PermissionCollection perms = super.getPermissions(codesource);
+
+        URL url = codesource.getLocation();
+
+        String path = null;
+        Permission p;
+
+        try {
+            p = url.openConnection().getPermission();
+        } catch (java.io.IOException ioe) {
+            p = null;
+        }
+
+        if (p instanceof FilePermission) {
+            path = p.getName();
+        } else if ((p == null) && (url.getProtocol().equals("file"))) {
+            path = url.getFile().replace('/', File.separatorChar);
+            path = ParseUtil.decode(path);
+        }
+
+        if (path != null) {
+            if (!path.endsWith(File.separator)) {
+                int endIndex = path.lastIndexOf(File.separatorChar);
+                if (endIndex != -1) {
+                        path = path.substring(0, endIndex+1) + "-";
+                        perms.add(new FilePermission(path,
+                            SecurityConstants.FILE_READ_ACTION));
+                }
+            }
+            perms.add(new SocketPermission("localhost",
+                SecurityConstants.SOCKET_CONNECT_ACCEPT_ACTION));
+            AccessController.doPrivileged(new PrivilegedAction() {
+                public Object run() {
+                    try {
+                        String host = InetAddress.getLocalHost().getHostName();
+                        perms.add(new SocketPermission(host,
+                            SecurityConstants.SOCKET_CONNECT_ACCEPT_ACTION));
+                    } catch (UnknownHostException uhe) {
+
+                    }
+                    return null;
+                }
+            });
+
+            Permission bperm;
+            try {
+                bperm = base.openConnection().getPermission();
+            } catch (java.io.IOException ioe) {
+                bperm = null;
+            }
+            if (bperm instanceof FilePermission) {
+                String bpath = bperm.getName();
+                if (bpath.endsWith(File.separator)) {
+                    bpath += "-";
+                }
+                perms.add(new FilePermission(bpath,
+                    SecurityConstants.FILE_READ_ACTION));
+            } else if ((bperm == null) && (base.getProtocol().equals("file"))) {
+                String bpath = base.getFile().replace('/', File.separatorChar);
+                bpath = ParseUtil.decode(bpath);
+                if (bpath.endsWith(File.separator)) {
+                    bpath += "-";
+                }
+                perms.add(new FilePermission(bpath, SecurityConstants.FILE_READ_ACTION));
+            }
+
+        }
+        return perms;
+    }
+
+    /*
+     * Returns the contents of the specified URL as an array of bytes.
+     */
+    private static byte[] getBytes(URL url) throws IOException {
+        URLConnection uc = url.openConnection();
+        if (uc instanceof java.net.HttpURLConnection) {
+            java.net.HttpURLConnection huc = (java.net.HttpURLConnection) uc;
+            int code = huc.getResponseCode();
+            if (code >= java.net.HttpURLConnection.HTTP_BAD_REQUEST) {
+                throw new IOException("open HTTP connection failed.");
+            }
+        }
+        int len = uc.getContentLength();
+
+        // Fixed #4507227: Slow performance to load
+        // class and resources. [stanleyh]
+        //
+        // Use buffered input stream [stanleyh]
+        InputStream in = new BufferedInputStream(uc.getInputStream());
+
+        byte[] b;
+        try {
+            if (len != -1) {
+                // Read exactly len bytes from the input stream
+                b = new byte[len];
+                while (len > 0) {
+                    int n = in.read(b, b.length - len, len);
+                    if (n == -1) {
+                        throw new IOException("unexpected EOF");
+                    }
+                    len -= n;
+                }
+            } else {
+                // Read until end of stream is reached - use 8K buffer
+                // to speed up performance [stanleyh]
+                b = new byte[8192];
+                int total = 0;
+                while ((len = in.read(b, total, b.length - total)) != -1) {
+                    total += len;
+                    if (total >= b.length) {
+                        byte[] tmp = new byte[total * 2];
+                        System.arraycopy(b, 0, tmp, 0, total);
+                        b = tmp;
+                    }
+                }
+                // Trim array to correct size, if necessary
+                if (total != b.length) {
+                    byte[] tmp = new byte[total];
+                    System.arraycopy(b, 0, tmp, 0, total);
+                    b = tmp;
+                }
+            }
+        } finally {
+            in.close();
+        }
+        return b;
+    }
+
+    // Object for synchronization around getResourceAsStream()
+    private Object syncResourceAsStream = new Object();
+    private Object syncResourceAsStreamFromJar = new Object();
+
+    // Flag to indicate getResourceAsStream() is in call
+    private boolean resourceAsStreamInCall = false;
+    private boolean resourceAsStreamFromJarInCall = false;
+
+    /**
+     * Returns an input stream for reading the specified resource.
+     *
+     * The search order is described in the documentation for {@link
+     * #getResource(String)}.<p>
+     *
+     * @param  name the resource name
+     * @return an input stream for reading the resource, or <code>null</code>
+     *         if the resource could not be found
+     * @since  JDK1.1
+     */
+    public InputStream getResourceAsStream(String name)
+    {
+
+        if (name == null) {
+            throw new NullPointerException("name");
+        }
+
+        try
+        {
+            InputStream is = null;
+
+            // Fixed #4507227: Slow performance to load
+            // class and resources. [stanleyh]
+            //
+            // The following is used to avoid calling
+            // AppletClassLoader.findResource() in
+            // super.getResourceAsStream(). Otherwise,
+            // unnecessary connection will be made.
+            //
+            synchronized(syncResourceAsStream)
+            {
+                resourceAsStreamInCall = true;
+
+                // Call super class
+                is = super.getResourceAsStream(name);
+
+                resourceAsStreamInCall = false;
+            }
+
+            // 4668479: Option to turn off codebase lookup in AppletClassLoader
+            // during resource requests. [stanley.ho]
+            if (codebaseLookup == true && is == null)
+            {
+                // If resource cannot be obtained,
+                // try to download it from codebase
+                URL url = new URL(base, ParseUtil.encodePath(name, false));
+                is = url.openStream();
+            }
+
+            return is;
+        }
+        catch (Exception e)
+        {
+            return null;
+        }
+    }
+
+
+    /**
+     * Returns an input stream for reading the specified resource from the
+     * the loaded jar files.
+     *
+     * The search order is described in the documentation for {@link
+     * #getResource(String)}.<p>
+     *
+     * @param  name the resource name
+     * @return an input stream for reading the resource, or <code>null</code>
+     *         if the resource could not be found
+     * @since  JDK1.1
+     */
+    public InputStream getResourceAsStreamFromJar(String name) {
+
+        if (name == null) {
+            throw new NullPointerException("name");
+        }
+
+        try {
+            InputStream is = null;
+            synchronized(syncResourceAsStreamFromJar) {
+                resourceAsStreamFromJarInCall = true;
+                // Call super class
+                is = super.getResourceAsStream(name);
+                resourceAsStreamFromJarInCall = false;
+            }
+
+            return is;
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+
+    /*
+     * Finds the applet resource with the specified name. First checks
+     * loaded JAR files then the applet code base for the resource.
+     */
+    public URL findResource(String name) {
+        // check loaded JAR files
+        URL url = super.findResource(name);
+
+        // 6215746:  Disable META-INF/* lookup from codebase in
+        // applet/plugin classloader. [stanley.ho]
+        if (name.startsWith("META-INF/"))
+            return url;
+
+        // 4668479: Option to turn off codebase lookup in AppletClassLoader
+        // during resource requests. [stanley.ho]
+        if (codebaseLookup == false)
+            return url;
+
+        if (url == null)
+        {
+            //#4805170, if it is a call from Applet.getImage()
+            //we should check for the image only in the archives
+            boolean insideGetResourceAsStreamFromJar = false;
+                synchronized(syncResourceAsStreamFromJar) {
+                insideGetResourceAsStreamFromJar = resourceAsStreamFromJarInCall;
+            }
+
+            if (insideGetResourceAsStreamFromJar) {
+                return null;
+            }
+
+            // Fixed #4507227: Slow performance to load
+            // class and resources. [stanleyh]
+            //
+            // Check if getResourceAsStream is called.
+            //
+            boolean insideGetResourceAsStream = false;
+
+            synchronized(syncResourceAsStream)
+            {
+                insideGetResourceAsStream = resourceAsStreamInCall;
+            }
+
+            // If getResourceAsStream is called, don't
+            // trigger the following code. Otherwise,
+            // unnecessary connection will be made.
+            //
+            if (insideGetResourceAsStream == false)
+            {
+                // otherwise, try the code base
+                try {
+                    url = new URL(base, ParseUtil.encodePath(name, false));
+                    // check if resource exists
+                    if(!resourceExists(url))
+                        url = null;
+                } catch (Exception e) {
+                    // all exceptions, including security exceptions, are caught
+                    url = null;
+                }
+            }
+        }
+        return url;
+    }
+
+
+    private boolean resourceExists(URL url) {
+        // Check if the resource exists.
+        // It almost works to just try to do an openConnection() but
+        // HttpURLConnection will return true on HTTP_BAD_REQUEST
+        // when the requested name ends in ".html", ".htm", and ".txt"
+        // and we want to be able to handle these
+        //
+        // Also, cannot just open a connection for things like FileURLConnection,
+        // because they succeed when connecting to a nonexistent file.
+        // So, in those cases we open and close an input stream.
+        boolean ok = true;
+        try {
+            URLConnection conn = url.openConnection();
+            if (conn instanceof java.net.HttpURLConnection) {
+                java.net.HttpURLConnection hconn =
+                    (java.net.HttpURLConnection) conn;
+
+                // To reduce overhead, using http HEAD method instead of GET method
+                hconn.setRequestMethod("HEAD");
+
+                int code = hconn.getResponseCode();
+                if (code == java.net.HttpURLConnection.HTTP_OK) {
+                    return true;
+                }
+                if (code >= java.net.HttpURLConnection.HTTP_BAD_REQUEST) {
+                    return false;
+                }
+            } else {
+                /**
+                 * Fix for #4182052 - stanleyh
+                 *
+                 * The same connection should be reused to avoid multiple
+                 * HTTP connections
+                 */
+
+                // our best guess for the other cases
+                InputStream is = conn.getInputStream();
+                is.close();
+            }
+        } catch (Exception ex) {
+            ok = false;
+        }
+        return ok;
+    }
+
+    /*
+     * Returns an enumeration of all the applet resources with the specified
+     * name. First checks loaded JAR files then the applet code base for all
+     * available resources.
+     */
+    public Enumeration findResources(String name) throws IOException {
+
+        final Enumeration e = super.findResources(name);
+
+        // 6215746:  Disable META-INF/* lookup from codebase in
+        // applet/plugin classloader. [stanley.ho]
+        if (name.startsWith("META-INF/"))
+            return e;
+
+        // 4668479: Option to turn off codebase lookup in AppletClassLoader
+        // during resource requests. [stanley.ho]
+        if (codebaseLookup == false)
+            return e;
+
+        URL u = new URL(base, ParseUtil.encodePath(name, false));
+        if (!resourceExists(u)) {
+            u = null;
+        }
+
+        final URL url = u;
+        return new Enumeration() {
+            private boolean done;
+            public Object nextElement() {
+                if (!done) {
+                    if (e.hasMoreElements()) {
+                        return e.nextElement();
+                    }
+                    done = true;
+                    if (url != null) {
+                        return url;
+                    }
+                }
+                throw new NoSuchElementException();
+            }
+            public boolean hasMoreElements() {
+                return !done && (e.hasMoreElements() || url != null);
+            }
+        };
+    }
+
+    /*
+     * Load and resolve the file specified by the applet tag CODE
+     * attribute. The argument can either be the relative path
+     * of the class file itself or just the name of the class.
+     */
+    Class loadCode(String name) throws ClassNotFoundException {
+        // first convert any '/' or native file separator to .
+        name = name.replace('/', '.');
+        name = name.replace(File.separatorChar, '.');
+
+        // deal with URL rewriting
+        String cookie = null;
+        int index = name.indexOf(";");
+        if(index != -1) {
+                cookie = name.substring(index, name.length());
+                name = name.substring(0, index);
+        }
+
+        // save that name for later
+        String fullName = name;
+        // then strip off any suffixes
+        if (name.endsWith(".class") || name.endsWith(".java")) {
+            name = name.substring(0, name.lastIndexOf('.'));
+        }
+        try {
+                if(cookie != null)
+                        name = (new StringBuffer(name)).append(cookie).toString();
+            return loadClass(name);
+        } catch (ClassNotFoundException e) {
+        }
+        // then if it didn't end with .java or .class, or in the
+        // really pathological case of a class named class or java
+        if(cookie != null)
+                fullName = (new StringBuffer(fullName)).append(cookie).toString();
+
+        return loadClass(fullName);
+    }
+
+    /*
+     * The threadgroup that the applets loaded by this classloader live
+     * in. In the sun.* implementation of applets, the security manager's
+     * (AppletSecurity) getThreadGroup returns the thread group of the
+     * first applet on the stack, which is the applet's thread group.
+     */
+    private AppletThreadGroup threadGroup;
+    private AppContext appContext;
+
+    public ThreadGroup getThreadGroup() {
+      synchronized (threadGroupSynchronizer) {
+        if (threadGroup == null || threadGroup.isDestroyed()) {
+            AccessController.doPrivileged(new PrivilegedAction() {
+                public Object run() {
+                    threadGroup = new AppletThreadGroup(base + "-threadGroup");
+                    // threadGroup.setDaemon(true);
+                    // threadGroup is now destroyed by AppContext.dispose()
+
+                    // Create the new AppContext from within a Thread belonging
+                    // to the newly created ThreadGroup, and wait for the
+                    // creation to complete before returning from this method.
+                    AppContextCreator creatorThread = new AppContextCreator(threadGroup);
+
+                    // Since this thread will later be used to launch the
+                    // applet's AWT-event dispatch thread and we want the applet
+                    // code executing the AWT callbacks to use their own class
+                    // loader rather than the system class loader, explicitly
+                    // set the context class loader to the AppletClassLoader.
+                    creatorThread.setContextClassLoader(AppletClassLoader.this);
+
+                    synchronized(creatorThread.syncObject)  {
+                        creatorThread.start();
+                        try {
+                            creatorThread.syncObject.wait();
+                        } catch (InterruptedException e) { }
+                        appContext = creatorThread.appContext;
+                    }
+                    return null;
+                }
+            });
+        }
+        return threadGroup;
+      }
+    }
+
+    /*
+     * Get the AppContext, if any, corresponding to this AppletClassLoader.
+     */
+    public AppContext getAppContext()  {
+        return appContext;
+    }
+
+    int usageCount = 0;
+
+    /**
+     * Grab this AppletClassLoader and its ThreadGroup/AppContext, so they
+     * won't be destroyed.
+     */
+    void grab() {
+        synchronized(grabReleaseSynchronizer) {
+            usageCount++;
+        }
+        getThreadGroup(); // Make sure ThreadGroup/AppContext exist
+    }
+
+    protected void setExceptionStatus()
+    {
+        exceptionStatus = true;
+    }
+
+    public boolean getExceptionStatus()
+    {
+        return exceptionStatus;
+    }
+
+    /**
+     * Release this AppletClassLoader and its ThreadGroup/AppContext.
+     * If nothing else has grabbed this AppletClassLoader, its ThreadGroup
+     * and AppContext will be destroyed.
+     *
+     * Because this method may destroy the AppletClassLoader's ThreadGroup,
+     * this method should NOT be called from within the AppletClassLoader's
+     * ThreadGroup.
+     *
+     * Changed modifier to protected in order to be able to overwrite this
+     * function in PluginClassLoader.java
+     */
+    protected void release() {
+
+        AppContext tempAppContext = null;
+
+        synchronized(grabReleaseSynchronizer) {
+            if (usageCount > 1)  {
+                --usageCount;
+            } else {
+                synchronized(threadGroupSynchronizer) {
+                    // Store app context in temp variable
+                    tempAppContext = appContext;
+                    usageCount = 0;
+                    appContext = null;
+                    threadGroup = null;
+                }
+            }
+        }
+
+        // Dispose appContext outside any sync block to
+        // prevent potential deadlock.
+        if (tempAppContext != null)  {
+            try {
+                tempAppContext.dispose(); // nuke the world!
+            } catch (IllegalThreadStateException e) { }
+        }
+    }
+
+    // Hash map to store applet compatibility info
+    private HashMap jdk11AppletInfo = new HashMap();
+    private HashMap jdk12AppletInfo = new HashMap();
+
+    /**
+     * Set applet target level as JDK 1.1.
+     *
+     * @param clazz Applet class.
+     * @param bool true if JDK is targeted for JDK 1.1;
+     *             false otherwise.
+     */
+    void setJDK11Target(Class clazz, boolean bool)
+    {
+         jdk11AppletInfo.put(clazz.toString(), Boolean.valueOf(bool));
+    }
+
+    /**
+     * Set applet target level as JDK 1.2.
+     *
+     * @param clazz Applet class.
+     * @param bool true if JDK is targeted for JDK 1.2;
+     *             false otherwise.
+     */
+    void setJDK12Target(Class clazz, boolean bool)
+    {
+        jdk12AppletInfo.put(clazz.toString(), Boolean.valueOf(bool));
+    }
+
+    /**
+     * Determine if applet is targeted for JDK 1.1.
+     *
+     * @param applet Applet class.
+     * @return TRUE if applet is targeted for JDK 1.1;
+     *         FALSE if applet is not;
+     *         null if applet is unknown.
+     */
+    Boolean isJDK11Target(Class clazz)
+    {
+        return (Boolean) jdk11AppletInfo.get(clazz.toString());
+    }
+
+    /**
+     * Determine if applet is targeted for JDK 1.2.
+     *
+     * @param applet Applet class.
+     * @return TRUE if applet is targeted for JDK 1.2;
+     *         FALSE if applet is not;
+     *         null if applet is unknown.
+     */
+    Boolean isJDK12Target(Class clazz)
+    {
+        return (Boolean) jdk12AppletInfo.get(clazz.toString());
+    }
+
+    private static AppletMessageHandler mh =
+        new AppletMessageHandler("appletclassloader");
+
+    /*
+     * Prints a class loading error message.
+     */
+    private static void printError(String name, Throwable e) {
+        String s = null;
+        if (e == null) {
+            s = mh.getMessage("filenotfound", name);
+        } else if (e instanceof IOException) {
+            s = mh.getMessage("fileioexception", name);
+        } else if (e instanceof ClassFormatError) {
+            s = mh.getMessage("fileformat", name);
+        } else if (e instanceof ThreadDeath) {
+            s = mh.getMessage("filedeath", name);
+        } else if (e instanceof Error) {
+            s = mh.getMessage("fileerror", e.toString(), name);
+        }
+        if (s != null) {
+            System.err.println(s);
+        }
+    }
+}
+
+/*
+ * The AppContextCreator class is used to create an AppContext from within
+ * a Thread belonging to the new AppContext's ThreadGroup.  To wait for
+ * this operation to complete before continuing, wait for the notifyAll()
+ * operation on the syncObject to occur.
+ */
+class AppContextCreator extends Thread  {
+    Object syncObject = new Object();
+    AppContext appContext = null;
+
+    AppContextCreator(ThreadGroup group)  {
+        super(group, "AppContextCreator");
+    }
+
+    public void run()  {
+        synchronized(syncObject)  {
+            appContext = SunToolkit.createNewAppContext();
+            syncObject.notifyAll();
+        }
+    } // run()
+
+} // class AppContextCreator