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