jdk/src/share/classes/sun/applet/AppletPanel.java
changeset 2 90ce3da70b43
child 438 2ae294e4518c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/sun/applet/AppletPanel.java	Sat Dec 01 00:00:00 2007 +0000
@@ -0,0 +1,1319 @@
+/*
+ * Copyright 1995-2007 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.applet.*;
+import java.awt.*;
+import java.awt.event.*;
+import java.awt.image.ColorModel;
+import java.awt.image.MemoryImageSource;
+import java.io.*;
+import java.lang.ref.WeakReference;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.InetAddress;
+import java.net.JarURLConnection;
+import java.net.MalformedURLException;
+import java.net.SocketPermission;
+import java.net.URL;
+import java.net.UnknownHostException;
+import java.security.*;
+import java.util.*;
+import java.util.Collections;
+import java.util.Locale;
+import java.util.WeakHashMap;
+import javax.swing.SwingUtilities;
+import sun.awt.AppContext;
+import sun.awt.EmbeddedFrame;
+import sun.awt.SunToolkit;
+import sun.misc.MessageUtils;
+import sun.misc.PerformanceLogger;
+import sun.misc.Queue;
+import sun.security.util.SecurityConstants;
+
+/**
+ * Applet panel class. The panel manages and manipulates the
+ * applet as it is being loaded. It forks a separate thread in a new
+ * thread group to call the applet's init(), start(), stop(), and
+ * destroy() methods.
+ *
+ * @author      Arthur van Hoff
+ */
+public
+abstract class AppletPanel extends Panel implements AppletStub, Runnable {
+
+    /**
+     * The applet (if loaded).
+     */
+    Applet applet;
+
+    /**
+     * Applet will allow initialization.  Should be
+     * set to false if loading a serialized applet
+     * that was pickled in the init=true state.
+     */
+    protected boolean doInit = true;
+
+
+    /**
+     * The classloader for the applet.
+     */
+    AppletClassLoader loader;
+
+    /* applet event ids */
+    public final static int APPLET_DISPOSE = 0;
+    public final static int APPLET_LOAD = 1;
+    public final static int APPLET_INIT = 2;
+    public final static int APPLET_START = 3;
+    public final static int APPLET_STOP = 4;
+    public final static int APPLET_DESTROY = 5;
+    public final static int APPLET_QUIT = 6;
+    public final static int APPLET_ERROR = 7;
+
+    /* send to the parent to force relayout */
+    public final static int APPLET_RESIZE = 51234;
+
+    /* sent to a (distant) parent to indicate that the applet is being
+     * loaded or as completed loading
+     */
+    public final static int APPLET_LOADING = 51235;
+    public final static int APPLET_LOADING_COMPLETED = 51236;
+
+    /**
+     * The current status. One of:
+     *    APPLET_DISPOSE,
+     *    APPLET_LOAD,
+     *    APPLET_INIT,
+     *    APPLET_START,
+     *    APPLET_STOP,
+     *    APPLET_DESTROY,
+     *    APPLET_ERROR.
+     */
+    protected int status;
+
+    /**
+     * The thread for the applet.
+     */
+    Thread handler;
+
+
+    /**
+     * The initial applet size.
+     */
+    Dimension defaultAppletSize = new Dimension(10, 10);
+
+    /**
+     * The current applet size.
+     */
+    Dimension currentAppletSize = new Dimension(10, 10);
+
+    MessageUtils mu = new MessageUtils();
+
+    /**
+     * The thread to use during applet loading
+     */
+
+    Thread loaderThread = null;
+
+    /**
+     * Flag to indicate that a loading has been cancelled
+     */
+    boolean loadAbortRequest = false;
+
+    /* abstract classes */
+    abstract protected String getCode();
+    abstract protected String getJarFiles();
+    abstract protected String getSerializedObject();
+
+    abstract public int    getWidth();
+    abstract public int    getHeight();
+    abstract public boolean hasInitialFocus();
+
+    private static int threadGroupNumber = 0;
+
+    protected void setupAppletAppContext() {
+        // do nothing
+    }
+
+    /*
+     * Creates a thread to run the applet. This method is called
+     * each time an applet is loaded and reloaded.
+     */
+    synchronized void createAppletThread() {
+        // Create a thread group for the applet, and start a new
+        // thread to load the applet.
+        String nm = "applet-" + getCode();
+        loader = getClassLoader(getCodeBase(), getClassLoaderCacheKey());
+        loader.grab(); // Keep this puppy around!
+
+        // 4668479: Option to turn off codebase lookup in AppletClassLoader
+        // during resource requests. [stanley.ho]
+        String param = getParameter("codebase_lookup");
+
+        if (param != null && param.equals("false"))
+            loader.setCodebaseLookup(false);
+        else
+            loader.setCodebaseLookup(true);
+
+
+        ThreadGroup appletGroup = loader.getThreadGroup();
+
+        handler = new Thread(appletGroup, this, "thread " + nm);
+        // set the context class loader for this thread
+        AccessController.doPrivileged(new PrivilegedAction() {
+                public Object run() {
+                    handler.setContextClassLoader(loader);
+                    return null;
+                }
+            });
+        handler.start();
+    }
+
+    void joinAppletThread() throws InterruptedException {
+        if (handler != null) {
+            handler.join();
+            handler = null;
+        }
+    }
+
+    void release() {
+        if (loader != null) {
+            loader.release();
+            loader = null;
+        }
+    }
+
+    /**
+     * Construct an applet viewer and start the applet.
+     */
+    public void init() {
+        try {
+            // Get the width (if any)
+            defaultAppletSize.width = getWidth();
+            currentAppletSize.width = defaultAppletSize.width;
+
+            // Get the height (if any)
+            defaultAppletSize.height = getHeight();
+            currentAppletSize.height = defaultAppletSize.height;
+
+        } catch (NumberFormatException e) {
+            // Turn on the error flag and let TagAppletPanel
+            // do the right thing.
+            status = APPLET_ERROR;
+            showAppletStatus("badattribute.exception");
+            showAppletLog("badattribute.exception");
+            showAppletException(e);
+        }
+
+        setLayout(new BorderLayout());
+
+        createAppletThread();
+    }
+
+    /**
+     * Minimum size
+     */
+    public Dimension minimumSize() {
+        return new Dimension(defaultAppletSize.width,
+                             defaultAppletSize.height);
+    }
+
+    /**
+     * Preferred size
+     */
+    public Dimension preferredSize() {
+        return new Dimension(currentAppletSize.width,
+                             currentAppletSize.height);
+    }
+
+    private AppletListener listeners;
+
+    /**
+     * AppletEvent Queue
+     */
+    private Queue queue = null;
+
+
+    synchronized public void addAppletListener(AppletListener l) {
+        listeners = AppletEventMulticaster.add(listeners, l);
+    }
+
+    synchronized public void removeAppletListener(AppletListener l) {
+        listeners = AppletEventMulticaster.remove(listeners, l);
+    }
+
+    /**
+     * Dispatch event to the listeners..
+     */
+    public void dispatchAppletEvent(int id, Object argument) {
+        //System.out.println("SEND= " + id);
+        if (listeners != null) {
+            AppletEvent evt = new AppletEvent(this, id, argument);
+            listeners.appletStateChanged(evt);
+        }
+    }
+
+    /**
+     * Send an event. Queue it for execution by the handler thread.
+     */
+    public void sendEvent(int id) {
+        synchronized(this) {
+            if (queue == null) {
+                //System.out.println("SEND0= " + id);
+                queue = new Queue();
+            }
+            Integer eventId = new Integer(id);
+            queue.enqueue(eventId);
+            notifyAll();
+        }
+        if (id == APPLET_QUIT) {
+            try {
+                joinAppletThread(); // Let the applet event handler exit
+            } catch (InterruptedException e) {
+            }
+
+            // AppletClassLoader.release() must be called by a Thread
+            // not within the applet's ThreadGroup
+            if (loader == null)
+                loader = getClassLoader(getCodeBase(), getClassLoaderCacheKey());
+            release();
+        }
+    }
+
+    /**
+     * Get an event from the queue.
+     */
+    synchronized AppletEvent getNextEvent() throws InterruptedException {
+        while (queue == null || queue.isEmpty()) {
+            wait();
+        }
+        Integer eventId = (Integer)queue.dequeue();
+        return new AppletEvent(this, eventId.intValue(), null);
+    }
+
+    boolean emptyEventQueue() {
+        if ((queue == null) || (queue.isEmpty()))
+            return true;
+        else
+            return false;
+    }
+
+    /**
+     * This kludge is specific to get over AccessControlException thrown during
+     * Applet.stop() or destroy() when static thread is suspended.  Set a flag
+     * in AppletClassLoader to indicate that an
+     * AccessControlException for RuntimePermission "modifyThread" or
+     * "modifyThreadGroup" had occurred.
+     */
+     private void setExceptionStatus(AccessControlException e) {
+     Permission p = e.getPermission();
+     if (p instanceof RuntimePermission) {
+         if (p.getName().startsWith("modifyThread")) {
+             if (loader == null)
+                 loader = getClassLoader(getCodeBase(), getClassLoaderCacheKey());
+             loader.setExceptionStatus();
+         }
+     }
+     }
+
+    /**
+     * Execute applet events.
+     * Here is the state transition diagram
+     *
+     *   Note: (XXX) is the action
+     *         APPLET_XXX is the state
+     *  (applet code loaded) --> APPLET_LOAD -- (applet init called)--> APPLET_INIT -- (
+     *   applet start called) --> APPLET_START -- (applet stop called) -->APPLET_STOP --(applet
+     *   destroyed called) --> APPLET_DESTROY -->(applet gets disposed) -->
+     *   APPLET_DISPOSE -->....
+     *
+     * In the legacy lifecycle model. The applet gets loaded, inited and started. So it stays
+     * in the APPLET_START state unless the applet goes away(refresh page or leave the page).
+     * So the applet stop method called and the applet enters APPLET_STOP state. Then if the applet
+     * is revisited, it will call applet start method and enter the APPLET_START state and stay there.
+     *
+     * In the modern lifecycle model. When the applet first time visited, it is same as legacy lifecycle
+     * model. However, when the applet page goes away. It calls applet stop method and enters APPLET_STOP
+     * state and then applet destroyed method gets called and enters APPLET_DESTROY state.
+     *
+     * This code is also called by AppletViewer. In AppletViewer "Restart" menu, the applet is jump from
+     * APPLET_STOP to APPLET_DESTROY and to APPLET_INIT .
+     *
+     * Also, the applet can jump from APPLET_INIT state to APPLET_DESTROY (in Netscape/Mozilla case).
+         * Same as APPLET_LOAD to
+     * APPLET_DISPOSE since all of this are triggered by browser.
+     *
+     *
+     */
+    public void run() {
+
+        Thread curThread = Thread.currentThread();
+        if (curThread == loaderThread) {
+            // if we are in the loader thread, cause
+            // loading to occur.  We may exit this with
+            // status being APPLET_DISPOSE, APPLET_ERROR,
+            // or APPLET_LOAD
+            runLoader();
+            return;
+        }
+
+        boolean disposed = false;
+        while (!disposed && !curThread.isInterrupted()) {
+            AppletEvent evt;
+            try {
+                evt = getNextEvent();
+            } catch (InterruptedException e) {
+                showAppletStatus("bail");
+                return;
+            }
+
+            //showAppletStatus("EVENT = " + evt.getID());
+            try {
+                switch (evt.getID()) {
+                  case APPLET_LOAD:
+                      if (!okToLoad()) {
+                          break;
+                      }
+                      // This complexity allows loading of applets to be
+                      // interruptable.  The actual thread loading runs
+                      // in a separate thread, so it can be interrupted
+                      // without harming the applet thread.
+                      // So that we don't have to worry about
+                      // concurrency issues, the main applet thread waits
+                      // until the loader thread terminates.
+                      // (one way or another).
+                      if (loaderThread == null) {
+                          // REMIND: do we want a name?
+                          //System.out.println("------------------- loading applet");
+                          setLoaderThread(new Thread(this));
+                          loaderThread.start();
+                          // we get to go to sleep while this runs
+                          loaderThread.join();
+                          setLoaderThread(null);
+                      } else {
+                          // REMIND: issue an error -- this case should never
+                          // occur.
+                      }
+                      break;
+
+                  case APPLET_INIT:
+                    // AppletViewer "Restart" will jump from destroy method to
+                    // init, that is why we need to check status w/ APPLET_DESTROY
+                      if (status != APPLET_LOAD && status != APPLET_DESTROY) {
+                          showAppletStatus("notloaded");
+                          break;
+                      }
+                      applet.resize(defaultAppletSize);
+                      if (doInit) {
+                          if (PerformanceLogger.loggingEnabled()) {
+                              PerformanceLogger.setTime("Applet Init");
+                              PerformanceLogger.outputLog();
+                          }
+                          applet.init();
+                      }
+
+                      //Need the default(fallback) font to be created in this AppContext
+                      Font f = getFont();
+                      if (f == null ||
+                          "dialog".equals(f.getFamily().toLowerCase(Locale.ENGLISH)) &&
+                          f.getSize() == 12 && f.getStyle() == Font.PLAIN) {
+                          setFont(new Font(Font.DIALOG, Font.PLAIN, 12));
+                      }
+
+                      doInit = true;    // allow restarts
+
+                      // Validate the applet in event dispatch thread
+                      // to avoid deadlock.
+                      try {
+                          final AppletPanel p = this;
+
+                          SwingUtilities.invokeAndWait(new Runnable() {
+                                  public void run() {
+                                      p.validate();
+                                  }
+                              });
+                      }
+                      catch(InterruptedException ie) {
+                      }
+                      catch(InvocationTargetException ite) {
+                      }
+
+                      status = APPLET_INIT;
+                      showAppletStatus("inited");
+                      break;
+
+                  case APPLET_START:
+                  {
+                      if (status != APPLET_INIT && status != APPLET_STOP) {
+                          showAppletStatus("notinited");
+                          break;
+                      }
+                      applet.resize(currentAppletSize);
+                      applet.start();
+
+                      // Validate and show the applet in event dispatch thread
+                      // to avoid deadlock.
+                      try {
+                          final AppletPanel p = this;
+                          final Applet a = applet;
+
+                          SwingUtilities.invokeAndWait(new Runnable() {
+                                  public void run() {
+                                      p.validate();
+                                      a.setVisible(true);
+
+                                      // Fix for BugTraq ID 4041703.
+                                      // Set the default focus for an applet.
+                                      if (hasInitialFocus())
+                                        setDefaultFocus();
+                                  }
+                              });
+                      }
+                      catch(InterruptedException ie) {
+                      }
+                      catch(InvocationTargetException ite) {
+                      }
+
+                      status = APPLET_START;
+                      showAppletStatus("started");
+                      break;
+                  }
+
+                case APPLET_STOP:
+                    if (status != APPLET_START) {
+                        showAppletStatus("notstarted");
+                        break;
+                    }
+                    status = APPLET_STOP;
+
+                    // Hide the applet in event dispatch thread
+                    // to avoid deadlock.
+                    try {
+                        final Applet a = applet;
+
+                        SwingUtilities.invokeAndWait(new Runnable() {
+                                public void run()
+                                {
+                                    a.setVisible(false);
+                                }
+                            });
+                    }
+                    catch(InterruptedException ie) {
+                    }
+                    catch(InvocationTargetException ite) {
+                    }
+
+
+                    // During Applet.stop(), any AccessControlException on an involved Class remains in
+                    // the "memory" of the AppletClassLoader.  If the same instance of the ClassLoader is
+                    // reused, the same exception will occur during class loading.  Set the AppletClassLoader's
+                    // exceptionStatusSet flag to allow recognition of what had happened
+                    // when reusing AppletClassLoader object.
+                    try {
+                        applet.stop();
+                    } catch (java.security.AccessControlException e) {
+                        setExceptionStatus(e);
+                        // rethrow exception to be handled as it normally would be.
+                        throw e;
+                    }
+                    showAppletStatus("stopped");
+                    break;
+
+                case APPLET_DESTROY:
+                    if (status != APPLET_STOP && status != APPLET_INIT) {
+                        showAppletStatus("notstopped");
+                        break;
+                    }
+                    status = APPLET_DESTROY;
+
+                    // During Applet.destroy(), any AccessControlException on an involved Class remains in
+                    // the "memory" of the AppletClassLoader.  If the same instance of the ClassLoader is
+                    // reused, the same exception will occur during class loading.  Set the AppletClassLoader's
+                    // exceptionStatusSet flag to allow recognition of what had happened
+                    // when reusing AppletClassLoader object.
+                    try {
+                        applet.destroy();
+                    } catch (java.security.AccessControlException e) {
+                        setExceptionStatus(e);
+                        // rethrow exception to be handled as it normally would be.
+                        throw e;
+                    }
+                    showAppletStatus("destroyed");
+                    break;
+
+                case APPLET_DISPOSE:
+                    if (status != APPLET_DESTROY && status != APPLET_LOAD) {
+                        showAppletStatus("notdestroyed");
+                        break;
+                    }
+                    status = APPLET_DISPOSE;
+
+                    try
+                    {
+                        final Applet a = applet;
+
+                        EventQueue.invokeAndWait(new Runnable()
+                        {
+                            public void run()
+                            {
+                                remove(a);
+                            }
+                        });
+                    }
+                    catch(InterruptedException ie)
+                    {
+                    }
+                    catch(InvocationTargetException ite)
+                    {
+                    }
+                    applet = null;
+                    showAppletStatus("disposed");
+                    disposed = true;
+                    break;
+
+                case APPLET_QUIT:
+                    return;
+                }
+            } catch (Exception e) {
+                status = APPLET_ERROR;
+                if (e.getMessage() != null) {
+                    showAppletStatus("exception2", e.getClass().getName(),
+                                     e.getMessage());
+                } else {
+                    showAppletStatus("exception", e.getClass().getName());
+                }
+                showAppletException(e);
+            } catch (ThreadDeath e) {
+                showAppletStatus("death");
+                return;
+            } catch (Error e) {
+                status = APPLET_ERROR;
+                if (e.getMessage() != null) {
+                    showAppletStatus("error2", e.getClass().getName(),
+                                     e.getMessage());
+                } else {
+                    showAppletStatus("error", e.getClass().getName());
+                }
+                showAppletException(e);
+            }
+            clearLoadAbortRequest();
+        }
+    }
+
+    /**
+     * Gets most recent focus owner component associated with the given window.
+     * It does that without calling Window.getMostRecentFocusOwner since it
+     * provides its own logic contradicting with setDefautlFocus. Instead, it
+     * calls KeyboardFocusManager directly.
+     */
+    private Component getMostRecentFocusOwnerForWindow(Window w) {
+        Method meth = (Method)AccessController.doPrivileged(new PrivilegedAction() {
+                public Object run() {
+                    Method meth = null;
+                    try {
+                        meth = KeyboardFocusManager.class.getDeclaredMethod("getMostRecentFocusOwner", new Class[] {Window.class});
+                        meth.setAccessible(true);
+                    } catch (Exception e) {
+                        // Must never happen
+                        e.printStackTrace();
+                    }
+                    return meth;
+                }
+            });
+        if (meth != null) {
+            // Meth refers static method
+            try {
+                return (Component)meth.invoke(null, new Object[] {w});
+            } catch (Exception e) {
+                // Must never happen
+                e.printStackTrace();
+            }
+        }
+        // Will get here if exception was thrown or meth is null
+        return w.getMostRecentFocusOwner();
+    }
+
+    /*
+     * Fix for BugTraq ID 4041703.
+     * Set the focus to a reasonable default for an Applet.
+     */
+    private void setDefaultFocus() {
+        Component toFocus = null;
+        Container parent = getParent();
+
+        if(parent != null) {
+            if (parent instanceof Window) {
+                toFocus = getMostRecentFocusOwnerForWindow((Window)parent);
+                if (toFocus == parent || toFocus == null) {
+                    toFocus = parent.getFocusTraversalPolicy().
+                        getInitialComponent((Window)parent);
+                }
+            } else if (parent.isFocusCycleRoot()) {
+                toFocus = parent.getFocusTraversalPolicy().
+                    getDefaultComponent(parent);
+            }
+        }
+
+        if (toFocus != null) {
+            if (parent instanceof EmbeddedFrame) {
+                ((EmbeddedFrame)parent).synthesizeWindowActivation(true);
+            }
+            // EmbeddedFrame might have focus before the applet was added.
+            // Thus after its activation the most recent focus owner will be
+            // restored. We need the applet's initial focusabled component to
+            // be focused here.
+            toFocus.requestFocusInWindow();
+        }
+    }
+
+    /**
+     * Load the applet into memory.
+     * Runs in a seperate (and interruptible) thread from the rest of the
+     * applet event processing so that it can be gracefully interrupted from
+     * things like HotJava.
+     */
+    private void runLoader() {
+        if (status != APPLET_DISPOSE) {
+            showAppletStatus("notdisposed");
+            return;
+        }
+
+        dispatchAppletEvent(APPLET_LOADING, null);
+
+        // REMIND -- might be cool to visually indicate loading here --
+        // maybe do animation?
+        status = APPLET_LOAD;
+
+        // Create a class loader
+        loader = getClassLoader(getCodeBase(), getClassLoaderCacheKey());
+
+        // Load the archives if present.
+        // REMIND - this probably should be done in a separate thread,
+        // or at least the additional archives (epll).
+
+        String code = getCode();
+
+        // setup applet AppContext
+        // this must be called before loadJarFiles
+        setupAppletAppContext();
+
+        try {
+            loadJarFiles(loader);
+            applet = createApplet(loader);
+        } catch (ClassNotFoundException e) {
+            status = APPLET_ERROR;
+            showAppletStatus("notfound", code);
+            showAppletLog("notfound", code);
+            showAppletException(e);
+            return;
+        } catch (InstantiationException e) {
+            status = APPLET_ERROR;
+            showAppletStatus("nocreate", code);
+            showAppletLog("nocreate", code);
+            showAppletException(e);
+            return;
+        } catch (IllegalAccessException e) {
+            status = APPLET_ERROR;
+            showAppletStatus("noconstruct", code);
+            showAppletLog("noconstruct", code);
+            showAppletException(e);
+            // sbb -- I added a return here
+            return;
+        } catch (Exception e) {
+            status = APPLET_ERROR;
+            showAppletStatus("exception", e.getMessage());
+            showAppletException(e);
+            return;
+        } catch (ThreadDeath e) {
+            status = APPLET_ERROR;
+            showAppletStatus("death");
+            return;
+        } catch (Error e) {
+            status = APPLET_ERROR;
+            showAppletStatus("error", e.getMessage());
+            showAppletException(e);
+            return;
+        } finally {
+            // notify that loading is no longer going on
+            dispatchAppletEvent(APPLET_LOADING_COMPLETED, null);
+        }
+
+        // Fixed #4508194: NullPointerException thrown during
+        // quick page switch
+        //
+        if (applet != null)
+        {
+            // Stick it in the frame
+            applet.setStub(this);
+            applet.hide();
+            add("Center", applet);
+            showAppletStatus("loaded");
+            validate();
+        }
+    }
+
+    protected Applet createApplet(final AppletClassLoader loader) throws ClassNotFoundException,
+                                                                         IllegalAccessException, IOException, InstantiationException, InterruptedException {
+        final String serName = getSerializedObject();
+        String code = getCode();
+
+        if (code != null && serName != null) {
+            System.err.println(amh.getMessage("runloader.err"));
+//          return null;
+            throw new InstantiationException("Either \"code\" or \"object\" should be specified, but not both.");
+        }
+        if (code == null && serName == null) {
+            String msg = "nocode";
+            status = APPLET_ERROR;
+            showAppletStatus(msg);
+            showAppletLog(msg);
+            repaint();
+        }
+        if (code != null) {
+            applet = (Applet)loader.loadCode(code).newInstance();
+            doInit = true;
+        } else {
+            // serName is not null;
+            InputStream is = (InputStream)
+                java.security.AccessController.doPrivileged(
+                                                            new java.security.PrivilegedAction() {
+                                                                public Object run() {
+                                                                    return loader.getResourceAsStream(serName);
+                                                                }
+                                                            });
+            ObjectInputStream ois =
+                new AppletObjectInputStream(is, loader);
+            Object serObject = ois.readObject();
+            applet = (Applet) serObject;
+            doInit = false; // skip over the first init
+        }
+
+        // Determine the JDK level that the applet targets.
+        // This is critical for enabling certain backward
+        // compatibility switch if an applet is a JDK 1.1
+        // applet. [stanley.ho]
+        findAppletJDKLevel(applet);
+
+        if (Thread.interrupted()) {
+            try {
+                status = APPLET_DISPOSE; // APPLET_ERROR?
+                applet = null;
+                // REMIND: This may not be exactly the right thing: the
+                // status is set by the stop button and not necessarily
+                // here.
+                showAppletStatus("death");
+            } finally {
+                Thread.currentThread().interrupt(); // resignal interrupt
+            }
+            return null;
+        }
+        return applet;
+    }
+
+    protected void loadJarFiles(AppletClassLoader loader) throws IOException,
+                                                                 InterruptedException {
+        // Load the archives if present.
+        // REMIND - this probably should be done in a separate thread,
+        // or at least the additional archives (epll).
+        String jarFiles = getJarFiles();
+
+        if (jarFiles != null) {
+            StringTokenizer st = new StringTokenizer(jarFiles, ",", false);
+            while(st.hasMoreTokens()) {
+                String tok = st.nextToken().trim();
+                try {
+                    loader.addJar(tok);
+                } catch (IllegalArgumentException e) {
+                    // bad archive name
+                    continue;
+                }
+            }
+        }
+    }
+
+    /**
+     * Request that the loading of the applet be stopped.
+     */
+    protected synchronized void stopLoading() {
+        // REMIND: fill in the body
+        if (loaderThread != null) {
+            //System.out.println("Interrupting applet loader thread: " + loaderThread);
+            loaderThread.interrupt();
+        } else {
+            setLoadAbortRequest();
+        }
+    }
+
+
+    protected synchronized boolean okToLoad() {
+        return !loadAbortRequest;
+    }
+
+    protected synchronized void clearLoadAbortRequest() {
+        loadAbortRequest = false;
+    }
+
+    protected synchronized void setLoadAbortRequest() {
+        loadAbortRequest = true;
+    }
+
+
+    private synchronized void setLoaderThread(Thread loaderThread) {
+        this.loaderThread = loaderThread;
+    }
+
+    /**
+     * Return true when the applet has been started.
+     */
+    public boolean isActive() {
+        return status == APPLET_START;
+    }
+
+
+    private EventQueue appEvtQ = null;
+    /**
+     * Is called when the applet wants to be resized.
+     */
+    public void appletResize(int width, int height) {
+        currentAppletSize.width = width;
+        currentAppletSize.height = height;
+        final Dimension currentSize = new Dimension(currentAppletSize.width,
+                                                    currentAppletSize.height);
+
+        if(loader != null) {
+            AppContext appCtxt = loader.getAppContext();
+            if(appCtxt != null)
+                appEvtQ = (java.awt.EventQueue)appCtxt.get(AppContext.EVENT_QUEUE_KEY);
+        }
+
+        final AppletPanel ap = this;
+        if (appEvtQ != null){
+            appEvtQ.postEvent(new InvocationEvent(Toolkit.getDefaultToolkit(),
+                                                  new Runnable(){
+                                                      public void run(){
+                                                          if(ap != null)
+                                                          {
+                                                              ap.dispatchAppletEvent(APPLET_RESIZE, currentSize);
+                                                          }
+                                                      }
+                                                  }));
+        }
+    }
+
+    public void setBounds(int x, int y, int width, int height) {
+        super.setBounds(x, y, width, height);
+        currentAppletSize.width = width;
+        currentAppletSize.height = height;
+    }
+
+    public Applet getApplet() {
+        return applet;
+    }
+
+    /**
+     * Status line. Called by the AppletPanel to provide
+     * feedback on the Applet's state.
+     */
+    protected void showAppletStatus(String status) {
+        getAppletContext().showStatus(amh.getMessage(status));
+    }
+
+    protected void showAppletStatus(String status, Object arg) {
+        getAppletContext().showStatus(amh.getMessage(status, arg));
+    }
+    protected void showAppletStatus(String status, Object arg1, Object arg2) {
+        getAppletContext().showStatus(amh.getMessage(status, arg1, arg2));
+    }
+
+    /**
+     * Called by the AppletPanel to print to the log.
+     */
+    protected void showAppletLog(String msg) {
+        System.out.println(amh.getMessage(msg));
+    }
+
+    protected void showAppletLog(String msg, Object arg) {
+        System.out.println(amh.getMessage(msg, arg));
+    }
+
+    /**
+     * Called by the AppletPanel to provide
+     * feedback when an exception has happened.
+     */
+    protected void showAppletException(Throwable t) {
+        t.printStackTrace();
+        repaint();
+    }
+
+    /**
+     * Get caching key for classloader cache
+     */
+    public String getClassLoaderCacheKey()
+    {
+        /**
+         * Fixed #4501142: Classlaoder sharing policy doesn't
+         * take "archive" into account. This will be overridden
+         * by Java Plug-in.                     [stanleyh]
+         */
+        return getCodeBase().toString();
+    }
+
+    /**
+     * The class loaders
+     */
+    private static HashMap classloaders = new HashMap();
+
+    /**
+     * Flush a class loader.
+     */
+    public static synchronized void flushClassLoader(String key) {
+        classloaders.remove(key);
+    }
+
+    /**
+     * Flush all class loaders.
+     */
+    public static synchronized void flushClassLoaders() {
+        classloaders = new HashMap();
+    }
+
+    /**
+     * This method actually creates an AppletClassLoader.
+     *
+     * It can be override by subclasses (such as the Plug-in)
+     * to provide different classloaders.
+     */
+    protected AppletClassLoader createClassLoader(final URL codebase) {
+        return new AppletClassLoader(codebase);
+    }
+
+    /**
+     * Get a class loader. Create in a restricted context
+     */
+    synchronized AppletClassLoader getClassLoader(final URL codebase, final String key) {
+        AppletClassLoader c = (AppletClassLoader)classloaders.get(key);
+        if (c == null) {
+            AccessControlContext acc =
+                getAccessControlContext(codebase);
+            c = (AppletClassLoader)
+                AccessController.doPrivileged(new PrivilegedAction() {
+                        public Object run() {
+                            AppletClassLoader ac = createClassLoader(codebase);
+                            /* Should the creation of the classloader be
+                             * within the class synchronized block?  Since
+                             * this class is used by the plugin, take care
+                             * to avoid deadlocks, or specialize
+                             * AppletPanel within the plugin.  It may take
+                             * an arbitrary amount of time to create a
+                             * class loader (involving getting Jar files
+                             * etc.) and may block unrelated applets from
+                             * finishing createAppletThread (due to the
+                             * class synchronization). If
+                             * createAppletThread does not finish quickly,
+                             * the applet cannot process other messages,
+                             * particularly messages such as destroy
+                             * (which timeout when called from the browser).
+                             */
+                            synchronized (getClass()) {
+                                AppletClassLoader res =
+                                    (AppletClassLoader)classloaders.get(key);
+                                if (res == null) {
+                                    classloaders.put(key, ac);
+                                    return ac;
+                                } else {
+                                    return res;
+                                }
+                            }
+                        }
+                    },acc);
+        }
+        return c;
+    }
+
+    /**
+     * get the context for the AppletClassLoader we are creating.
+     * the context is granted permission to create the class loader,
+     * connnect to the codebase, and whatever else the policy grants
+     * to all codebases.
+     */
+    private AccessControlContext getAccessControlContext(final URL codebase) {
+
+        PermissionCollection perms = (PermissionCollection)
+            AccessController.doPrivileged(new PrivilegedAction() {
+                    public Object run() {
+                        Policy p = java.security.Policy.getPolicy();
+                        if (p != null) {
+                            return p.getPermissions(new CodeSource(null,
+                                                                   (java.security.cert.Certificate[]) null));
+                        } else {
+                            return null;
+                        }
+                    }
+                });
+
+        if (perms == null)
+            perms = new Permissions();
+
+        //XXX: this is needed to be able to create the classloader itself!
+
+        perms.add(SecurityConstants.CREATE_CLASSLOADER_PERMISSION);
+
+        Permission p;
+        java.net.URLConnection urlConnection = null;
+        try {
+            urlConnection = codebase.openConnection();
+            p = urlConnection.getPermission();
+        } catch (java.io.IOException ioe) {
+            p = null;
+        }
+
+        if (p != null)
+            perms.add(p);
+
+        if (p instanceof FilePermission) {
+
+            String path = p.getName();
+
+            int endIndex = path.lastIndexOf(File.separatorChar);
+
+            if (endIndex != -1) {
+                path = path.substring(0, endIndex+1);
+
+                if (path.endsWith(File.separator)) {
+                    path += "-";
+                }
+                perms.add(new FilePermission(path,
+                                             SecurityConstants.FILE_READ_ACTION));
+            }
+        } else {
+            URL locUrl = codebase;
+            if (urlConnection instanceof JarURLConnection) {
+                locUrl = ((JarURLConnection)urlConnection).getJarFileURL();
+            }
+            String host = locUrl.getHost();
+            if (host != null && (host.length() > 0))
+                perms.add(new SocketPermission(host,
+                                               SecurityConstants.SOCKET_CONNECT_ACCEPT_ACTION));
+        }
+
+        ProtectionDomain domain =
+            new ProtectionDomain(new CodeSource(codebase,
+                                                (java.security.cert.Certificate[]) null), perms);
+        AccessControlContext acc =
+            new AccessControlContext(new ProtectionDomain[] { domain });
+
+        return acc;
+    }
+
+    public Thread getAppletHandlerThread() {
+        return handler;
+    }
+
+    public int getAppletWidth() {
+        return currentAppletSize.width;
+    }
+
+    public int getAppletHeight() {
+        return currentAppletSize.height;
+    }
+
+    public static void changeFrameAppContext(Frame frame, AppContext newAppContext)
+    {
+        // Fixed #4754451: Applet can have methods running on main
+        // thread event queue.
+        //
+        // The cause of this bug is that the frame of the applet
+        // is created in main thread group. Thus, when certain
+        // AWT/Swing events are generated, the events will be
+        // dispatched through the wrong event dispatch thread.
+        //
+        // To fix this, we rearrange the AppContext with the frame,
+        // so the proper event queue will be looked up.
+        //
+        // Swing also maintains a Frame list for the AppContext,
+        // so we will have to rearrange it as well.
+
+        // Check if frame's AppContext has already been set properly
+        AppContext oldAppContext = SunToolkit.targetToAppContext(frame);
+
+        if (oldAppContext == newAppContext)
+            return;
+
+        // Synchronization on Window.class is needed for locking the
+        // critical section of the window list in AppContext.
+        synchronized (Window.class)
+        {
+            WeakReference weakRef = null;
+            // Remove frame from the Window list in wrong AppContext
+            {
+                // Lookup current frame's AppContext
+                Vector<WeakReference<Window>> windowList = (Vector<WeakReference<Window>>)oldAppContext.get(Window.class);
+                if (windowList != null) {
+                    for (WeakReference ref : windowList) {
+                        if (ref.get() == frame) {
+                            weakRef = ref;
+                            break;
+                        }
+                    }
+                    // Remove frame from wrong AppContext
+                    if (weakRef != null)
+                        windowList.remove(weakRef);
+                }
+            }
+
+            // Put the frame into the applet's AppContext map
+            SunToolkit.insertTargetMapping(frame, newAppContext);
+
+            // Insert frame into the Window list in the applet's AppContext map
+            {
+                Vector<WeakReference<Window>> windowList = (Vector)newAppContext.get(Window.class);
+                if (windowList == null) {
+                    windowList = new Vector<WeakReference<Window>>();
+                    newAppContext.put(Window.class, windowList);
+                }
+                // use the same weakRef here as it is used elsewhere
+                windowList.add(weakRef);
+            }
+        }
+    }
+
+    // Flag to indicate if applet is targeted for JDK 1.1.
+    private boolean jdk11Applet = false;
+
+    // Flag to indicate if applet is targeted for JDK 1.2.
+    private boolean jdk12Applet = false;
+
+    /**
+     * Determine JDK level of an applet.
+     */
+    private void findAppletJDKLevel(Applet applet)
+    {
+        // To determine the JDK level of an applet, the
+        // most reliable way is to check the major version
+        // of the applet class file.
+
+        // synchronized on applet class object, so calling from
+        // different instances of the same applet will be
+        // serialized.
+        Class appletClass = applet.getClass();
+
+        synchronized(appletClass)  {
+            // Determine if the JDK level of an applet has been
+            // checked before.
+            Boolean jdk11Target = (Boolean) loader.isJDK11Target(appletClass);
+            Boolean jdk12Target = (Boolean) loader.isJDK12Target(appletClass);
+
+            // if applet JDK level has been checked before, retrieve
+            // value and return.
+            if (jdk11Target != null || jdk12Target != null) {
+                jdk11Applet = (jdk11Target == null) ? false : jdk11Target.booleanValue();
+                jdk12Applet = (jdk12Target == null) ? false : jdk12Target.booleanValue();
+                return;
+            }
+
+            String name = appletClass.getName();
+
+            // first convert any '.' to '/'
+            name = name.replace('.', '/');
+
+            // append .class
+            final String resourceName = name + ".class";
+
+            InputStream is = null;
+            byte[] classHeader = new byte[8];
+
+            try {
+                is = (InputStream) java.security.AccessController.doPrivileged(
+                    new java.security.PrivilegedAction() {
+                        public Object run() {
+                            return loader.getResourceAsStream(resourceName);
+                        }
+                    });
+
+                // Read the first 8 bytes of the class file
+                int byteRead = is.read(classHeader, 0, 8);
+                is.close();
+
+                // return if the header is not read in entirely
+                // for some reasons.
+                if (byteRead != 8)
+                    return;
+            }
+            catch (IOException e)   {
+                return;
+            }
+
+            // Check major version in class file header
+            int major_version = readShort(classHeader, 6);
+
+            // Major version in class file is as follows:
+            //   45 - JDK 1.1
+            //   46 - JDK 1.2
+            //   47 - JDK 1.3
+            //   48 - JDK 1.4
+            //   49 - JDK 1.5
+            if (major_version < 46)
+                jdk11Applet = true;
+            else if (major_version == 46)
+                jdk12Applet = true;
+
+            // Store applet JDK level in AppContext for later lookup,
+            // e.g. page switch.
+            loader.setJDK11Target(appletClass, jdk11Applet);
+            loader.setJDK12Target(appletClass, jdk12Applet);
+        }
+    }
+
+    /**
+     * Return true if applet is targeted to JDK 1.1.
+     */
+    protected boolean isJDK11Applet()   {
+        return jdk11Applet;
+    }
+
+    /**
+     * Return true if applet is targeted to JDK1.2.
+     */
+    protected boolean isJDK12Applet()   {
+        return jdk12Applet;
+    }
+
+    /**
+     * Read short from byte array.
+     */
+    private int readShort(byte[] b, int off)    {
+        int hi = readByte(b[off]);
+        int lo = readByte(b[off + 1]);
+        return (hi << 8) | lo;
+    }
+
+    private int readByte(byte b) {
+        return ((int)b) & 0xFF;
+    }
+
+
+    private static AppletMessageHandler amh = new AppletMessageHandler("appletpanel");
+}