jdk/src/java.desktop/share/classes/sun/awt/AppContext.java
changeset 25859 3317bb8137f4
parent 25529 a6c103245f63
child 26037 508779ce6619
equal deleted inserted replaced
25858:836adbf7a2cd 25859:3317bb8137f4
       
     1 /*
       
     2  * Copyright (c) 1998, 2013, Oracle and/or its affiliates. All rights reserved.
       
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
       
     4  *
       
     5  * This code is free software; you can redistribute it and/or modify it
       
     6  * under the terms of the GNU General Public License version 2 only, as
       
     7  * published by the Free Software Foundation.  Oracle designates this
       
     8  * particular file as subject to the "Classpath" exception as provided
       
     9  * by Oracle in the LICENSE file that accompanied this code.
       
    10  *
       
    11  * This code is distributed in the hope that it will be useful, but WITHOUT
       
    12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
       
    13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
       
    14  * version 2 for more details (a copy is included in the LICENSE file that
       
    15  * accompanied this code).
       
    16  *
       
    17  * You should have received a copy of the GNU General Public License version
       
    18  * 2 along with this work; if not, write to the Free Software Foundation,
       
    19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
       
    20  *
       
    21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
       
    22  * or visit www.oracle.com if you need additional information or have any
       
    23  * questions.
       
    24  */
       
    25 
       
    26 package sun.awt;
       
    27 
       
    28 import java.awt.EventQueue;
       
    29 import java.awt.Window;
       
    30 import java.awt.SystemTray;
       
    31 import java.awt.TrayIcon;
       
    32 import java.awt.Toolkit;
       
    33 import java.awt.GraphicsEnvironment;
       
    34 import java.awt.event.InvocationEvent;
       
    35 import java.security.AccessController;
       
    36 import java.security.PrivilegedAction;
       
    37 import java.util.Collections;
       
    38 import java.util.HashMap;
       
    39 import java.util.IdentityHashMap;
       
    40 import java.util.Map;
       
    41 import java.util.Set;
       
    42 import java.util.HashSet;
       
    43 import java.beans.PropertyChangeSupport;
       
    44 import java.beans.PropertyChangeListener;
       
    45 import java.lang.ref.SoftReference;
       
    46 import sun.util.logging.PlatformLogger;
       
    47 import java.util.concurrent.locks.Condition;
       
    48 import java.util.concurrent.locks.Lock;
       
    49 import java.util.concurrent.locks.ReentrantLock;
       
    50 import java.util.concurrent.atomic.AtomicInteger;
       
    51 import java.util.function.Supplier;
       
    52 
       
    53 /**
       
    54  * The AppContext is a table referenced by ThreadGroup which stores
       
    55  * application service instances.  (If you are not writing an application
       
    56  * service, or don't know what one is, please do not use this class.)
       
    57  * The AppContext allows applet access to what would otherwise be
       
    58  * potentially dangerous services, such as the ability to peek at
       
    59  * EventQueues or change the look-and-feel of a Swing application.<p>
       
    60  *
       
    61  * Most application services use a singleton object to provide their
       
    62  * services, either as a default (such as getSystemEventQueue or
       
    63  * getDefaultToolkit) or as static methods with class data (System).
       
    64  * The AppContext works with the former method by extending the concept
       
    65  * of "default" to be ThreadGroup-specific.  Application services
       
    66  * lookup their singleton in the AppContext.<p>
       
    67  *
       
    68  * For example, here we have a Foo service, with its pre-AppContext
       
    69  * code:<p>
       
    70  * <code><pre>
       
    71  *    public class Foo {
       
    72  *        private static Foo defaultFoo = new Foo();
       
    73  *
       
    74  *        public static Foo getDefaultFoo() {
       
    75  *            return defaultFoo;
       
    76  *        }
       
    77  *
       
    78  *    ... Foo service methods
       
    79  *    }</pre></code><p>
       
    80  *
       
    81  * The problem with the above is that the Foo service is global in scope,
       
    82  * so that applets and other untrusted code can execute methods on the
       
    83  * single, shared Foo instance.  The Foo service therefore either needs
       
    84  * to block its use by untrusted code using a SecurityManager test, or
       
    85  * restrict its capabilities so that it doesn't matter if untrusted code
       
    86  * executes it.<p>
       
    87  *
       
    88  * Here's the Foo class written to use the AppContext:<p>
       
    89  * <code><pre>
       
    90  *    public class Foo {
       
    91  *        public static Foo getDefaultFoo() {
       
    92  *            Foo foo = (Foo)AppContext.getAppContext().get(Foo.class);
       
    93  *            if (foo == null) {
       
    94  *                foo = new Foo();
       
    95  *                getAppContext().put(Foo.class, foo);
       
    96  *            }
       
    97  *            return foo;
       
    98  *        }
       
    99  *
       
   100  *    ... Foo service methods
       
   101  *    }</pre></code><p>
       
   102  *
       
   103  * Since a separate AppContext can exist for each ThreadGroup, trusted
       
   104  * and untrusted code have access to different Foo instances.  This allows
       
   105  * untrusted code access to "system-wide" services -- the service remains
       
   106  * within the AppContext "sandbox".  For example, say a malicious applet
       
   107  * wants to peek all of the key events on the EventQueue to listen for
       
   108  * passwords; if separate EventQueues are used for each ThreadGroup
       
   109  * using AppContexts, the only key events that applet will be able to
       
   110  * listen to are its own.  A more reasonable applet request would be to
       
   111  * change the Swing default look-and-feel; with that default stored in
       
   112  * an AppContext, the applet's look-and-feel will change without
       
   113  * disrupting other applets or potentially the browser itself.<p>
       
   114  *
       
   115  * Because the AppContext is a facility for safely extending application
       
   116  * service support to applets, none of its methods may be blocked by a
       
   117  * a SecurityManager check in a valid Java implementation.  Applets may
       
   118  * therefore safely invoke any of its methods without worry of being
       
   119  * blocked.
       
   120  *
       
   121  * Note: If a SecurityManager is installed which derives from
       
   122  * sun.awt.AWTSecurityManager, it may override the
       
   123  * AWTSecurityManager.getAppContext() method to return the proper
       
   124  * AppContext based on the execution context, in the case where
       
   125  * the default ThreadGroup-based AppContext indexing would return
       
   126  * the main "system" AppContext.  For example, in an applet situation,
       
   127  * if a system thread calls into an applet, rather than returning the
       
   128  * main "system" AppContext (the one corresponding to the system thread),
       
   129  * an installed AWTSecurityManager may return the applet's AppContext
       
   130  * based on the execution context.
       
   131  *
       
   132  * @author  Thomas Ball
       
   133  * @author  Fred Ecks
       
   134  */
       
   135 public final class AppContext {
       
   136     private static final PlatformLogger log = PlatformLogger.getLogger("sun.awt.AppContext");
       
   137 
       
   138     /* Since the contents of an AppContext are unique to each Java
       
   139      * session, this class should never be serialized. */
       
   140 
       
   141     /*
       
   142      * The key to put()/get() the Java EventQueue into/from the AppContext.
       
   143      */
       
   144     public static final Object EVENT_QUEUE_KEY = new StringBuffer("EventQueue");
       
   145 
       
   146     /*
       
   147      * The keys to store EventQueue push/pop lock and condition.
       
   148      */
       
   149     public final static Object EVENT_QUEUE_LOCK_KEY = new StringBuilder("EventQueue.Lock");
       
   150     public final static Object EVENT_QUEUE_COND_KEY = new StringBuilder("EventQueue.Condition");
       
   151 
       
   152     /* A map of AppContexts, referenced by ThreadGroup.
       
   153      */
       
   154     private static final Map<ThreadGroup, AppContext> threadGroup2appContext =
       
   155             Collections.synchronizedMap(new IdentityHashMap<ThreadGroup, AppContext>());
       
   156 
       
   157     /**
       
   158      * Returns a set containing all <code>AppContext</code>s.
       
   159      */
       
   160     public static Set<AppContext> getAppContexts() {
       
   161         synchronized (threadGroup2appContext) {
       
   162             return new HashSet<AppContext>(threadGroup2appContext.values());
       
   163         }
       
   164     }
       
   165 
       
   166     /* The main "system" AppContext, used by everything not otherwise
       
   167        contained in another AppContext. It is implicitly created for
       
   168        standalone apps only (i.e. not applets)
       
   169      */
       
   170     private static volatile AppContext mainAppContext = null;
       
   171 
       
   172     private static class GetAppContextLock {};
       
   173     private final static Object getAppContextLock = new GetAppContextLock();
       
   174 
       
   175     /*
       
   176      * The hash map associated with this AppContext.  A private delegate
       
   177      * is used instead of subclassing HashMap so as to avoid all of
       
   178      * HashMap's potentially risky methods, such as clear(), elements(),
       
   179      * putAll(), etc.
       
   180      */
       
   181     private final Map<Object, Object> table = new HashMap<>();
       
   182 
       
   183     private final ThreadGroup threadGroup;
       
   184 
       
   185     /**
       
   186      * If any <code>PropertyChangeListeners</code> have been registered,
       
   187      * the <code>changeSupport</code> field describes them.
       
   188      *
       
   189      * @see #addPropertyChangeListener
       
   190      * @see #removePropertyChangeListener
       
   191      * @see #firePropertyChange
       
   192      */
       
   193     private PropertyChangeSupport changeSupport = null;
       
   194 
       
   195     public static final String DISPOSED_PROPERTY_NAME = "disposed";
       
   196     public static final String GUI_DISPOSED = "guidisposed";
       
   197 
       
   198     private enum State {
       
   199         VALID,
       
   200         BEING_DISPOSED,
       
   201         DISPOSED
       
   202     };
       
   203 
       
   204     private volatile State state = State.VALID;
       
   205 
       
   206     public boolean isDisposed() {
       
   207         return state == State.DISPOSED;
       
   208     }
       
   209 
       
   210     /*
       
   211      * The total number of AppContexts, system-wide.  This number is
       
   212      * incremented at the beginning of the constructor, and decremented
       
   213      * at the end of dispose().  getAppContext() checks to see if this
       
   214      * number is 1.  If so, it returns the sole AppContext without
       
   215      * checking Thread.currentThread().
       
   216      */
       
   217     private static final AtomicInteger numAppContexts = new AtomicInteger(0);
       
   218 
       
   219 
       
   220     /*
       
   221      * The context ClassLoader that was used to create this AppContext.
       
   222      */
       
   223     private final ClassLoader contextClassLoader;
       
   224 
       
   225     /**
       
   226      * Constructor for AppContext.  This method is <i>not</i> public,
       
   227      * nor should it ever be used as such.  The proper way to construct
       
   228      * an AppContext is through the use of SunToolkit.createNewAppContext.
       
   229      * A ThreadGroup is created for the new AppContext, a Thread is
       
   230      * created within that ThreadGroup, and that Thread calls
       
   231      * SunToolkit.createNewAppContext before calling anything else.
       
   232      * That creates both the new AppContext and its EventQueue.
       
   233      *
       
   234      * @param   threadGroup     The ThreadGroup for the new AppContext
       
   235      * @see     sun.awt.SunToolkit
       
   236      * @since   1.2
       
   237      */
       
   238     AppContext(ThreadGroup threadGroup) {
       
   239         numAppContexts.incrementAndGet();
       
   240 
       
   241         this.threadGroup = threadGroup;
       
   242         threadGroup2appContext.put(threadGroup, this);
       
   243 
       
   244         this.contextClassLoader =
       
   245              AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
       
   246                     public ClassLoader run() {
       
   247                         return Thread.currentThread().getContextClassLoader();
       
   248                     }
       
   249                 });
       
   250 
       
   251         // Initialize push/pop lock and its condition to be used by all the
       
   252         // EventQueues within this AppContext
       
   253         Lock eventQueuePushPopLock = new ReentrantLock();
       
   254         put(EVENT_QUEUE_LOCK_KEY, eventQueuePushPopLock);
       
   255         Condition eventQueuePushPopCond = eventQueuePushPopLock.newCondition();
       
   256         put(EVENT_QUEUE_COND_KEY, eventQueuePushPopCond);
       
   257     }
       
   258 
       
   259     private static final ThreadLocal<AppContext> threadAppContext =
       
   260             new ThreadLocal<AppContext>();
       
   261 
       
   262     private final static void initMainAppContext() {
       
   263         // On the main Thread, we get the ThreadGroup, make a corresponding
       
   264         // AppContext, and instantiate the Java EventQueue.  This way, legacy
       
   265         // code is unaffected by the move to multiple AppContext ability.
       
   266         AccessController.doPrivileged(new PrivilegedAction<Void>() {
       
   267             public Void run() {
       
   268                 ThreadGroup currentThreadGroup =
       
   269                         Thread.currentThread().getThreadGroup();
       
   270                 ThreadGroup parentThreadGroup = currentThreadGroup.getParent();
       
   271                 while (parentThreadGroup != null) {
       
   272                     // Find the root ThreadGroup to construct our main AppContext
       
   273                     currentThreadGroup = parentThreadGroup;
       
   274                     parentThreadGroup = currentThreadGroup.getParent();
       
   275                 }
       
   276 
       
   277                 mainAppContext = SunToolkit.createNewAppContext(currentThreadGroup);
       
   278                 return null;
       
   279             }
       
   280         });
       
   281     }
       
   282 
       
   283     /**
       
   284      * Returns the appropriate AppContext for the caller,
       
   285      * as determined by its ThreadGroup.  If the main "system" AppContext
       
   286      * would be returned and there's an AWTSecurityManager installed, it
       
   287      * is called to get the proper AppContext based on the execution
       
   288      * context.
       
   289      *
       
   290      * @return  the AppContext for the caller.
       
   291      * @see     java.lang.ThreadGroup
       
   292      * @since   1.2
       
   293      */
       
   294     public final static AppContext getAppContext() {
       
   295         // we are standalone app, return the main app context
       
   296         if (numAppContexts.get() == 1 && mainAppContext != null) {
       
   297             return mainAppContext;
       
   298         }
       
   299 
       
   300         AppContext appContext = threadAppContext.get();
       
   301 
       
   302         if (null == appContext) {
       
   303             appContext = AccessController.doPrivileged(new PrivilegedAction<AppContext>()
       
   304             {
       
   305                 public AppContext run() {
       
   306                     // Get the current ThreadGroup, and look for it and its
       
   307                     // parents in the hash from ThreadGroup to AppContext --
       
   308                     // it should be found, because we use createNewContext()
       
   309                     // when new AppContext objects are created.
       
   310                     ThreadGroup currentThreadGroup = Thread.currentThread().getThreadGroup();
       
   311                     ThreadGroup threadGroup = currentThreadGroup;
       
   312 
       
   313                     // Special case: we implicitly create the main app context
       
   314                     // if no contexts have been created yet. This covers standalone apps
       
   315                     // and excludes applets because by the time applet starts
       
   316                     // a number of contexts have already been created by the plugin.
       
   317                     synchronized (getAppContextLock) {
       
   318                         if (numAppContexts.get() == 0) {
       
   319                             if (System.getProperty("javaplugin.version") == null &&
       
   320                                     System.getProperty("javawebstart.version") == null) {
       
   321                                 initMainAppContext();
       
   322                             } else if (System.getProperty("javafx.version") != null &&
       
   323                                     threadGroup.getParent() != null) {
       
   324                                 // Swing inside JavaFX case
       
   325                                 SunToolkit.createNewAppContext();
       
   326                             }
       
   327                         }
       
   328                     }
       
   329 
       
   330                     AppContext context = threadGroup2appContext.get(threadGroup);
       
   331                     while (context == null) {
       
   332                         threadGroup = threadGroup.getParent();
       
   333                         if (threadGroup == null) {
       
   334                             // We've got up to the root thread group and did not find an AppContext
       
   335                             // Try to get it from the security manager
       
   336                             SecurityManager securityManager = System.getSecurityManager();
       
   337                             if (securityManager != null) {
       
   338                                 ThreadGroup smThreadGroup = securityManager.getThreadGroup();
       
   339                                 if (smThreadGroup != null) {
       
   340                                     /*
       
   341                                      * If we get this far then it's likely that
       
   342                                      * the ThreadGroup does not actually belong
       
   343                                      * to the applet, so do not cache it.
       
   344                                      */
       
   345                                     return threadGroup2appContext.get(smThreadGroup);
       
   346                                 }
       
   347                             }
       
   348                             return null;
       
   349                         }
       
   350                         context = threadGroup2appContext.get(threadGroup);
       
   351                     }
       
   352 
       
   353                     // In case we did anything in the above while loop, we add
       
   354                     // all the intermediate ThreadGroups to threadGroup2appContext
       
   355                     // so we won't spin again.
       
   356                     for (ThreadGroup tg = currentThreadGroup; tg != threadGroup; tg = tg.getParent()) {
       
   357                         threadGroup2appContext.put(tg, context);
       
   358                     }
       
   359 
       
   360                     // Now we're done, so we cache the latest key/value pair.
       
   361                     threadAppContext.set(context);
       
   362 
       
   363                     return context;
       
   364                 }
       
   365             });
       
   366         }
       
   367 
       
   368         return appContext;
       
   369     }
       
   370 
       
   371     /**
       
   372      * Returns true if the specified AppContext is the main AppContext.
       
   373      *
       
   374      * @param   ctx the context to compare with the main context
       
   375      * @return  true if the specified AppContext is the main AppContext.
       
   376      * @since   1.8
       
   377      */
       
   378     public final static boolean isMainContext(AppContext ctx) {
       
   379         return (ctx != null && ctx == mainAppContext);
       
   380     }
       
   381 
       
   382     private final static AppContext getExecutionAppContext() {
       
   383         SecurityManager securityManager = System.getSecurityManager();
       
   384         if ((securityManager != null) &&
       
   385             (securityManager instanceof AWTSecurityManager))
       
   386         {
       
   387             AWTSecurityManager awtSecMgr = (AWTSecurityManager) securityManager;
       
   388             AppContext secAppContext = awtSecMgr.getAppContext();
       
   389             return secAppContext; // Return what we're told
       
   390         }
       
   391         return null;
       
   392     }
       
   393 
       
   394     private long DISPOSAL_TIMEOUT = 5000;  // Default to 5-second timeout
       
   395                                            // for disposal of all Frames
       
   396                                            // (we wait for this time twice,
       
   397                                            // once for dispose(), and once
       
   398                                            // to clear the EventQueue).
       
   399 
       
   400     private long THREAD_INTERRUPT_TIMEOUT = 1000;
       
   401                             // Default to 1-second timeout for all
       
   402                             // interrupted Threads to exit, and another
       
   403                             // 1 second for all stopped Threads to die.
       
   404 
       
   405     /**
       
   406      * Disposes of this AppContext, all of its top-level Frames, and
       
   407      * all Threads and ThreadGroups contained within it.
       
   408      *
       
   409      * This method must be called from a Thread which is not contained
       
   410      * within this AppContext.
       
   411      *
       
   412      * @exception  IllegalThreadStateException  if the current thread is
       
   413      *                                    contained within this AppContext
       
   414      * @since      1.2
       
   415      */
       
   416     public void dispose() throws IllegalThreadStateException {
       
   417         // Check to be sure that the current Thread isn't in this AppContext
       
   418         if (this.threadGroup.parentOf(Thread.currentThread().getThreadGroup())) {
       
   419             throw new IllegalThreadStateException(
       
   420                 "Current Thread is contained within AppContext to be disposed."
       
   421               );
       
   422         }
       
   423 
       
   424         synchronized(this) {
       
   425             if (this.state != State.VALID) {
       
   426                 return; // If already disposed or being disposed, bail.
       
   427             }
       
   428 
       
   429             this.state = State.BEING_DISPOSED;
       
   430         }
       
   431 
       
   432         final PropertyChangeSupport changeSupport = this.changeSupport;
       
   433         if (changeSupport != null) {
       
   434             changeSupport.firePropertyChange(DISPOSED_PROPERTY_NAME, false, true);
       
   435         }
       
   436 
       
   437         // First, we post an InvocationEvent to be run on the
       
   438         // EventDispatchThread which disposes of all top-level Frames and TrayIcons
       
   439 
       
   440         final Object notificationLock = new Object();
       
   441 
       
   442         Runnable runnable = new Runnable() {
       
   443             public void run() {
       
   444                 Window[] windowsToDispose = Window.getOwnerlessWindows();
       
   445                 for (Window w : windowsToDispose) {
       
   446                     try {
       
   447                         w.dispose();
       
   448                     } catch (Throwable t) {
       
   449                         log.finer("exception occurred while disposing app context", t);
       
   450                     }
       
   451                 }
       
   452                 AccessController.doPrivileged(new PrivilegedAction<Void>() {
       
   453                         public Void run() {
       
   454                             if (!GraphicsEnvironment.isHeadless() && SystemTray.isSupported())
       
   455                             {
       
   456                                 SystemTray systemTray = SystemTray.getSystemTray();
       
   457                                 TrayIcon[] trayIconsToDispose = systemTray.getTrayIcons();
       
   458                                 for (TrayIcon ti : trayIconsToDispose) {
       
   459                                     systemTray.remove(ti);
       
   460                                 }
       
   461                             }
       
   462                             return null;
       
   463                         }
       
   464                     });
       
   465                 // Alert PropertyChangeListeners that the GUI has been disposed.
       
   466                 if (changeSupport != null) {
       
   467                     changeSupport.firePropertyChange(GUI_DISPOSED, false, true);
       
   468                 }
       
   469                 synchronized(notificationLock) {
       
   470                     notificationLock.notifyAll(); // Notify caller that we're done
       
   471                 }
       
   472             }
       
   473         };
       
   474         synchronized(notificationLock) {
       
   475             SunToolkit.postEvent(this,
       
   476                 new InvocationEvent(Toolkit.getDefaultToolkit(), runnable));
       
   477             try {
       
   478                 notificationLock.wait(DISPOSAL_TIMEOUT);
       
   479             } catch (InterruptedException e) { }
       
   480         }
       
   481 
       
   482         // Next, we post another InvocationEvent to the end of the
       
   483         // EventQueue.  When it's executed, we know we've executed all
       
   484         // events in the queue.
       
   485 
       
   486         runnable = new Runnable() { public void run() {
       
   487             synchronized(notificationLock) {
       
   488                 notificationLock.notifyAll(); // Notify caller that we're done
       
   489             }
       
   490         } };
       
   491         synchronized(notificationLock) {
       
   492             SunToolkit.postEvent(this,
       
   493                 new InvocationEvent(Toolkit.getDefaultToolkit(), runnable));
       
   494             try {
       
   495                 notificationLock.wait(DISPOSAL_TIMEOUT);
       
   496             } catch (InterruptedException e) { }
       
   497         }
       
   498 
       
   499         // We are done with posting events, so change the state to disposed
       
   500         synchronized(this) {
       
   501             this.state = State.DISPOSED;
       
   502         }
       
   503 
       
   504         // Next, we interrupt all Threads in the ThreadGroup
       
   505         this.threadGroup.interrupt();
       
   506             // Note, the EventDispatchThread we've interrupted may dump an
       
   507             // InterruptedException to the console here.  This needs to be
       
   508             // fixed in the EventDispatchThread, not here.
       
   509 
       
   510         // Next, we sleep 10ms at a time, waiting for all of the active
       
   511         // Threads in the ThreadGroup to exit.
       
   512 
       
   513         long startTime = System.currentTimeMillis();
       
   514         long endTime = startTime + THREAD_INTERRUPT_TIMEOUT;
       
   515         while ((this.threadGroup.activeCount() > 0) &&
       
   516                (System.currentTimeMillis() < endTime)) {
       
   517             try {
       
   518                 Thread.sleep(10);
       
   519             } catch (InterruptedException e) { }
       
   520         }
       
   521 
       
   522         // Then, we stop any remaining Threads
       
   523         this.threadGroup.stop();
       
   524 
       
   525         // Next, we sleep 10ms at a time, waiting for all of the active
       
   526         // Threads in the ThreadGroup to die.
       
   527 
       
   528         startTime = System.currentTimeMillis();
       
   529         endTime = startTime + THREAD_INTERRUPT_TIMEOUT;
       
   530         while ((this.threadGroup.activeCount() > 0) &&
       
   531                (System.currentTimeMillis() < endTime)) {
       
   532             try {
       
   533                 Thread.sleep(10);
       
   534             } catch (InterruptedException e) { }
       
   535         }
       
   536 
       
   537         // Next, we remove this and all subThreadGroups from threadGroup2appContext
       
   538         int numSubGroups = this.threadGroup.activeGroupCount();
       
   539         if (numSubGroups > 0) {
       
   540             ThreadGroup [] subGroups = new ThreadGroup[numSubGroups];
       
   541             numSubGroups = this.threadGroup.enumerate(subGroups);
       
   542             for (int subGroup = 0; subGroup < numSubGroups; subGroup++) {
       
   543                 threadGroup2appContext.remove(subGroups[subGroup]);
       
   544             }
       
   545         }
       
   546         threadGroup2appContext.remove(this.threadGroup);
       
   547 
       
   548         threadAppContext.set(null);
       
   549 
       
   550         // Finally, we destroy the ThreadGroup entirely.
       
   551         try {
       
   552             this.threadGroup.destroy();
       
   553         } catch (IllegalThreadStateException e) {
       
   554             // Fired if not all the Threads died, ignore it and proceed
       
   555         }
       
   556 
       
   557         synchronized (table) {
       
   558             this.table.clear(); // Clear out the Hashtable to ease garbage collection
       
   559         }
       
   560 
       
   561         numAppContexts.decrementAndGet();
       
   562 
       
   563         mostRecentKeyValue = null;
       
   564     }
       
   565 
       
   566     static final class PostShutdownEventRunnable implements Runnable {
       
   567         private final AppContext appContext;
       
   568 
       
   569         public PostShutdownEventRunnable(AppContext ac) {
       
   570             appContext = ac;
       
   571         }
       
   572 
       
   573         public void run() {
       
   574             final EventQueue eq = (EventQueue)appContext.get(EVENT_QUEUE_KEY);
       
   575             if (eq != null) {
       
   576                 eq.postEvent(AWTAutoShutdown.getShutdownEvent());
       
   577             }
       
   578         }
       
   579     }
       
   580 
       
   581     static final class CreateThreadAction implements PrivilegedAction<Thread> {
       
   582         private final AppContext appContext;
       
   583         private final Runnable runnable;
       
   584 
       
   585         public CreateThreadAction(AppContext ac, Runnable r) {
       
   586             appContext = ac;
       
   587             runnable = r;
       
   588         }
       
   589 
       
   590         public Thread run() {
       
   591             Thread t = new Thread(appContext.getThreadGroup(), runnable);
       
   592             t.setContextClassLoader(appContext.getContextClassLoader());
       
   593             t.setPriority(Thread.NORM_PRIORITY + 1);
       
   594             t.setDaemon(true);
       
   595             return t;
       
   596         }
       
   597     }
       
   598 
       
   599     static void stopEventDispatchThreads() {
       
   600         for (AppContext appContext: getAppContexts()) {
       
   601             if (appContext.isDisposed()) {
       
   602                 continue;
       
   603             }
       
   604             Runnable r = new PostShutdownEventRunnable(appContext);
       
   605             // For security reasons EventQueue.postEvent should only be called
       
   606             // on a thread that belongs to the corresponding thread group.
       
   607             if (appContext != AppContext.getAppContext()) {
       
   608                 // Create a thread that belongs to the thread group associated
       
   609                 // with the AppContext and invokes EventQueue.postEvent.
       
   610                 PrivilegedAction<Thread> action = new CreateThreadAction(appContext, r);
       
   611                 Thread thread = AccessController.doPrivileged(action);
       
   612                 thread.start();
       
   613             } else {
       
   614                 r.run();
       
   615             }
       
   616         }
       
   617     }
       
   618 
       
   619     private MostRecentKeyValue mostRecentKeyValue = null;
       
   620     private MostRecentKeyValue shadowMostRecentKeyValue = null;
       
   621 
       
   622     /**
       
   623      * Returns the value to which the specified key is mapped in this context.
       
   624      *
       
   625      * @param   key   a key in the AppContext.
       
   626      * @return  the value to which the key is mapped in this AppContext;
       
   627      *          <code>null</code> if the key is not mapped to any value.
       
   628      * @see     #put(Object, Object)
       
   629      * @since   1.2
       
   630      */
       
   631     public Object get(Object key) {
       
   632         /*
       
   633          * The most recent reference should be updated inside a synchronized
       
   634          * block to avoid a race when put() and get() are executed in
       
   635          * parallel on different threads.
       
   636          */
       
   637         synchronized (table) {
       
   638             // Note: this most recent key/value caching is thread-hot.
       
   639             // A simple test using SwingSet found that 72% of lookups
       
   640             // were matched using the most recent key/value.  By instantiating
       
   641             // a simple MostRecentKeyValue object on cache misses, the
       
   642             // cache hits can be processed without synchronization.
       
   643 
       
   644             MostRecentKeyValue recent = mostRecentKeyValue;
       
   645             if ((recent != null) && (recent.key == key)) {
       
   646                 return recent.value;
       
   647             }
       
   648 
       
   649             Object value = table.get(key);
       
   650             if(mostRecentKeyValue == null) {
       
   651                 mostRecentKeyValue = new MostRecentKeyValue(key, value);
       
   652                 shadowMostRecentKeyValue = new MostRecentKeyValue(key, value);
       
   653             } else {
       
   654                 MostRecentKeyValue auxKeyValue = mostRecentKeyValue;
       
   655                 shadowMostRecentKeyValue.setPair(key, value);
       
   656                 mostRecentKeyValue = shadowMostRecentKeyValue;
       
   657                 shadowMostRecentKeyValue = auxKeyValue;
       
   658             }
       
   659             return value;
       
   660         }
       
   661     }
       
   662 
       
   663     /**
       
   664      * Maps the specified <code>key</code> to the specified
       
   665      * <code>value</code> in this AppContext.  Neither the key nor the
       
   666      * value can be <code>null</code>.
       
   667      * <p>
       
   668      * The value can be retrieved by calling the <code>get</code> method
       
   669      * with a key that is equal to the original key.
       
   670      *
       
   671      * @param      key     the AppContext key.
       
   672      * @param      value   the value.
       
   673      * @return     the previous value of the specified key in this
       
   674      *             AppContext, or <code>null</code> if it did not have one.
       
   675      * @exception  NullPointerException  if the key or value is
       
   676      *               <code>null</code>.
       
   677      * @see     #get(Object)
       
   678      * @since   1.2
       
   679      */
       
   680     public Object put(Object key, Object value) {
       
   681         synchronized (table) {
       
   682             MostRecentKeyValue recent = mostRecentKeyValue;
       
   683             if ((recent != null) && (recent.key == key))
       
   684                 recent.value = value;
       
   685             return table.put(key, value);
       
   686         }
       
   687     }
       
   688 
       
   689     /**
       
   690      * Removes the key (and its corresponding value) from this
       
   691      * AppContext. This method does nothing if the key is not in the
       
   692      * AppContext.
       
   693      *
       
   694      * @param   key   the key that needs to be removed.
       
   695      * @return  the value to which the key had been mapped in this AppContext,
       
   696      *          or <code>null</code> if the key did not have a mapping.
       
   697      * @since   1.2
       
   698      */
       
   699     public Object remove(Object key) {
       
   700         synchronized (table) {
       
   701             MostRecentKeyValue recent = mostRecentKeyValue;
       
   702             if ((recent != null) && (recent.key == key))
       
   703                 recent.value = null;
       
   704             return table.remove(key);
       
   705         }
       
   706     }
       
   707 
       
   708     /**
       
   709      * Returns the root ThreadGroup for all Threads contained within
       
   710      * this AppContext.
       
   711      * @since   1.2
       
   712      */
       
   713     public ThreadGroup getThreadGroup() {
       
   714         return threadGroup;
       
   715     }
       
   716 
       
   717     /**
       
   718      * Returns the context ClassLoader that was used to create this
       
   719      * AppContext.
       
   720      *
       
   721      * @see java.lang.Thread#getContextClassLoader
       
   722      */
       
   723     public ClassLoader getContextClassLoader() {
       
   724         return contextClassLoader;
       
   725     }
       
   726 
       
   727     /**
       
   728      * Returns a string representation of this AppContext.
       
   729      * @since   1.2
       
   730      */
       
   731     @Override
       
   732     public String toString() {
       
   733         return getClass().getName() + "[threadGroup=" + threadGroup.getName() + "]";
       
   734     }
       
   735 
       
   736     /**
       
   737      * Returns an array of all the property change listeners
       
   738      * registered on this component.
       
   739      *
       
   740      * @return all of this component's <code>PropertyChangeListener</code>s
       
   741      *         or an empty array if no property change
       
   742      *         listeners are currently registered
       
   743      *
       
   744      * @see      #addPropertyChangeListener
       
   745      * @see      #removePropertyChangeListener
       
   746      * @see      #getPropertyChangeListeners(java.lang.String)
       
   747      * @see      java.beans.PropertyChangeSupport#getPropertyChangeListeners
       
   748      * @since    1.4
       
   749      */
       
   750     public synchronized PropertyChangeListener[] getPropertyChangeListeners() {
       
   751         if (changeSupport == null) {
       
   752             return new PropertyChangeListener[0];
       
   753         }
       
   754         return changeSupport.getPropertyChangeListeners();
       
   755     }
       
   756 
       
   757     /**
       
   758      * Adds a PropertyChangeListener to the listener list for a specific
       
   759      * property. The specified property may be one of the following:
       
   760      * <ul>
       
   761      *    <li>if this AppContext is disposed ("disposed")</li>
       
   762      * </ul>
       
   763      * <ul>
       
   764      *    <li>if this AppContext's unowned Windows have been disposed
       
   765      *    ("guidisposed").  Code to cleanup after the GUI is disposed
       
   766      *    (such as LookAndFeel.uninitialize()) should execute in response to
       
   767      *    this property being fired.  Notifications for the "guidisposed"
       
   768      *    property are sent on the event dispatch thread.</li>
       
   769      * </ul>
       
   770      * <p>
       
   771      * If listener is null, no exception is thrown and no action is performed.
       
   772      *
       
   773      * @param propertyName one of the property names listed above
       
   774      * @param listener the PropertyChangeListener to be added
       
   775      *
       
   776      * @see #removePropertyChangeListener(java.lang.String, java.beans.PropertyChangeListener)
       
   777      * @see #getPropertyChangeListeners(java.lang.String)
       
   778      * @see #addPropertyChangeListener(java.lang.String, java.beans.PropertyChangeListener)
       
   779      */
       
   780     public synchronized void addPropertyChangeListener(
       
   781                              String propertyName,
       
   782                              PropertyChangeListener listener) {
       
   783         if (listener == null) {
       
   784             return;
       
   785         }
       
   786         if (changeSupport == null) {
       
   787             changeSupport = new PropertyChangeSupport(this);
       
   788         }
       
   789         changeSupport.addPropertyChangeListener(propertyName, listener);
       
   790     }
       
   791 
       
   792     /**
       
   793      * Removes a PropertyChangeListener from the listener list for a specific
       
   794      * property. This method should be used to remove PropertyChangeListeners
       
   795      * that were registered for a specific bound property.
       
   796      * <p>
       
   797      * If listener is null, no exception is thrown and no action is performed.
       
   798      *
       
   799      * @param propertyName a valid property name
       
   800      * @param listener the PropertyChangeListener to be removed
       
   801      *
       
   802      * @see #addPropertyChangeListener(java.lang.String, java.beans.PropertyChangeListener)
       
   803      * @see #getPropertyChangeListeners(java.lang.String)
       
   804      * @see #removePropertyChangeListener(java.beans.PropertyChangeListener)
       
   805      */
       
   806     public synchronized void removePropertyChangeListener(
       
   807                              String propertyName,
       
   808                              PropertyChangeListener listener) {
       
   809         if (listener == null || changeSupport == null) {
       
   810             return;
       
   811         }
       
   812         changeSupport.removePropertyChangeListener(propertyName, listener);
       
   813     }
       
   814 
       
   815     /**
       
   816      * Returns an array of all the listeners which have been associated
       
   817      * with the named property.
       
   818      *
       
   819      * @return all of the <code>PropertyChangeListeners</code> associated with
       
   820      *         the named property or an empty array if no listeners have
       
   821      *         been added
       
   822      *
       
   823      * @see #addPropertyChangeListener(java.lang.String, java.beans.PropertyChangeListener)
       
   824      * @see #removePropertyChangeListener(java.lang.String, java.beans.PropertyChangeListener)
       
   825      * @see #getPropertyChangeListeners
       
   826      * @since 1.4
       
   827      */
       
   828     public synchronized PropertyChangeListener[] getPropertyChangeListeners(
       
   829                                                         String propertyName) {
       
   830         if (changeSupport == null) {
       
   831             return new PropertyChangeListener[0];
       
   832         }
       
   833         return changeSupport.getPropertyChangeListeners(propertyName);
       
   834     }
       
   835 
       
   836     // Set up JavaAWTAccess in SharedSecrets
       
   837     static {
       
   838         sun.misc.SharedSecrets.setJavaAWTAccess(new sun.misc.JavaAWTAccess() {
       
   839             private boolean hasRootThreadGroup(final AppContext ecx) {
       
   840                 return AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
       
   841                     @Override
       
   842                     public Boolean run() {
       
   843                         return ecx.threadGroup.getParent() == null;
       
   844                     }
       
   845                 });
       
   846             }
       
   847 
       
   848             /**
       
   849              * Returns the AppContext used for applet logging isolation, or null if
       
   850              * the default global context can be used.
       
   851              * If there's no applet, or if the caller is a stand alone application,
       
   852              * or running in the main app context, returns null.
       
   853              * Otherwise, returns the AppContext of the calling applet.
       
   854              * @return null if the global default context can be used,
       
   855              *         an AppContext otherwise.
       
   856              **/
       
   857             public Object getAppletContext() {
       
   858                 // There's no AppContext: return null.
       
   859                 // No need to call getAppContext() if numAppContext == 0:
       
   860                 // it means that no AppContext has been created yet, and
       
   861                 // we don't want to trigger the creation of a main app
       
   862                 // context since we don't need it.
       
   863                 if (numAppContexts.get() == 0) return null;
       
   864 
       
   865                 // Get the context from the security manager
       
   866                 AppContext ecx = getExecutionAppContext();
       
   867 
       
   868                 // Not sure we really need to re-check numAppContexts here.
       
   869                 // If all applets have gone away then we could have a
       
   870                 // numAppContexts coming back to 0. So we recheck
       
   871                 // it here because we don't want to trigger the
       
   872                 // creation of a main AppContext in that case.
       
   873                 // This is probably not 100% MT-safe but should reduce
       
   874                 // the window of opportunity in which that issue could
       
   875                 // happen.
       
   876                 if (numAppContexts.get() > 0) {
       
   877                    // Defaults to thread group caching.
       
   878                    // This is probably not required as we only really need
       
   879                    // isolation in a deployed applet environment, in which
       
   880                    // case ecx will not be null when we reach here
       
   881                    // However it helps emulate the deployed environment,
       
   882                    // in tests for instance.
       
   883                    ecx = ecx != null ? ecx : getAppContext();
       
   884                 }
       
   885 
       
   886                 // getAppletContext() may be called when initializing the main
       
   887                 // app context - in which case mainAppContext will still be
       
   888                 // null. To work around this issue we simply use
       
   889                 // AppContext.threadGroup.getParent() == null instead, since
       
   890                 // mainAppContext is the only AppContext which should have
       
   891                 // the root TG as its thread group.
       
   892                 // See: JDK-8023258
       
   893                 final boolean isMainAppContext = ecx == null
       
   894                     || mainAppContext == ecx
       
   895                     || mainAppContext == null && hasRootThreadGroup(ecx);
       
   896 
       
   897                 return isMainAppContext ? null : ecx;
       
   898             }
       
   899 
       
   900         });
       
   901     }
       
   902 
       
   903     public static <T> T getSoftReferenceValue(Object key,
       
   904             Supplier<T> supplier) {
       
   905 
       
   906         final AppContext appContext = AppContext.getAppContext();
       
   907         @SuppressWarnings("unchecked")
       
   908         SoftReference<T> ref = (SoftReference<T>) appContext.get(key);
       
   909         if (ref != null) {
       
   910             final T object = ref.get();
       
   911             if (object != null) {
       
   912                 return object;
       
   913             }
       
   914         }
       
   915         final T object = supplier.get();
       
   916         ref = new SoftReference<>(object);
       
   917         appContext.put(key, ref);
       
   918         return object;
       
   919     }
       
   920 }
       
   921 
       
   922 final class MostRecentKeyValue {
       
   923     Object key;
       
   924     Object value;
       
   925     MostRecentKeyValue(Object k, Object v) {
       
   926         key = k;
       
   927         value = v;
       
   928     }
       
   929     void setPair(Object k, Object v) {
       
   930         key = k;
       
   931         value = v;
       
   932     }
       
   933 }