1 /*
   2  * Copyright (c) 1998, 2014, 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 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 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 static boolean isMainContext(AppContext ctx) {
 379         return (ctx != null && ctx == mainAppContext);
 380     }
 381 
 382     private 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     @SuppressWarnings("deprecation")
 417     public void dispose() throws IllegalThreadStateException {
 418         // Check to be sure that the current Thread isn't in this AppContext
 419         if (this.threadGroup.parentOf(Thread.currentThread().getThreadGroup())) {
 420             throw new IllegalThreadStateException(
 421                 "Current Thread is contained within AppContext to be disposed."
 422               );
 423         }
 424 
 425         synchronized(this) {
 426             if (this.state != State.VALID) {
 427                 return; // If already disposed or being disposed, bail.
 428             }
 429 
 430             this.state = State.BEING_DISPOSED;
 431         }
 432 
 433         final PropertyChangeSupport changeSupport = this.changeSupport;
 434         if (changeSupport != null) {
 435             changeSupport.firePropertyChange(DISPOSED_PROPERTY_NAME, false, true);
 436         }
 437 
 438         // First, we post an InvocationEvent to be run on the
 439         // EventDispatchThread which disposes of all top-level Frames and TrayIcons
 440 
 441         final Object notificationLock = new Object();
 442 
 443         Runnable runnable = new Runnable() {
 444             public void run() {
 445                 Window[] windowsToDispose = Window.getOwnerlessWindows();
 446                 for (Window w : windowsToDispose) {
 447                     try {
 448                         w.dispose();
 449                     } catch (Throwable t) {
 450                         log.finer("exception occurred while disposing app context", t);
 451                     }
 452                 }
 453                 AccessController.doPrivileged(new PrivilegedAction<Void>() {
 454                         public Void run() {
 455                             if (!GraphicsEnvironment.isHeadless() && SystemTray.isSupported())
 456                             {
 457                                 SystemTray systemTray = SystemTray.getSystemTray();
 458                                 TrayIcon[] trayIconsToDispose = systemTray.getTrayIcons();
 459                                 for (TrayIcon ti : trayIconsToDispose) {
 460                                     systemTray.remove(ti);
 461                                 }
 462                             }
 463                             return null;
 464                         }
 465                     });
 466                 // Alert PropertyChangeListeners that the GUI has been disposed.
 467                 if (changeSupport != null) {
 468                     changeSupport.firePropertyChange(GUI_DISPOSED, false, true);
 469                 }
 470                 synchronized(notificationLock) {
 471                     notificationLock.notifyAll(); // Notify caller that we're done
 472                 }
 473             }
 474         };
 475         synchronized(notificationLock) {
 476             SunToolkit.postEvent(this,
 477                 new InvocationEvent(Toolkit.getDefaultToolkit(), runnable));
 478             try {
 479                 notificationLock.wait(DISPOSAL_TIMEOUT);
 480             } catch (InterruptedException e) { }
 481         }
 482 
 483         // Next, we post another InvocationEvent to the end of the
 484         // EventQueue.  When it's executed, we know we've executed all
 485         // events in the queue.
 486 
 487         runnable = new Runnable() { public void run() {
 488             synchronized(notificationLock) {
 489                 notificationLock.notifyAll(); // Notify caller that we're done
 490             }
 491         } };
 492         synchronized(notificationLock) {
 493             SunToolkit.postEvent(this,
 494                 new InvocationEvent(Toolkit.getDefaultToolkit(), runnable));
 495             try {
 496                 notificationLock.wait(DISPOSAL_TIMEOUT);
 497             } catch (InterruptedException e) { }
 498         }
 499 
 500         // We are done with posting events, so change the state to disposed
 501         synchronized(this) {
 502             this.state = State.DISPOSED;
 503         }
 504 
 505         // Next, we interrupt all Threads in the ThreadGroup
 506         this.threadGroup.interrupt();
 507             // Note, the EventDispatchThread we've interrupted may dump an
 508             // InterruptedException to the console here.  This needs to be
 509             // fixed in the EventDispatchThread, not here.
 510 
 511         // Next, we sleep 10ms at a time, waiting for all of the active
 512         // Threads in the ThreadGroup to exit.
 513 
 514         long startTime = System.currentTimeMillis();
 515         long endTime = startTime + THREAD_INTERRUPT_TIMEOUT;
 516         while ((this.threadGroup.activeCount() > 0) &&
 517                (System.currentTimeMillis() < endTime)) {
 518             try {
 519                 Thread.sleep(10);
 520             } catch (InterruptedException e) { }
 521         }
 522 
 523         // Then, we stop any remaining Threads
 524         this.threadGroup.stop();
 525 
 526         // Next, we sleep 10ms at a time, waiting for all of the active
 527         // Threads in the ThreadGroup to die.
 528 
 529         startTime = System.currentTimeMillis();
 530         endTime = startTime + THREAD_INTERRUPT_TIMEOUT;
 531         while ((this.threadGroup.activeCount() > 0) &&
 532                (System.currentTimeMillis() < endTime)) {
 533             try {
 534                 Thread.sleep(10);
 535             } catch (InterruptedException e) { }
 536         }
 537 
 538         // Next, we remove this and all subThreadGroups from threadGroup2appContext
 539         int numSubGroups = this.threadGroup.activeGroupCount();
 540         if (numSubGroups > 0) {
 541             ThreadGroup [] subGroups = new ThreadGroup[numSubGroups];
 542             numSubGroups = this.threadGroup.enumerate(subGroups);
 543             for (int subGroup = 0; subGroup < numSubGroups; subGroup++) {
 544                 threadGroup2appContext.remove(subGroups[subGroup]);
 545             }
 546         }
 547         threadGroup2appContext.remove(this.threadGroup);
 548 
 549         threadAppContext.set(null);
 550 
 551         // Finally, we destroy the ThreadGroup entirely.
 552         try {
 553             this.threadGroup.destroy();
 554         } catch (IllegalThreadStateException e) {
 555             // Fired if not all the Threads died, ignore it and proceed
 556         }
 557 
 558         synchronized (table) {
 559             this.table.clear(); // Clear out the Hashtable to ease garbage collection
 560         }
 561 
 562         numAppContexts.decrementAndGet();
 563 
 564         mostRecentKeyValue = null;
 565     }
 566 
 567     static final class PostShutdownEventRunnable implements Runnable {
 568         private final AppContext appContext;
 569 
 570         public PostShutdownEventRunnable(AppContext ac) {
 571             appContext = ac;
 572         }
 573 
 574         public void run() {
 575             final EventQueue eq = (EventQueue)appContext.get(EVENT_QUEUE_KEY);
 576             if (eq != null) {
 577                 eq.postEvent(AWTAutoShutdown.getShutdownEvent());
 578             }
 579         }
 580     }
 581 
 582     static final class CreateThreadAction implements PrivilegedAction<Thread> {
 583         private final AppContext appContext;
 584         private final Runnable runnable;
 585 
 586         public CreateThreadAction(AppContext ac, Runnable r) {
 587             appContext = ac;
 588             runnable = r;
 589         }
 590 
 591         public Thread run() {
 592             Thread t = new Thread(appContext.getThreadGroup(), runnable);
 593             t.setContextClassLoader(appContext.getContextClassLoader());
 594             t.setPriority(Thread.NORM_PRIORITY + 1);
 595             t.setDaemon(true);
 596             return t;
 597         }
 598     }
 599 
 600     static void stopEventDispatchThreads() {
 601         for (AppContext appContext: getAppContexts()) {
 602             if (appContext.isDisposed()) {
 603                 continue;
 604             }
 605             Runnable r = new PostShutdownEventRunnable(appContext);
 606             // For security reasons EventQueue.postEvent should only be called
 607             // on a thread that belongs to the corresponding thread group.
 608             if (appContext != AppContext.getAppContext()) {
 609                 // Create a thread that belongs to the thread group associated
 610                 // with the AppContext and invokes EventQueue.postEvent.
 611                 PrivilegedAction<Thread> action = new CreateThreadAction(appContext, r);
 612                 Thread thread = AccessController.doPrivileged(action);
 613                 thread.start();
 614             } else {
 615                 r.run();
 616             }
 617         }
 618     }
 619 
 620     private MostRecentKeyValue mostRecentKeyValue = null;
 621     private MostRecentKeyValue shadowMostRecentKeyValue = null;
 622 
 623     /**
 624      * Returns the value to which the specified key is mapped in this context.
 625      *
 626      * @param   key   a key in the AppContext.
 627      * @return  the value to which the key is mapped in this AppContext;
 628      *          <code>null</code> if the key is not mapped to any value.
 629      * @see     #put(Object, Object)
 630      * @since   1.2
 631      */
 632     public Object get(Object key) {
 633         /*
 634          * The most recent reference should be updated inside a synchronized
 635          * block to avoid a race when put() and get() are executed in
 636          * parallel on different threads.
 637          */
 638         synchronized (table) {
 639             // Note: this most recent key/value caching is thread-hot.
 640             // A simple test using SwingSet found that 72% of lookups
 641             // were matched using the most recent key/value.  By instantiating
 642             // a simple MostRecentKeyValue object on cache misses, the
 643             // cache hits can be processed without synchronization.
 644 
 645             MostRecentKeyValue recent = mostRecentKeyValue;
 646             if ((recent != null) && (recent.key == key)) {
 647                 return recent.value;
 648             }
 649 
 650             Object value = table.get(key);
 651             if(mostRecentKeyValue == null) {
 652                 mostRecentKeyValue = new MostRecentKeyValue(key, value);
 653                 shadowMostRecentKeyValue = new MostRecentKeyValue(key, value);
 654             } else {
 655                 MostRecentKeyValue auxKeyValue = mostRecentKeyValue;
 656                 shadowMostRecentKeyValue.setPair(key, value);
 657                 mostRecentKeyValue = shadowMostRecentKeyValue;
 658                 shadowMostRecentKeyValue = auxKeyValue;
 659             }
 660             return value;
 661         }
 662     }
 663 
 664     /**
 665      * Maps the specified <code>key</code> to the specified
 666      * <code>value</code> in this AppContext.  Neither the key nor the
 667      * value can be <code>null</code>.
 668      * <p>
 669      * The value can be retrieved by calling the <code>get</code> method
 670      * with a key that is equal to the original key.
 671      *
 672      * @param      key     the AppContext key.
 673      * @param      value   the value.
 674      * @return     the previous value of the specified key in this
 675      *             AppContext, or <code>null</code> if it did not have one.
 676      * @exception  NullPointerException  if the key or value is
 677      *               <code>null</code>.
 678      * @see     #get(Object)
 679      * @since   1.2
 680      */
 681     public Object put(Object key, Object value) {
 682         synchronized (table) {
 683             MostRecentKeyValue recent = mostRecentKeyValue;
 684             if ((recent != null) && (recent.key == key))
 685                 recent.value = value;
 686             return table.put(key, value);
 687         }
 688     }
 689 
 690     /**
 691      * Removes the key (and its corresponding value) from this
 692      * AppContext. This method does nothing if the key is not in the
 693      * AppContext.
 694      *
 695      * @param   key   the key that needs to be removed.
 696      * @return  the value to which the key had been mapped in this AppContext,
 697      *          or <code>null</code> if the key did not have a mapping.
 698      * @since   1.2
 699      */
 700     public Object remove(Object key) {
 701         synchronized (table) {
 702             MostRecentKeyValue recent = mostRecentKeyValue;
 703             if ((recent != null) && (recent.key == key))
 704                 recent.value = null;
 705             return table.remove(key);
 706         }
 707     }
 708 
 709     /**
 710      * Returns the root ThreadGroup for all Threads contained within
 711      * this AppContext.
 712      * @since   1.2
 713      */
 714     public ThreadGroup getThreadGroup() {
 715         return threadGroup;
 716     }
 717 
 718     /**
 719      * Returns the context ClassLoader that was used to create this
 720      * AppContext.
 721      *
 722      * @see java.lang.Thread#getContextClassLoader
 723      */
 724     public ClassLoader getContextClassLoader() {
 725         return contextClassLoader;
 726     }
 727 
 728     /**
 729      * Returns a string representation of this AppContext.
 730      * @since   1.2
 731      */
 732     @Override
 733     public String toString() {
 734         return getClass().getName() + "[threadGroup=" + threadGroup.getName() + "]";
 735     }
 736 
 737     /**
 738      * Returns an array of all the property change listeners
 739      * registered on this component.
 740      *
 741      * @return all of this component's <code>PropertyChangeListener</code>s
 742      *         or an empty array if no property change
 743      *         listeners are currently registered
 744      *
 745      * @see      #addPropertyChangeListener
 746      * @see      #removePropertyChangeListener
 747      * @see      #getPropertyChangeListeners(java.lang.String)
 748      * @see      java.beans.PropertyChangeSupport#getPropertyChangeListeners
 749      * @since    1.4
 750      */
 751     public synchronized PropertyChangeListener[] getPropertyChangeListeners() {
 752         if (changeSupport == null) {
 753             return new PropertyChangeListener[0];
 754         }
 755         return changeSupport.getPropertyChangeListeners();
 756     }
 757 
 758     /**
 759      * Adds a PropertyChangeListener to the listener list for a specific
 760      * property. The specified property may be one of the following:
 761      * <ul>
 762      *    <li>if this AppContext is disposed ("disposed")</li>
 763      * </ul>
 764      * <ul>
 765      *    <li>if this AppContext's unowned Windows have been disposed
 766      *    ("guidisposed").  Code to cleanup after the GUI is disposed
 767      *    (such as LookAndFeel.uninitialize()) should execute in response to
 768      *    this property being fired.  Notifications for the "guidisposed"
 769      *    property are sent on the event dispatch thread.</li>
 770      * </ul>
 771      * <p>
 772      * If listener is null, no exception is thrown and no action is performed.
 773      *
 774      * @param propertyName one of the property names listed above
 775      * @param listener the PropertyChangeListener to be added
 776      *
 777      * @see #removePropertyChangeListener(java.lang.String, java.beans.PropertyChangeListener)
 778      * @see #getPropertyChangeListeners(java.lang.String)
 779      * @see #addPropertyChangeListener(java.lang.String, java.beans.PropertyChangeListener)
 780      */
 781     public synchronized void addPropertyChangeListener(
 782                              String propertyName,
 783                              PropertyChangeListener listener) {
 784         if (listener == null) {
 785             return;
 786         }
 787         if (changeSupport == null) {
 788             changeSupport = new PropertyChangeSupport(this);
 789         }
 790         changeSupport.addPropertyChangeListener(propertyName, listener);
 791     }
 792 
 793     /**
 794      * Removes a PropertyChangeListener from the listener list for a specific
 795      * property. This method should be used to remove PropertyChangeListeners
 796      * that were registered for a specific bound property.
 797      * <p>
 798      * If listener is null, no exception is thrown and no action is performed.
 799      *
 800      * @param propertyName a valid property name
 801      * @param listener the PropertyChangeListener to be removed
 802      *
 803      * @see #addPropertyChangeListener(java.lang.String, java.beans.PropertyChangeListener)
 804      * @see #getPropertyChangeListeners(java.lang.String)
 805      * @see #removePropertyChangeListener(java.beans.PropertyChangeListener)
 806      */
 807     public synchronized void removePropertyChangeListener(
 808                              String propertyName,
 809                              PropertyChangeListener listener) {
 810         if (listener == null || changeSupport == null) {
 811             return;
 812         }
 813         changeSupport.removePropertyChangeListener(propertyName, listener);
 814     }
 815 
 816     /**
 817      * Returns an array of all the listeners which have been associated
 818      * with the named property.
 819      *
 820      * @return all of the <code>PropertyChangeListeners</code> associated with
 821      *         the named property or an empty array if no listeners have
 822      *         been added
 823      *
 824      * @see #addPropertyChangeListener(java.lang.String, java.beans.PropertyChangeListener)
 825      * @see #removePropertyChangeListener(java.lang.String, java.beans.PropertyChangeListener)
 826      * @see #getPropertyChangeListeners
 827      * @since 1.4
 828      */
 829     public synchronized PropertyChangeListener[] getPropertyChangeListeners(
 830                                                         String propertyName) {
 831         if (changeSupport == null) {
 832             return new PropertyChangeListener[0];
 833         }
 834         return changeSupport.getPropertyChangeListeners(propertyName);
 835     }
 836 
 837     // Set up JavaAWTAccess in SharedSecrets
 838     static {
 839         sun.misc.SharedSecrets.setJavaAWTAccess(new sun.misc.JavaAWTAccess() {
 840             private boolean hasRootThreadGroup(final AppContext ecx) {
 841                 return AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
 842                     @Override
 843                     public Boolean run() {
 844                         return ecx.threadGroup.getParent() == null;
 845                     }
 846                 });
 847             }
 848 
 849             /**
 850              * Returns the AppContext used for applet logging isolation, or null if
 851              * the default global context can be used.
 852              * If there's no applet, or if the caller is a stand alone application,
 853              * or running in the main app context, returns null.
 854              * Otherwise, returns the AppContext of the calling applet.
 855              * @return null if the global default context can be used,
 856              *         an AppContext otherwise.
 857              **/
 858             public Object getAppletContext() {
 859                 // There's no AppContext: return null.
 860                 // No need to call getAppContext() if numAppContext == 0:
 861                 // it means that no AppContext has been created yet, and
 862                 // we don't want to trigger the creation of a main app
 863                 // context since we don't need it.
 864                 if (numAppContexts.get() == 0) return null;
 865 
 866                 // Get the context from the security manager
 867                 AppContext ecx = getExecutionAppContext();
 868 
 869                 // Not sure we really need to re-check numAppContexts here.
 870                 // If all applets have gone away then we could have a
 871                 // numAppContexts coming back to 0. So we recheck
 872                 // it here because we don't want to trigger the
 873                 // creation of a main AppContext in that case.
 874                 // This is probably not 100% MT-safe but should reduce
 875                 // the window of opportunity in which that issue could
 876                 // happen.
 877                 if (numAppContexts.get() > 0) {
 878                    // Defaults to thread group caching.
 879                    // This is probably not required as we only really need
 880                    // isolation in a deployed applet environment, in which
 881                    // case ecx will not be null when we reach here
 882                    // However it helps emulate the deployed environment,
 883                    // in tests for instance.
 884                    ecx = ecx != null ? ecx : getAppContext();
 885                 }
 886 
 887                 // getAppletContext() may be called when initializing the main
 888                 // app context - in which case mainAppContext will still be
 889                 // null. To work around this issue we simply use
 890                 // AppContext.threadGroup.getParent() == null instead, since
 891                 // mainAppContext is the only AppContext which should have
 892                 // the root TG as its thread group.
 893                 // See: JDK-8023258
 894                 final boolean isMainAppContext = ecx == null
 895                     || mainAppContext == ecx
 896                     || mainAppContext == null && hasRootThreadGroup(ecx);
 897 
 898                 return isMainAppContext ? null : ecx;
 899             }
 900 
 901         });
 902     }
 903 
 904     public static <T> T getSoftReferenceValue(Object key,
 905             Supplier<T> supplier) {
 906 
 907         final AppContext appContext = AppContext.getAppContext();
 908         @SuppressWarnings("unchecked")
 909         SoftReference<T> ref = (SoftReference<T>) appContext.get(key);
 910         if (ref != null) {
 911             final T object = ref.get();
 912             if (object != null) {
 913                 return object;
 914             }
 915         }
 916         final T object = supplier.get();
 917         ref = new SoftReference<>(object);
 918         appContext.put(key, ref);
 919         return object;
 920     }
 921 }
 922 
 923 final class MostRecentKeyValue {
 924     Object key;
 925     Object value;
 926     MostRecentKeyValue(Object k, Object v) {
 927         key = k;
 928         value = v;
 929     }
 930     void setPair(Object k, Object v) {
 931         key = k;
 932         value = v;
 933     }
 934 }