History

My first initial commit with all of mine experimental changes.

Dmitry Kabak (userdima committed 13 years ago

Commit d37b88d1 parent 3f5b46618352 files · +2598093 −-8690Browse files

Showing the changes to java/pkg/sun/awt/AppContext.java (1 of 18352 files). Show all

Unified · Split

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