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