Use Tree Navigation
public class

AppletClassLoader

extends URLClassLoader
/*
 * Copyright (c) 1995, 2009, Oracle and/or its affiliates. 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.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.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.misc.IOUtils;
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 {
            b
= IOUtils.readFully(in, len, true);
       
} 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