1 /*
   2  * Copyright (c) 1998, 2015, 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 jdk.internal.misc.JavaAWTAccess;
  48 import jdk.internal.misc.SharedSecrets;
  49 import sun.misc.ManagedLocalsThread;
  50 import sun.util.logging.PlatformLogger;
  51 import java.util.concurrent.locks.Condition;
  52 import java.util.concurrent.locks.Lock;
  53 import java.util.concurrent.locks.ReentrantLock;
  54 import java.util.concurrent.atomic.AtomicInteger;
  55 import java.util.function.Supplier;
  56 
  57 /**
  58  * The AppContext is a table referenced by ThreadGroup which stores
  59  * application service instances.  (If you are not writing an application
  60  * service, or don't know what one is, please do not use this class.)
  61  * The AppContext allows applet access to what would otherwise be
  62  * potentially dangerous services, such as the ability to peek at
  63  * EventQueues or change the look-and-feel of a Swing application.<p>
  64  *
  65  * Most application services use a singleton object to provide their
  66  * services, either as a default (such as getSystemEventQueue or
  67  * getDefaultToolkit) or as static methods with class data (System).
  68  * The AppContext works with the former method by extending the concept
  69  * of "default" to be ThreadGroup-specific.  Application services
  70  * lookup their singleton in the AppContext.<p>
  71  *
  72  * For example, here we have a Foo service, with its pre-AppContext
  73  * code:<p>
  74  * <code><pre>
  75  *    public class Foo {
  76  *        private static Foo defaultFoo = new Foo();
  77  *
  78  *        public static Foo getDefaultFoo() {
  79  *            return defaultFoo;
  80  *        }
  81  *
  82  *    ... Foo service methods
  83  *    }</pre></code><p>

  84  *
  85  * The problem with the above is that the Foo service is global in scope,
  86  * so that applets and other untrusted code can execute methods on the
  87  * single, shared Foo instance.  The Foo service therefore either needs
  88  * to block its use by untrusted code using a SecurityManager test, or
  89  * restrict its capabilities so that it doesn't matter if untrusted code
  90  * executes it.<p>
  91  *
  92  * Here's the Foo class written to use the AppContext:<p>
  93  * <code><pre>
  94  *    public class Foo {
  95  *        public static Foo getDefaultFoo() {
  96  *            Foo foo = (Foo)AppContext.getAppContext().get(Foo.class);
  97  *            if (foo == null) {
  98  *                foo = new Foo();
  99  *                getAppContext().put(Foo.class, foo);
 100  *            }
 101  *            return foo;
 102  *        }
 103  *
 104  *    ... Foo service methods
 105  *    }</pre></code><p>

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