jdk/src/java.desktop/share/classes/sun/awt/AppContext.java
changeset 25859 3317bb8137f4
parent 25529 a6c103245f63
child 26037 508779ce6619
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.desktop/share/classes/sun/awt/AppContext.java	Sun Aug 17 15:54:13 2014 +0100
@@ -0,0 +1,933 @@
+/*
+ * Copyright (c) 1998, 2013, 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.awt;
+
+import java.awt.EventQueue;
+import java.awt.Window;
+import java.awt.SystemTray;
+import java.awt.TrayIcon;
+import java.awt.Toolkit;
+import java.awt.GraphicsEnvironment;
+import java.awt.event.InvocationEvent;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.HashSet;
+import java.beans.PropertyChangeSupport;
+import java.beans.PropertyChangeListener;
+import java.lang.ref.SoftReference;
+import sun.util.logging.PlatformLogger;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Supplier;
+
+/**
+ * The AppContext is a table referenced by ThreadGroup which stores
+ * application service instances.  (If you are not writing an application
+ * service, or don't know what one is, please do not use this class.)
+ * The AppContext allows applet access to what would otherwise be
+ * potentially dangerous services, such as the ability to peek at
+ * EventQueues or change the look-and-feel of a Swing application.<p>
+ *
+ * Most application services use a singleton object to provide their
+ * services, either as a default (such as getSystemEventQueue or
+ * getDefaultToolkit) or as static methods with class data (System).
+ * The AppContext works with the former method by extending the concept
+ * of "default" to be ThreadGroup-specific.  Application services
+ * lookup their singleton in the AppContext.<p>
+ *
+ * For example, here we have a Foo service, with its pre-AppContext
+ * code:<p>
+ * <code><pre>
+ *    public class Foo {
+ *        private static Foo defaultFoo = new Foo();
+ *
+ *        public static Foo getDefaultFoo() {
+ *            return defaultFoo;
+ *        }
+ *
+ *    ... Foo service methods
+ *    }</pre></code><p>
+ *
+ * The problem with the above is that the Foo service is global in scope,
+ * so that applets and other untrusted code can execute methods on the
+ * single, shared Foo instance.  The Foo service therefore either needs
+ * to block its use by untrusted code using a SecurityManager test, or
+ * restrict its capabilities so that it doesn't matter if untrusted code
+ * executes it.<p>
+ *
+ * Here's the Foo class written to use the AppContext:<p>
+ * <code><pre>
+ *    public class Foo {
+ *        public static Foo getDefaultFoo() {
+ *            Foo foo = (Foo)AppContext.getAppContext().get(Foo.class);
+ *            if (foo == null) {
+ *                foo = new Foo();
+ *                getAppContext().put(Foo.class, foo);
+ *            }
+ *            return foo;
+ *        }
+ *
+ *    ... Foo service methods
+ *    }</pre></code><p>
+ *
+ * Since a separate AppContext can exist for each ThreadGroup, trusted
+ * and untrusted code have access to different Foo instances.  This allows
+ * untrusted code access to "system-wide" services -- the service remains
+ * within the AppContext "sandbox".  For example, say a malicious applet
+ * wants to peek all of the key events on the EventQueue to listen for
+ * passwords; if separate EventQueues are used for each ThreadGroup
+ * using AppContexts, the only key events that applet will be able to
+ * listen to are its own.  A more reasonable applet request would be to
+ * change the Swing default look-and-feel; with that default stored in
+ * an AppContext, the applet's look-and-feel will change without
+ * disrupting other applets or potentially the browser itself.<p>
+ *
+ * Because the AppContext is a facility for safely extending application
+ * service support to applets, none of its methods may be blocked by a
+ * a SecurityManager check in a valid Java implementation.  Applets may
+ * therefore safely invoke any of its methods without worry of being
+ * blocked.
+ *
+ * Note: If a SecurityManager is installed which derives from
+ * sun.awt.AWTSecurityManager, it may override the
+ * AWTSecurityManager.getAppContext() method to return the proper
+ * AppContext based on the execution context, in the case where
+ * the default ThreadGroup-based AppContext indexing would return
+ * the main "system" AppContext.  For example, in an applet situation,
+ * if a system thread calls into an applet, rather than returning the
+ * main "system" AppContext (the one corresponding to the system thread),
+ * an installed AWTSecurityManager may return the applet's AppContext
+ * based on the execution context.
+ *
+ * @author  Thomas Ball
+ * @author  Fred Ecks
+ */
+public final class AppContext {
+    private static final PlatformLogger log = PlatformLogger.getLogger("sun.awt.AppContext");
+
+    /* Since the contents of an AppContext are unique to each Java
+     * session, this class should never be serialized. */
+
+    /*
+     * The key to put()/get() the Java EventQueue into/from the AppContext.
+     */
+    public static final Object EVENT_QUEUE_KEY = new StringBuffer("EventQueue");
+
+    /*
+     * The keys to store EventQueue push/pop lock and condition.
+     */
+    public final static Object EVENT_QUEUE_LOCK_KEY = new StringBuilder("EventQueue.Lock");
+    public final static Object EVENT_QUEUE_COND_KEY = new StringBuilder("EventQueue.Condition");
+
+    /* A map of AppContexts, referenced by ThreadGroup.
+     */
+    private static final Map<ThreadGroup, AppContext> threadGroup2appContext =
+            Collections.synchronizedMap(new IdentityHashMap<ThreadGroup, AppContext>());
+
+    /**
+     * Returns a set containing all <code>AppContext</code>s.
+     */
+    public static Set<AppContext> getAppContexts() {
+        synchronized (threadGroup2appContext) {
+            return new HashSet<AppContext>(threadGroup2appContext.values());
+        }
+    }
+
+    /* The main "system" AppContext, used by everything not otherwise
+       contained in another AppContext. It is implicitly created for
+       standalone apps only (i.e. not applets)
+     */
+    private static volatile AppContext mainAppContext = null;
+
+    private static class GetAppContextLock {};
+    private final static Object getAppContextLock = new GetAppContextLock();
+
+    /*
+     * The hash map associated with this AppContext.  A private delegate
+     * is used instead of subclassing HashMap so as to avoid all of
+     * HashMap's potentially risky methods, such as clear(), elements(),
+     * putAll(), etc.
+     */
+    private final Map<Object, Object> table = new HashMap<>();
+
+    private final ThreadGroup threadGroup;
+
+    /**
+     * If any <code>PropertyChangeListeners</code> have been registered,
+     * the <code>changeSupport</code> field describes them.
+     *
+     * @see #addPropertyChangeListener
+     * @see #removePropertyChangeListener
+     * @see #firePropertyChange
+     */
+    private PropertyChangeSupport changeSupport = null;
+
+    public static final String DISPOSED_PROPERTY_NAME = "disposed";
+    public static final String GUI_DISPOSED = "guidisposed";
+
+    private enum State {
+        VALID,
+        BEING_DISPOSED,
+        DISPOSED
+    };
+
+    private volatile State state = State.VALID;
+
+    public boolean isDisposed() {
+        return state == State.DISPOSED;
+    }
+
+    /*
+     * The total number of AppContexts, system-wide.  This number is
+     * incremented at the beginning of the constructor, and decremented
+     * at the end of dispose().  getAppContext() checks to see if this
+     * number is 1.  If so, it returns the sole AppContext without
+     * checking Thread.currentThread().
+     */
+    private static final AtomicInteger numAppContexts = new AtomicInteger(0);
+
+
+    /*
+     * The context ClassLoader that was used to create this AppContext.
+     */
+    private final ClassLoader contextClassLoader;
+
+    /**
+     * Constructor for AppContext.  This method is <i>not</i> public,
+     * nor should it ever be used as such.  The proper way to construct
+     * an AppContext is through the use of SunToolkit.createNewAppContext.
+     * A ThreadGroup is created for the new AppContext, a Thread is
+     * created within that ThreadGroup, and that Thread calls
+     * SunToolkit.createNewAppContext before calling anything else.
+     * That creates both the new AppContext and its EventQueue.
+     *
+     * @param   threadGroup     The ThreadGroup for the new AppContext
+     * @see     sun.awt.SunToolkit
+     * @since   1.2
+     */
+    AppContext(ThreadGroup threadGroup) {
+        numAppContexts.incrementAndGet();
+
+        this.threadGroup = threadGroup;
+        threadGroup2appContext.put(threadGroup, this);
+
+        this.contextClassLoader =
+             AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
+                    public ClassLoader run() {
+                        return Thread.currentThread().getContextClassLoader();
+                    }
+                });
+
+        // Initialize push/pop lock and its condition to be used by all the
+        // EventQueues within this AppContext
+        Lock eventQueuePushPopLock = new ReentrantLock();
+        put(EVENT_QUEUE_LOCK_KEY, eventQueuePushPopLock);
+        Condition eventQueuePushPopCond = eventQueuePushPopLock.newCondition();
+        put(EVENT_QUEUE_COND_KEY, eventQueuePushPopCond);
+    }
+
+    private static final ThreadLocal<AppContext> threadAppContext =
+            new ThreadLocal<AppContext>();
+
+    private final static void initMainAppContext() {
+        // On the main Thread, we get the ThreadGroup, make a corresponding
+        // AppContext, and instantiate the Java EventQueue.  This way, legacy
+        // code is unaffected by the move to multiple AppContext ability.
+        AccessController.doPrivileged(new PrivilegedAction<Void>() {
+            public Void run() {
+                ThreadGroup currentThreadGroup =
+                        Thread.currentThread().getThreadGroup();
+                ThreadGroup parentThreadGroup = currentThreadGroup.getParent();
+                while (parentThreadGroup != null) {
+                    // Find the root ThreadGroup to construct our main AppContext
+                    currentThreadGroup = parentThreadGroup;
+                    parentThreadGroup = currentThreadGroup.getParent();
+                }
+
+                mainAppContext = SunToolkit.createNewAppContext(currentThreadGroup);
+                return null;
+            }
+        });
+    }
+
+    /**
+     * Returns the appropriate AppContext for the caller,
+     * as determined by its ThreadGroup.  If the main "system" AppContext
+     * would be returned and there's an AWTSecurityManager installed, it
+     * is called to get the proper AppContext based on the execution
+     * context.
+     *
+     * @return  the AppContext for the caller.
+     * @see     java.lang.ThreadGroup
+     * @since   1.2
+     */
+    public final static AppContext getAppContext() {
+        // we are standalone app, return the main app context
+        if (numAppContexts.get() == 1 && mainAppContext != null) {
+            return mainAppContext;
+        }
+
+        AppContext appContext = threadAppContext.get();
+
+        if (null == appContext) {
+            appContext = AccessController.doPrivileged(new PrivilegedAction<AppContext>()
+            {
+                public AppContext run() {
+                    // Get the current ThreadGroup, and look for it and its
+                    // parents in the hash from ThreadGroup to AppContext --
+                    // it should be found, because we use createNewContext()
+                    // when new AppContext objects are created.
+                    ThreadGroup currentThreadGroup = Thread.currentThread().getThreadGroup();
+                    ThreadGroup threadGroup = currentThreadGroup;
+
+                    // Special case: we implicitly create the main app context
+                    // if no contexts have been created yet. This covers standalone apps
+                    // and excludes applets because by the time applet starts
+                    // a number of contexts have already been created by the plugin.
+                    synchronized (getAppContextLock) {
+                        if (numAppContexts.get() == 0) {
+                            if (System.getProperty("javaplugin.version") == null &&
+                                    System.getProperty("javawebstart.version") == null) {
+                                initMainAppContext();
+                            } else if (System.getProperty("javafx.version") != null &&
+                                    threadGroup.getParent() != null) {
+                                // Swing inside JavaFX case
+                                SunToolkit.createNewAppContext();
+                            }
+                        }
+                    }
+
+                    AppContext context = threadGroup2appContext.get(threadGroup);
+                    while (context == null) {
+                        threadGroup = threadGroup.getParent();
+                        if (threadGroup == null) {
+                            // We've got up to the root thread group and did not find an AppContext
+                            // Try to get it from the security manager
+                            SecurityManager securityManager = System.getSecurityManager();
+                            if (securityManager != null) {
+                                ThreadGroup smThreadGroup = securityManager.getThreadGroup();
+                                if (smThreadGroup != null) {
+                                    /*
+                                     * If we get this far then it's likely that
+                                     * the ThreadGroup does not actually belong
+                                     * to the applet, so do not cache it.
+                                     */
+                                    return threadGroup2appContext.get(smThreadGroup);
+                                }
+                            }
+                            return null;
+                        }
+                        context = threadGroup2appContext.get(threadGroup);
+                    }
+
+                    // In case we did anything in the above while loop, we add
+                    // all the intermediate ThreadGroups to threadGroup2appContext
+                    // so we won't spin again.
+                    for (ThreadGroup tg = currentThreadGroup; tg != threadGroup; tg = tg.getParent()) {
+                        threadGroup2appContext.put(tg, context);
+                    }
+
+                    // Now we're done, so we cache the latest key/value pair.
+                    threadAppContext.set(context);
+
+                    return context;
+                }
+            });
+        }
+
+        return appContext;
+    }
+
+    /**
+     * Returns true if the specified AppContext is the main AppContext.
+     *
+     * @param   ctx the context to compare with the main context
+     * @return  true if the specified AppContext is the main AppContext.
+     * @since   1.8
+     */
+    public final static boolean isMainContext(AppContext ctx) {
+        return (ctx != null && ctx == mainAppContext);
+    }
+
+    private final static AppContext getExecutionAppContext() {
+        SecurityManager securityManager = System.getSecurityManager();
+        if ((securityManager != null) &&
+            (securityManager instanceof AWTSecurityManager))
+        {
+            AWTSecurityManager awtSecMgr = (AWTSecurityManager) securityManager;
+            AppContext secAppContext = awtSecMgr.getAppContext();
+            return secAppContext; // Return what we're told
+        }
+        return null;
+    }
+
+    private long DISPOSAL_TIMEOUT = 5000;  // Default to 5-second timeout
+                                           // for disposal of all Frames
+                                           // (we wait for this time twice,
+                                           // once for dispose(), and once
+                                           // to clear the EventQueue).
+
+    private long THREAD_INTERRUPT_TIMEOUT = 1000;
+                            // Default to 1-second timeout for all
+                            // interrupted Threads to exit, and another
+                            // 1 second for all stopped Threads to die.
+
+    /**
+     * Disposes of this AppContext, all of its top-level Frames, and
+     * all Threads and ThreadGroups contained within it.
+     *
+     * This method must be called from a Thread which is not contained
+     * within this AppContext.
+     *
+     * @exception  IllegalThreadStateException  if the current thread is
+     *                                    contained within this AppContext
+     * @since      1.2
+     */
+    public void dispose() throws IllegalThreadStateException {
+        // Check to be sure that the current Thread isn't in this AppContext
+        if (this.threadGroup.parentOf(Thread.currentThread().getThreadGroup())) {
+            throw new IllegalThreadStateException(
+                "Current Thread is contained within AppContext to be disposed."
+              );
+        }
+
+        synchronized(this) {
+            if (this.state != State.VALID) {
+                return; // If already disposed or being disposed, bail.
+            }
+
+            this.state = State.BEING_DISPOSED;
+        }
+
+        final PropertyChangeSupport changeSupport = this.changeSupport;
+        if (changeSupport != null) {
+            changeSupport.firePropertyChange(DISPOSED_PROPERTY_NAME, false, true);
+        }
+
+        // First, we post an InvocationEvent to be run on the
+        // EventDispatchThread which disposes of all top-level Frames and TrayIcons
+
+        final Object notificationLock = new Object();
+
+        Runnable runnable = new Runnable() {
+            public void run() {
+                Window[] windowsToDispose = Window.getOwnerlessWindows();
+                for (Window w : windowsToDispose) {
+                    try {
+                        w.dispose();
+                    } catch (Throwable t) {
+                        log.finer("exception occurred while disposing app context", t);
+                    }
+                }
+                AccessController.doPrivileged(new PrivilegedAction<Void>() {
+                        public Void run() {
+                            if (!GraphicsEnvironment.isHeadless() && SystemTray.isSupported())
+                            {
+                                SystemTray systemTray = SystemTray.getSystemTray();
+                                TrayIcon[] trayIconsToDispose = systemTray.getTrayIcons();
+                                for (TrayIcon ti : trayIconsToDispose) {
+                                    systemTray.remove(ti);
+                                }
+                            }
+                            return null;
+                        }
+                    });
+                // Alert PropertyChangeListeners that the GUI has been disposed.
+                if (changeSupport != null) {
+                    changeSupport.firePropertyChange(GUI_DISPOSED, false, true);
+                }
+                synchronized(notificationLock) {
+                    notificationLock.notifyAll(); // Notify caller that we're done
+                }
+            }
+        };
+        synchronized(notificationLock) {
+            SunToolkit.postEvent(this,
+                new InvocationEvent(Toolkit.getDefaultToolkit(), runnable));
+            try {
+                notificationLock.wait(DISPOSAL_TIMEOUT);
+            } catch (InterruptedException e) { }
+        }
+
+        // Next, we post another InvocationEvent to the end of the
+        // EventQueue.  When it's executed, we know we've executed all
+        // events in the queue.
+
+        runnable = new Runnable() { public void run() {
+            synchronized(notificationLock) {
+                notificationLock.notifyAll(); // Notify caller that we're done
+            }
+        } };
+        synchronized(notificationLock) {
+            SunToolkit.postEvent(this,
+                new InvocationEvent(Toolkit.getDefaultToolkit(), runnable));
+            try {
+                notificationLock.wait(DISPOSAL_TIMEOUT);
+            } catch (InterruptedException e) { }
+        }
+
+        // We are done with posting events, so change the state to disposed
+        synchronized(this) {
+            this.state = State.DISPOSED;
+        }
+
+        // Next, we interrupt all Threads in the ThreadGroup
+        this.threadGroup.interrupt();
+            // Note, the EventDispatchThread we've interrupted may dump an
+            // InterruptedException to the console here.  This needs to be
+            // fixed in the EventDispatchThread, not here.
+
+        // Next, we sleep 10ms at a time, waiting for all of the active
+        // Threads in the ThreadGroup to exit.
+
+        long startTime = System.currentTimeMillis();
+        long endTime = startTime + THREAD_INTERRUPT_TIMEOUT;
+        while ((this.threadGroup.activeCount() > 0) &&
+               (System.currentTimeMillis() < endTime)) {
+            try {
+                Thread.sleep(10);
+            } catch (InterruptedException e) { }
+        }
+
+        // Then, we stop any remaining Threads
+        this.threadGroup.stop();
+
+        // Next, we sleep 10ms at a time, waiting for all of the active
+        // Threads in the ThreadGroup to die.
+
+        startTime = System.currentTimeMillis();
+        endTime = startTime + THREAD_INTERRUPT_TIMEOUT;
+        while ((this.threadGroup.activeCount() > 0) &&
+               (System.currentTimeMillis() < endTime)) {
+            try {
+                Thread.sleep(10);
+            } catch (InterruptedException e) { }
+        }
+
+        // Next, we remove this and all subThreadGroups from threadGroup2appContext
+        int numSubGroups = this.threadGroup.activeGroupCount();
+        if (numSubGroups > 0) {
+            ThreadGroup [] subGroups = new ThreadGroup[numSubGroups];
+            numSubGroups = this.threadGroup.enumerate(subGroups);
+            for (int subGroup = 0; subGroup < numSubGroups; subGroup++) {
+                threadGroup2appContext.remove(subGroups[subGroup]);
+            }
+        }
+        threadGroup2appContext.remove(this.threadGroup);
+
+        threadAppContext.set(null);
+
+        // Finally, we destroy the ThreadGroup entirely.
+        try {
+            this.threadGroup.destroy();
+        } catch (IllegalThreadStateException e) {
+            // Fired if not all the Threads died, ignore it and proceed
+        }
+
+        synchronized (table) {
+            this.table.clear(); // Clear out the Hashtable to ease garbage collection
+        }
+
+        numAppContexts.decrementAndGet();
+
+        mostRecentKeyValue = null;
+    }
+
+    static final class PostShutdownEventRunnable implements Runnable {
+        private final AppContext appContext;
+
+        public PostShutdownEventRunnable(AppContext ac) {
+            appContext = ac;
+        }
+
+        public void run() {
+            final EventQueue eq = (EventQueue)appContext.get(EVENT_QUEUE_KEY);
+            if (eq != null) {
+                eq.postEvent(AWTAutoShutdown.getShutdownEvent());
+            }
+        }
+    }
+
+    static final class CreateThreadAction implements PrivilegedAction<Thread> {
+        private final AppContext appContext;
+        private final Runnable runnable;
+
+        public CreateThreadAction(AppContext ac, Runnable r) {
+            appContext = ac;
+            runnable = r;
+        }
+
+        public Thread run() {
+            Thread t = new Thread(appContext.getThreadGroup(), runnable);
+            t.setContextClassLoader(appContext.getContextClassLoader());
+            t.setPriority(Thread.NORM_PRIORITY + 1);
+            t.setDaemon(true);
+            return t;
+        }
+    }
+
+    static void stopEventDispatchThreads() {
+        for (AppContext appContext: getAppContexts()) {
+            if (appContext.isDisposed()) {
+                continue;
+            }
+            Runnable r = new PostShutdownEventRunnable(appContext);
+            // For security reasons EventQueue.postEvent should only be called
+            // on a thread that belongs to the corresponding thread group.
+            if (appContext != AppContext.getAppContext()) {
+                // Create a thread that belongs to the thread group associated
+                // with the AppContext and invokes EventQueue.postEvent.
+                PrivilegedAction<Thread> action = new CreateThreadAction(appContext, r);
+                Thread thread = AccessController.doPrivileged(action);
+                thread.start();
+            } else {
+                r.run();
+            }
+        }
+    }
+
+    private MostRecentKeyValue mostRecentKeyValue = null;
+    private MostRecentKeyValue shadowMostRecentKeyValue = null;
+
+    /**
+     * Returns the value to which the specified key is mapped in this context.
+     *
+     * @param   key   a key in the AppContext.
+     * @return  the value to which the key is mapped in this AppContext;
+     *          <code>null</code> if the key is not mapped to any value.
+     * @see     #put(Object, Object)
+     * @since   1.2
+     */
+    public Object get(Object key) {
+        /*
+         * The most recent reference should be updated inside a synchronized
+         * block to avoid a race when put() and get() are executed in
+         * parallel on different threads.
+         */
+        synchronized (table) {
+            // Note: this most recent key/value caching is thread-hot.
+            // A simple test using SwingSet found that 72% of lookups
+            // were matched using the most recent key/value.  By instantiating
+            // a simple MostRecentKeyValue object on cache misses, the
+            // cache hits can be processed without synchronization.
+
+            MostRecentKeyValue recent = mostRecentKeyValue;
+            if ((recent != null) && (recent.key == key)) {
+                return recent.value;
+            }
+
+            Object value = table.get(key);
+            if(mostRecentKeyValue == null) {
+                mostRecentKeyValue = new MostRecentKeyValue(key, value);
+                shadowMostRecentKeyValue = new MostRecentKeyValue(key, value);
+            } else {
+                MostRecentKeyValue auxKeyValue = mostRecentKeyValue;
+                shadowMostRecentKeyValue.setPair(key, value);
+                mostRecentKeyValue = shadowMostRecentKeyValue;
+                shadowMostRecentKeyValue = auxKeyValue;
+            }
+            return value;
+        }
+    }
+
+    /**
+     * Maps the specified <code>key</code> to the specified
+     * <code>value</code> in this AppContext.  Neither the key nor the
+     * value can be <code>null</code>.
+     * <p>
+     * The value can be retrieved by calling the <code>get</code> method
+     * with a key that is equal to the original key.
+     *
+     * @param      key     the AppContext key.
+     * @param      value   the value.
+     * @return     the previous value of the specified key in this
+     *             AppContext, or <code>null</code> if it did not have one.
+     * @exception  NullPointerException  if the key or value is
+     *               <code>null</code>.
+     * @see     #get(Object)
+     * @since   1.2
+     */
+    public Object put(Object key, Object value) {
+        synchronized (table) {
+            MostRecentKeyValue recent = mostRecentKeyValue;
+            if ((recent != null) && (recent.key == key))
+                recent.value = value;
+            return table.put(key, value);
+        }
+    }
+
+    /**
+     * Removes the key (and its corresponding value) from this
+     * AppContext. This method does nothing if the key is not in the
+     * AppContext.
+     *
+     * @param   key   the key that needs to be removed.
+     * @return  the value to which the key had been mapped in this AppContext,
+     *          or <code>null</code> if the key did not have a mapping.
+     * @since   1.2
+     */
+    public Object remove(Object key) {
+        synchronized (table) {
+            MostRecentKeyValue recent = mostRecentKeyValue;
+            if ((recent != null) && (recent.key == key))
+                recent.value = null;
+            return table.remove(key);
+        }
+    }
+
+    /**
+     * Returns the root ThreadGroup for all Threads contained within
+     * this AppContext.
+     * @since   1.2
+     */
+    public ThreadGroup getThreadGroup() {
+        return threadGroup;
+    }
+
+    /**
+     * Returns the context ClassLoader that was used to create this
+     * AppContext.
+     *
+     * @see java.lang.Thread#getContextClassLoader
+     */
+    public ClassLoader getContextClassLoader() {
+        return contextClassLoader;
+    }
+
+    /**
+     * Returns a string representation of this AppContext.
+     * @since   1.2
+     */
+    @Override
+    public String toString() {
+        return getClass().getName() + "[threadGroup=" + threadGroup.getName() + "]";
+    }
+
+    /**
+     * Returns an array of all the property change listeners
+     * registered on this component.
+     *
+     * @return all of this component's <code>PropertyChangeListener</code>s
+     *         or an empty array if no property change
+     *         listeners are currently registered
+     *
+     * @see      #addPropertyChangeListener
+     * @see      #removePropertyChangeListener
+     * @see      #getPropertyChangeListeners(java.lang.String)
+     * @see      java.beans.PropertyChangeSupport#getPropertyChangeListeners
+     * @since    1.4
+     */
+    public synchronized PropertyChangeListener[] getPropertyChangeListeners() {
+        if (changeSupport == null) {
+            return new PropertyChangeListener[0];
+        }
+        return changeSupport.getPropertyChangeListeners();
+    }
+
+    /**
+     * Adds a PropertyChangeListener to the listener list for a specific
+     * property. The specified property may be one of the following:
+     * <ul>
+     *    <li>if this AppContext is disposed ("disposed")</li>
+     * </ul>
+     * <ul>
+     *    <li>if this AppContext's unowned Windows have been disposed
+     *    ("guidisposed").  Code to cleanup after the GUI is disposed
+     *    (such as LookAndFeel.uninitialize()) should execute in response to
+     *    this property being fired.  Notifications for the "guidisposed"
+     *    property are sent on the event dispatch thread.</li>
+     * </ul>
+     * <p>
+     * If listener is null, no exception is thrown and no action is performed.
+     *
+     * @param propertyName one of the property names listed above
+     * @param listener the PropertyChangeListener to be added
+     *
+     * @see #removePropertyChangeListener(java.lang.String, java.beans.PropertyChangeListener)
+     * @see #getPropertyChangeListeners(java.lang.String)
+     * @see #addPropertyChangeListener(java.lang.String, java.beans.PropertyChangeListener)
+     */
+    public synchronized void addPropertyChangeListener(
+                             String propertyName,
+                             PropertyChangeListener listener) {
+        if (listener == null) {
+            return;
+        }
+        if (changeSupport == null) {
+            changeSupport = new PropertyChangeSupport(this);
+        }
+        changeSupport.addPropertyChangeListener(propertyName, listener);
+    }
+
+    /**
+     * Removes a PropertyChangeListener from the listener list for a specific
+     * property. This method should be used to remove PropertyChangeListeners
+     * that were registered for a specific bound property.
+     * <p>
+     * If listener is null, no exception is thrown and no action is performed.
+     *
+     * @param propertyName a valid property name
+     * @param listener the PropertyChangeListener to be removed
+     *
+     * @see #addPropertyChangeListener(java.lang.String, java.beans.PropertyChangeListener)
+     * @see #getPropertyChangeListeners(java.lang.String)
+     * @see #removePropertyChangeListener(java.beans.PropertyChangeListener)
+     */
+    public synchronized void removePropertyChangeListener(
+                             String propertyName,
+                             PropertyChangeListener listener) {
+        if (listener == null || changeSupport == null) {
+            return;
+        }
+        changeSupport.removePropertyChangeListener(propertyName, listener);
+    }
+
+    /**
+     * Returns an array of all the listeners which have been associated
+     * with the named property.
+     *
+     * @return all of the <code>PropertyChangeListeners</code> associated with
+     *         the named property or an empty array if no listeners have
+     *         been added
+     *
+     * @see #addPropertyChangeListener(java.lang.String, java.beans.PropertyChangeListener)
+     * @see #removePropertyChangeListener(java.lang.String, java.beans.PropertyChangeListener)
+     * @see #getPropertyChangeListeners
+     * @since 1.4
+     */
+    public synchronized PropertyChangeListener[] getPropertyChangeListeners(
+                                                        String propertyName) {
+        if (changeSupport == null) {
+            return new PropertyChangeListener[0];
+        }
+        return changeSupport.getPropertyChangeListeners(propertyName);
+    }
+
+    // Set up JavaAWTAccess in SharedSecrets
+    static {
+        sun.misc.SharedSecrets.setJavaAWTAccess(new sun.misc.JavaAWTAccess() {
+            private boolean hasRootThreadGroup(final AppContext ecx) {
+                return AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
+                    @Override
+                    public Boolean run() {
+                        return ecx.threadGroup.getParent() == null;
+                    }
+                });
+            }
+
+            /**
+             * Returns the AppContext used for applet logging isolation, or null if
+             * the default global context can be used.
+             * If there's no applet, or if the caller is a stand alone application,
+             * or running in the main app context, returns null.
+             * Otherwise, returns the AppContext of the calling applet.
+             * @return null if the global default context can be used,
+             *         an AppContext otherwise.
+             **/
+            public Object getAppletContext() {
+                // There's no AppContext: return null.
+                // No need to call getAppContext() if numAppContext == 0:
+                // it means that no AppContext has been created yet, and
+                // we don't want to trigger the creation of a main app
+                // context since we don't need it.
+                if (numAppContexts.get() == 0) return null;
+
+                // Get the context from the security manager
+                AppContext ecx = getExecutionAppContext();
+
+                // Not sure we really need to re-check numAppContexts here.
+                // If all applets have gone away then we could have a
+                // numAppContexts coming back to 0. So we recheck
+                // it here because we don't want to trigger the
+                // creation of a main AppContext in that case.
+                // This is probably not 100% MT-safe but should reduce
+                // the window of opportunity in which that issue could
+                // happen.
+                if (numAppContexts.get() > 0) {
+                   // Defaults to thread group caching.
+                   // This is probably not required as we only really need
+                   // isolation in a deployed applet environment, in which
+                   // case ecx will not be null when we reach here
+                   // However it helps emulate the deployed environment,
+                   // in tests for instance.
+                   ecx = ecx != null ? ecx : getAppContext();
+                }
+
+                // getAppletContext() may be called when initializing the main
+                // app context - in which case mainAppContext will still be
+                // null. To work around this issue we simply use
+                // AppContext.threadGroup.getParent() == null instead, since
+                // mainAppContext is the only AppContext which should have
+                // the root TG as its thread group.
+                // See: JDK-8023258
+                final boolean isMainAppContext = ecx == null
+                    || mainAppContext == ecx
+                    || mainAppContext == null && hasRootThreadGroup(ecx);
+
+                return isMainAppContext ? null : ecx;
+            }
+
+        });
+    }
+
+    public static <T> T getSoftReferenceValue(Object key,
+            Supplier<T> supplier) {
+
+        final AppContext appContext = AppContext.getAppContext();
+        @SuppressWarnings("unchecked")
+        SoftReference<T> ref = (SoftReference<T>) appContext.get(key);
+        if (ref != null) {
+            final T object = ref.get();
+            if (object != null) {
+                return object;
+            }
+        }
+        final T object = supplier.get();
+        ref = new SoftReference<>(object);
+        appContext.put(key, ref);
+        return object;
+    }
+}
+
+final class MostRecentKeyValue {
+    Object key;
+    Object value;
+    MostRecentKeyValue(Object k, Object v) {
+        key = k;
+        value = v;
+    }
+    void setPair(Object k, Object v) {
+        key = k;
+        value = v;
+    }
+}