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      */
getAppContexts()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 
isDisposed()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      */
AppContext(ThreadGroup threadGroup)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 
initMainAppContext()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      */
getAppContext()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      */
isMainContext(AppContext ctx)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")
dispose()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 
PostShutdownEventRunnable(AppContext ac)552         PostShutdownEventRunnable(AppContext ac) {
553             appContext = ac;
554         }
555 
run()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 
CreateThreadAction(AppContext ac, Runnable r)568         CreateThreadAction(AppContext ac, Runnable r) {
569             appContext = ac;
570             runnable = r;
571         }
572 
run()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 
stopEventDispatchThreads()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      */
get(Object key)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      */
put(Object key, Object value)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      */
remove(Object key)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      */
getThreadGroup()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      */
getContextClassLoader()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
toString()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      */
getPropertyChangeListeners()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      */
addPropertyChangeListener( String propertyName, PropertyChangeListener listener)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      */
removePropertyChangeListener( String propertyName, PropertyChangeListener listener)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      */
getPropertyChangeListeners( String propertyName)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 {
SharedSecrets.setJavaAWTAccess(new JavaAWTAccess() { private boolean hasRootThreadGroup(final AppContext ecx) { return AccessController.doPrivileged(new PrivilegedAction<Boolean>() { @Override public Boolean run() { return ecx.threadGroup.getParent() == null; } }); } public Object getAppletContext() { if (numAppContexts.get() == 0) return null; AppContext ecx = null; if (numAppContexts.get() > 0) { ecx = ecx != null ? ecx : getAppContext(); } final boolean isMainAppContext = ecx == null || mainAppContext == ecx || mainAppContext == null && hasRootThreadGroup(ecx); return isMainAppContext ? null : ecx; } })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 
getSoftReferenceValue(Object key, Supplier<T> supplier)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;
MostRecentKeyValue(Object k, Object v)908     MostRecentKeyValue(Object k, Object v) {
909         key = k;
910         value = v;
911     }
setPair(Object k, Object v)912     void setPair(Object k, Object v) {
913         key = k;
914         value = v;
915     }
916 }
917