Path: blob/master/src/java.desktop/share/classes/sun/awt/AppContext.java
41152 views
/*1* Copyright (c) 1998, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package sun.awt;2627import java.awt.EventQueue;28import java.awt.Window;29import java.awt.SystemTray;30import java.awt.TrayIcon;31import java.awt.Toolkit;32import java.awt.GraphicsEnvironment;33import java.awt.event.InvocationEvent;34import java.security.AccessController;35import java.security.PrivilegedAction;36import java.util.Collections;37import java.util.HashMap;38import java.util.IdentityHashMap;39import java.util.Map;40import java.util.Set;41import java.util.HashSet;42import java.beans.PropertyChangeSupport;43import java.beans.PropertyChangeListener;44import java.lang.ref.SoftReference;4546import jdk.internal.access.JavaAWTAccess;47import jdk.internal.access.SharedSecrets;48import sun.util.logging.PlatformLogger;49import java.util.concurrent.locks.Condition;50import java.util.concurrent.locks.Lock;51import java.util.concurrent.locks.ReentrantLock;52import java.util.concurrent.atomic.AtomicInteger;53import java.util.function.Supplier;5455/**56* The AppContext is a table referenced by ThreadGroup which stores57* application service instances. (If you are not writing an application58* service, or don't know what one is, please do not use this class.)59* The AppContext allows applet access to what would otherwise be60* potentially dangerous services, such as the ability to peek at61* EventQueues or change the look-and-feel of a Swing application.<p>62*63* Most application services use a singleton object to provide their64* services, either as a default (such as getSystemEventQueue or65* getDefaultToolkit) or as static methods with class data (System).66* The AppContext works with the former method by extending the concept67* of "default" to be ThreadGroup-specific. Application services68* lookup their singleton in the AppContext.<p>69*70* For example, here we have a Foo service, with its pre-AppContext71* code:<p>72* <pre>{@code73* public class Foo {74* private static Foo defaultFoo = new Foo();75*76* public static Foo getDefaultFoo() {77* return defaultFoo;78* }79*80* ... Foo service methods81* }82* }</pre><p>83*84* The problem with the above is that the Foo service is global in scope,85* so that applets and other untrusted code can execute methods on the86* single, shared Foo instance. The Foo service therefore either needs87* to block its use by untrusted code using a SecurityManager test, or88* restrict its capabilities so that it doesn't matter if untrusted code89* executes it.<p>90*91* Here's the Foo class written to use the AppContext:<p>92* <pre>{@code93* public class Foo {94* public static Foo getDefaultFoo() {95* Foo foo = (Foo)AppContext.getAppContext().get(Foo.class);96* if (foo == null) {97* foo = new Foo();98* getAppContext().put(Foo.class, foo);99* }100* return foo;101* }102*103* ... Foo service methods104* }105* }</pre><p>106*107* Since a separate AppContext can exist for each ThreadGroup, trusted108* and untrusted code have access to different Foo instances. This allows109* untrusted code access to "system-wide" services -- the service remains110* within the AppContext "sandbox". For example, say a malicious applet111* wants to peek all of the key events on the EventQueue to listen for112* passwords; if separate EventQueues are used for each ThreadGroup113* using AppContexts, the only key events that applet will be able to114* listen to are its own. A more reasonable applet request would be to115* change the Swing default look-and-feel; with that default stored in116* an AppContext, the applet's look-and-feel will change without117* disrupting other applets or potentially the browser itself.<p>118*119* Because the AppContext is a facility for safely extending application120* service support to applets, none of its methods may be blocked by a121* a SecurityManager check in a valid Java implementation. Applets may122* therefore safely invoke any of its methods without worry of being123* blocked.124*125* @author Thomas Ball126* @author Fred Ecks127*/128public final class AppContext {129private static final PlatformLogger log = PlatformLogger.getLogger("sun.awt.AppContext");130131/* Since the contents of an AppContext are unique to each Java132* session, this class should never be serialized. */133134/*135* The key to put()/get() the Java EventQueue into/from the AppContext.136*/137public static final Object EVENT_QUEUE_KEY = new StringBuffer("EventQueue");138139/*140* The keys to store EventQueue push/pop lock and condition.141*/142public static final Object EVENT_QUEUE_LOCK_KEY = new StringBuilder("EventQueue.Lock");143public static final Object EVENT_QUEUE_COND_KEY = new StringBuilder("EventQueue.Condition");144145/* A map of AppContexts, referenced by ThreadGroup.146*/147private static final Map<ThreadGroup, AppContext> threadGroup2appContext =148Collections.synchronizedMap(new IdentityHashMap<ThreadGroup, AppContext>());149150/**151* Returns a set containing all {@code AppContext}s.152*/153public static Set<AppContext> getAppContexts() {154synchronized (threadGroup2appContext) {155return new HashSet<AppContext>(threadGroup2appContext.values());156}157}158159/* The main "system" AppContext, used by everything not otherwise160contained in another AppContext. It is implicitly created for161standalone apps only (i.e. not applets)162*/163private static volatile AppContext mainAppContext = null;164165private static class GetAppContextLock {};166private static final Object getAppContextLock = new GetAppContextLock();167168/*169* The hash map associated with this AppContext. A private delegate170* is used instead of subclassing HashMap so as to avoid all of171* HashMap's potentially risky methods, such as clear(), elements(),172* putAll(), etc.173*/174private final Map<Object, Object> table = new HashMap<>();175176private final ThreadGroup threadGroup;177178/**179* If any {@code PropertyChangeListeners} have been registered,180* the {@code changeSupport} field describes them.181*182* @see #addPropertyChangeListener183* @see #removePropertyChangeListener184* @see PropertyChangeSupport#firePropertyChange185*/186private PropertyChangeSupport changeSupport = null;187188public static final String DISPOSED_PROPERTY_NAME = "disposed";189public static final String GUI_DISPOSED = "guidisposed";190191private enum State {192VALID,193BEING_DISPOSED,194DISPOSED195};196197private volatile State state = State.VALID;198199public boolean isDisposed() {200return state == State.DISPOSED;201}202203/*204* The total number of AppContexts, system-wide. This number is205* incremented at the beginning of the constructor, and decremented206* at the end of dispose(). getAppContext() checks to see if this207* number is 1. If so, it returns the sole AppContext without208* checking Thread.currentThread().209*/210private static final AtomicInteger numAppContexts = new AtomicInteger();211212213/*214* The context ClassLoader that was used to create this AppContext.215*/216private final ClassLoader contextClassLoader;217218/**219* Constructor for AppContext. This method is <i>not</i> public,220* nor should it ever be used as such. The proper way to construct221* an AppContext is through the use of SunToolkit.createNewAppContext.222* A ThreadGroup is created for the new AppContext, a Thread is223* created within that ThreadGroup, and that Thread calls224* SunToolkit.createNewAppContext before calling anything else.225* That creates both the new AppContext and its EventQueue.226*227* @param threadGroup The ThreadGroup for the new AppContext228* @see sun.awt.SunToolkit229* @since 1.2230*/231@SuppressWarnings("removal")232AppContext(ThreadGroup threadGroup) {233numAppContexts.incrementAndGet();234235this.threadGroup = threadGroup;236threadGroup2appContext.put(threadGroup, this);237238this.contextClassLoader =239AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {240public ClassLoader run() {241return Thread.currentThread().getContextClassLoader();242}243});244245// Initialize push/pop lock and its condition to be used by all the246// EventQueues within this AppContext247Lock eventQueuePushPopLock = new ReentrantLock();248put(EVENT_QUEUE_LOCK_KEY, eventQueuePushPopLock);249Condition eventQueuePushPopCond = eventQueuePushPopLock.newCondition();250put(EVENT_QUEUE_COND_KEY, eventQueuePushPopCond);251}252253private static final ThreadLocal<AppContext> threadAppContext =254new ThreadLocal<AppContext>();255256@SuppressWarnings("removal")257private static void initMainAppContext() {258// On the main Thread, we get the ThreadGroup, make a corresponding259// AppContext, and instantiate the Java EventQueue. This way, legacy260// code is unaffected by the move to multiple AppContext ability.261AccessController.doPrivileged(new PrivilegedAction<Void>() {262public Void run() {263ThreadGroup currentThreadGroup =264Thread.currentThread().getThreadGroup();265ThreadGroup parentThreadGroup = currentThreadGroup.getParent();266while (parentThreadGroup != null) {267// Find the root ThreadGroup to construct our main AppContext268currentThreadGroup = parentThreadGroup;269parentThreadGroup = currentThreadGroup.getParent();270}271272mainAppContext = SunToolkit.createNewAppContext(currentThreadGroup);273return null;274}275});276}277278/**279* Returns the appropriate AppContext for the caller,280* as determined by its ThreadGroup.281*282* @return the AppContext for the caller.283* @see java.lang.ThreadGroup284* @since 1.2285*/286@SuppressWarnings("removal")287public static AppContext getAppContext() {288// we are standalone app, return the main app context289if (numAppContexts.get() == 1 && mainAppContext != null) {290return mainAppContext;291}292293AppContext appContext = threadAppContext.get();294295if (null == appContext) {296appContext = AccessController.doPrivileged(new PrivilegedAction<AppContext>()297{298public AppContext run() {299// Get the current ThreadGroup, and look for it and its300// parents in the hash from ThreadGroup to AppContext --301// it should be found, because we use createNewContext()302// when new AppContext objects are created.303ThreadGroup currentThreadGroup = Thread.currentThread().getThreadGroup();304ThreadGroup threadGroup = currentThreadGroup;305306// Special case: we implicitly create the main app context307// if no contexts have been created yet. This covers standalone apps308// and excludes applets because by the time applet starts309// a number of contexts have already been created by the plugin.310synchronized (getAppContextLock) {311if (numAppContexts.get() == 0) {312if (System.getProperty("javaplugin.version") == null &&313System.getProperty("javawebstart.version") == null) {314initMainAppContext();315} else if (System.getProperty("javafx.version") != null &&316threadGroup.getParent() != null) {317// Swing inside JavaFX case318SunToolkit.createNewAppContext();319}320}321}322323AppContext context = threadGroup2appContext.get(threadGroup);324while (context == null) {325threadGroup = threadGroup.getParent();326if (threadGroup == null) {327// We've got up to the root thread group and did not find an AppContext328// Try to get it from the security manager329SecurityManager securityManager = System.getSecurityManager();330if (securityManager != null) {331ThreadGroup smThreadGroup = securityManager.getThreadGroup();332if (smThreadGroup != null) {333/*334* If we get this far then it's likely that335* the ThreadGroup does not actually belong336* to the applet, so do not cache it.337*/338return threadGroup2appContext.get(smThreadGroup);339}340}341return null;342}343context = threadGroup2appContext.get(threadGroup);344}345346// In case we did anything in the above while loop, we add347// all the intermediate ThreadGroups to threadGroup2appContext348// so we won't spin again.349for (ThreadGroup tg = currentThreadGroup; tg != threadGroup; tg = tg.getParent()) {350threadGroup2appContext.put(tg, context);351}352353// Now we're done, so we cache the latest key/value pair.354threadAppContext.set(context);355356return context;357}358});359}360361return appContext;362}363364/**365* Returns true if the specified AppContext is the main AppContext.366*367* @param ctx the context to compare with the main context368* @return true if the specified AppContext is the main AppContext.369* @since 1.8370*/371public static boolean isMainContext(AppContext ctx) {372return (ctx != null && ctx == mainAppContext);373}374375private long DISPOSAL_TIMEOUT = 5000; // Default to 5-second timeout376// for disposal of all Frames377// (we wait for this time twice,378// once for dispose(), and once379// to clear the EventQueue).380381private long THREAD_INTERRUPT_TIMEOUT = 1000;382// Default to 1-second timeout for all383// interrupted Threads to exit, and another384// 1 second for all stopped Threads to die.385386/**387* Disposes of this AppContext, all of its top-level Frames, and388* all Threads and ThreadGroups contained within it.389*390* This method must be called from a Thread which is not contained391* within this AppContext.392*393* @exception IllegalThreadStateException if the current thread is394* contained within this AppContext395* @since 1.2396*/397@SuppressWarnings({"deprecation", "removal"})398public void dispose() throws IllegalThreadStateException {399// Check to be sure that the current Thread isn't in this AppContext400if (this.threadGroup.parentOf(Thread.currentThread().getThreadGroup())) {401throw new IllegalThreadStateException(402"Current Thread is contained within AppContext to be disposed."403);404}405406synchronized(this) {407if (this.state != State.VALID) {408return; // If already disposed or being disposed, bail.409}410411this.state = State.BEING_DISPOSED;412}413414final PropertyChangeSupport changeSupport = this.changeSupport;415if (changeSupport != null) {416changeSupport.firePropertyChange(DISPOSED_PROPERTY_NAME, false, true);417}418419// First, we post an InvocationEvent to be run on the420// EventDispatchThread which disposes of all top-level Frames and TrayIcons421422final Object notificationLock = new Object();423424Runnable runnable = new Runnable() {425public void run() {426Window[] windowsToDispose = Window.getOwnerlessWindows();427for (Window w : windowsToDispose) {428try {429w.dispose();430} catch (Throwable t) {431log.finer("exception occurred while disposing app context", t);432}433}434AccessController.doPrivileged(new PrivilegedAction<Void>() {435public Void run() {436if (!GraphicsEnvironment.isHeadless() && SystemTray.isSupported())437{438SystemTray systemTray = SystemTray.getSystemTray();439TrayIcon[] trayIconsToDispose = systemTray.getTrayIcons();440for (TrayIcon ti : trayIconsToDispose) {441systemTray.remove(ti);442}443}444return null;445}446});447// Alert PropertyChangeListeners that the GUI has been disposed.448if (changeSupport != null) {449changeSupport.firePropertyChange(GUI_DISPOSED, false, true);450}451synchronized(notificationLock) {452notificationLock.notifyAll(); // Notify caller that we're done453}454}455};456synchronized(notificationLock) {457SunToolkit.postEvent(this,458new InvocationEvent(Toolkit.getDefaultToolkit(), runnable));459try {460notificationLock.wait(DISPOSAL_TIMEOUT);461} catch (InterruptedException e) { }462}463464// Next, we post another InvocationEvent to the end of the465// EventQueue. When it's executed, we know we've executed all466// events in the queue.467468runnable = new Runnable() { public void run() {469synchronized(notificationLock) {470notificationLock.notifyAll(); // Notify caller that we're done471}472} };473synchronized(notificationLock) {474SunToolkit.postEvent(this,475new InvocationEvent(Toolkit.getDefaultToolkit(), runnable));476try {477notificationLock.wait(DISPOSAL_TIMEOUT);478} catch (InterruptedException e) { }479}480481// We are done with posting events, so change the state to disposed482synchronized(this) {483this.state = State.DISPOSED;484}485486// Next, we interrupt all Threads in the ThreadGroup487this.threadGroup.interrupt();488// Note, the EventDispatchThread we've interrupted may dump an489// InterruptedException to the console here. This needs to be490// fixed in the EventDispatchThread, not here.491492// Next, we sleep 10ms at a time, waiting for all of the active493// Threads in the ThreadGroup to exit.494495long startTime = System.currentTimeMillis();496long endTime = startTime + THREAD_INTERRUPT_TIMEOUT;497while ((this.threadGroup.activeCount() > 0) &&498(System.currentTimeMillis() < endTime)) {499try {500Thread.sleep(10);501} catch (InterruptedException e) { }502}503504// Then, we stop any remaining Threads505AccessController.doPrivileged((PrivilegedAction<Void>) () -> {506threadGroup.stop();507return null;508});509510// Next, we sleep 10ms at a time, waiting for all of the active511// Threads in the ThreadGroup to die.512513startTime = System.currentTimeMillis();514endTime = startTime + THREAD_INTERRUPT_TIMEOUT;515while ((this.threadGroup.activeCount() > 0) &&516(System.currentTimeMillis() < endTime)) {517try {518Thread.sleep(10);519} catch (InterruptedException e) { }520}521522// Next, we remove this and all subThreadGroups from threadGroup2appContext523int numSubGroups = this.threadGroup.activeGroupCount();524if (numSubGroups > 0) {525ThreadGroup [] subGroups = new ThreadGroup[numSubGroups];526numSubGroups = this.threadGroup.enumerate(subGroups);527for (int subGroup = 0; subGroup < numSubGroups; subGroup++) {528threadGroup2appContext.remove(subGroups[subGroup]);529}530}531threadGroup2appContext.remove(this.threadGroup);532533threadAppContext.set(null);534535// Finally, we destroy the ThreadGroup entirely.536try {537this.threadGroup.destroy();538} catch (IllegalThreadStateException e) {539// Fired if not all the Threads died, ignore it and proceed540}541542synchronized (table) {543this.table.clear(); // Clear out the Hashtable to ease garbage collection544}545546numAppContexts.decrementAndGet();547548mostRecentKeyValue = null;549}550551static final class PostShutdownEventRunnable implements Runnable {552private final AppContext appContext;553554PostShutdownEventRunnable(AppContext ac) {555appContext = ac;556}557558public void run() {559final EventQueue eq = (EventQueue)appContext.get(EVENT_QUEUE_KEY);560if (eq != null) {561eq.postEvent(AWTAutoShutdown.getShutdownEvent());562}563}564}565566static final class CreateThreadAction implements PrivilegedAction<Thread> {567private final AppContext appContext;568private final Runnable runnable;569570CreateThreadAction(AppContext ac, Runnable r) {571appContext = ac;572runnable = r;573}574575public Thread run() {576Thread t = new Thread(appContext.getThreadGroup(),577runnable, "AppContext Disposer", 0, false);578t.setContextClassLoader(appContext.getContextClassLoader());579t.setPriority(Thread.NORM_PRIORITY + 1);580t.setDaemon(true);581return t;582}583}584585static void stopEventDispatchThreads() {586for (AppContext appContext: getAppContexts()) {587if (appContext.isDisposed()) {588continue;589}590Runnable r = new PostShutdownEventRunnable(appContext);591// For security reasons EventQueue.postEvent should only be called592// on a thread that belongs to the corresponding thread group.593if (appContext != AppContext.getAppContext()) {594// Create a thread that belongs to the thread group associated595// with the AppContext and invokes EventQueue.postEvent.596PrivilegedAction<Thread> action = new CreateThreadAction(appContext, r);597@SuppressWarnings("removal")598Thread thread = AccessController.doPrivileged(action);599thread.start();600} else {601r.run();602}603}604}605606private MostRecentKeyValue mostRecentKeyValue = null;607private MostRecentKeyValue shadowMostRecentKeyValue = null;608609/**610* Returns the value to which the specified key is mapped in this context.611*612* @param key a key in the AppContext.613* @return the value to which the key is mapped in this AppContext;614* {@code null} if the key is not mapped to any value.615* @see #put(Object, Object)616* @since 1.2617*/618public Object get(Object key) {619/*620* The most recent reference should be updated inside a synchronized621* block to avoid a race when put() and get() are executed in622* parallel on different threads.623*/624synchronized (table) {625// Note: this most recent key/value caching is thread-hot.626// A simple test using SwingSet found that 72% of lookups627// were matched using the most recent key/value. By instantiating628// a simple MostRecentKeyValue object on cache misses, the629// cache hits can be processed without synchronization.630631MostRecentKeyValue recent = mostRecentKeyValue;632if ((recent != null) && (recent.key == key)) {633return recent.value;634}635636Object value = table.get(key);637if(mostRecentKeyValue == null) {638mostRecentKeyValue = new MostRecentKeyValue(key, value);639shadowMostRecentKeyValue = new MostRecentKeyValue(key, value);640} else {641MostRecentKeyValue auxKeyValue = mostRecentKeyValue;642shadowMostRecentKeyValue.setPair(key, value);643mostRecentKeyValue = shadowMostRecentKeyValue;644shadowMostRecentKeyValue = auxKeyValue;645}646return value;647}648}649650/**651* Maps the specified {@code key} to the specified652* {@code value} in this AppContext. Neither the key nor the653* value can be {@code null}.654* <p>655* The value can be retrieved by calling the {@code get} method656* with a key that is equal to the original key.657*658* @param key the AppContext key.659* @param value the value.660* @return the previous value of the specified key in this661* AppContext, or {@code null} if it did not have one.662* @exception NullPointerException if the key or value is663* {@code null}.664* @see #get(Object)665* @since 1.2666*/667public Object put(Object key, Object value) {668synchronized (table) {669MostRecentKeyValue recent = mostRecentKeyValue;670if ((recent != null) && (recent.key == key))671recent.value = value;672return table.put(key, value);673}674}675676/**677* Removes the key (and its corresponding value) from this678* AppContext. This method does nothing if the key is not in the679* AppContext.680*681* @param key the key that needs to be removed.682* @return the value to which the key had been mapped in this AppContext,683* or {@code null} if the key did not have a mapping.684* @since 1.2685*/686public Object remove(Object key) {687synchronized (table) {688MostRecentKeyValue recent = mostRecentKeyValue;689if ((recent != null) && (recent.key == key))690recent.value = null;691return table.remove(key);692}693}694695/**696* Returns the root ThreadGroup for all Threads contained within697* this AppContext.698* @since 1.2699*/700public ThreadGroup getThreadGroup() {701return threadGroup;702}703704/**705* Returns the context ClassLoader that was used to create this706* AppContext.707*708* @see java.lang.Thread#getContextClassLoader709*/710public ClassLoader getContextClassLoader() {711return contextClassLoader;712}713714/**715* Returns a string representation of this AppContext.716* @since 1.2717*/718@Override719public String toString() {720return getClass().getName() + "[threadGroup=" + threadGroup.getName() + "]";721}722723/**724* Returns an array of all the property change listeners725* registered on this component.726*727* @return all of this component's {@code PropertyChangeListener}s728* or an empty array if no property change729* listeners are currently registered730*731* @see #addPropertyChangeListener732* @see #removePropertyChangeListener733* @see #getPropertyChangeListeners(java.lang.String)734* @see java.beans.PropertyChangeSupport#getPropertyChangeListeners735* @since 1.4736*/737public synchronized PropertyChangeListener[] getPropertyChangeListeners() {738if (changeSupport == null) {739return new PropertyChangeListener[0];740}741return changeSupport.getPropertyChangeListeners();742}743744/**745* Adds a PropertyChangeListener to the listener list for a specific746* property. The specified property may be one of the following:747* <ul>748* <li>if this AppContext is disposed ("disposed")</li>749* </ul>750* <ul>751* <li>if this AppContext's unowned Windows have been disposed752* ("guidisposed"). Code to cleanup after the GUI is disposed753* (such as LookAndFeel.uninitialize()) should execute in response to754* this property being fired. Notifications for the "guidisposed"755* property are sent on the event dispatch thread.</li>756* </ul>757* <p>758* If listener is null, no exception is thrown and no action is performed.759*760* @param propertyName one of the property names listed above761* @param listener the PropertyChangeListener to be added762*763* @see #removePropertyChangeListener(java.lang.String, java.beans.PropertyChangeListener)764* @see #getPropertyChangeListeners(java.lang.String)765* @see #addPropertyChangeListener(java.lang.String, java.beans.PropertyChangeListener)766*/767public synchronized void addPropertyChangeListener(768String propertyName,769PropertyChangeListener listener) {770if (listener == null) {771return;772}773if (changeSupport == null) {774changeSupport = new PropertyChangeSupport(this);775}776changeSupport.addPropertyChangeListener(propertyName, listener);777}778779/**780* Removes a PropertyChangeListener from the listener list for a specific781* property. This method should be used to remove PropertyChangeListeners782* that were registered for a specific bound property.783* <p>784* If listener is null, no exception is thrown and no action is performed.785*786* @param propertyName a valid property name787* @param listener the PropertyChangeListener to be removed788*789* @see #addPropertyChangeListener(java.lang.String, java.beans.PropertyChangeListener)790* @see #getPropertyChangeListeners(java.lang.String)791* @see PropertyChangeSupport#removePropertyChangeListener(java.beans.PropertyChangeListener)792*/793public synchronized void removePropertyChangeListener(794String propertyName,795PropertyChangeListener listener) {796if (listener == null || changeSupport == null) {797return;798}799changeSupport.removePropertyChangeListener(propertyName, listener);800}801802/**803* Returns an array of all the listeners which have been associated804* with the named property.805*806* @return all of the {@code PropertyChangeListeners} associated with807* the named property or an empty array if no listeners have808* been added809*810* @see #addPropertyChangeListener(java.lang.String, java.beans.PropertyChangeListener)811* @see #removePropertyChangeListener(java.lang.String, java.beans.PropertyChangeListener)812* @see #getPropertyChangeListeners813* @since 1.4814*/815public synchronized PropertyChangeListener[] getPropertyChangeListeners(816String propertyName) {817if (changeSupport == null) {818return new PropertyChangeListener[0];819}820return changeSupport.getPropertyChangeListeners(propertyName);821}822823// Set up JavaAWTAccess in SharedSecrets824static {825SharedSecrets.setJavaAWTAccess(new JavaAWTAccess() {826@SuppressWarnings("removal")827private boolean hasRootThreadGroup(final AppContext ecx) {828return AccessController.doPrivileged(new PrivilegedAction<Boolean>() {829@Override830public Boolean run() {831return ecx.threadGroup.getParent() == null;832}833});834}835836/**837* Returns the AppContext used for applet logging isolation, or null if838* the default global context can be used.839* If there's no applet, or if the caller is a stand alone application,840* or running in the main app context, returns null.841* Otherwise, returns the AppContext of the calling applet.842* @return null if the global default context can be used,843* an AppContext otherwise.844**/845public Object getAppletContext() {846// There's no AppContext: return null.847// No need to call getAppContext() if numAppContext == 0:848// it means that no AppContext has been created yet, and849// we don't want to trigger the creation of a main app850// context since we don't need it.851if (numAppContexts.get() == 0) return null;852853AppContext ecx = null;854855// Not sure we really need to re-check numAppContexts here.856// If all applets have gone away then we could have a857// numAppContexts coming back to 0. So we recheck858// it here because we don't want to trigger the859// creation of a main AppContext in that case.860// This is probably not 100% MT-safe but should reduce861// the window of opportunity in which that issue could862// happen.863if (numAppContexts.get() > 0) {864// Defaults to thread group caching.865// This is probably not required as we only really need866// isolation in a deployed applet environment, in which867// case ecx will not be null when we reach here868// However it helps emulate the deployed environment,869// in tests for instance.870ecx = ecx != null ? ecx : getAppContext();871}872873// getAppletContext() may be called when initializing the main874// app context - in which case mainAppContext will still be875// null. To work around this issue we simply use876// AppContext.threadGroup.getParent() == null instead, since877// mainAppContext is the only AppContext which should have878// the root TG as its thread group.879// See: JDK-8023258880final boolean isMainAppContext = ecx == null881|| mainAppContext == ecx882|| mainAppContext == null && hasRootThreadGroup(ecx);883884return isMainAppContext ? null : ecx;885}886887});888}889890public static <T> T getSoftReferenceValue(Object key,891Supplier<T> supplier) {892893final AppContext appContext = AppContext.getAppContext();894@SuppressWarnings("unchecked")895SoftReference<T> ref = (SoftReference<T>) appContext.get(key);896if (ref != null) {897final T object = ref.get();898if (object != null) {899return object;900}901}902final T object = supplier.get();903ref = new SoftReference<>(object);904appContext.put(key, ref);905return object;906}907}908909final class MostRecentKeyValue {910Object key;911Object value;912MostRecentKeyValue(Object k, Object v) {913key = k;914value = v;915}916void setPair(Object k, Object v) {917key = k;918value = v;919}920}921922923