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