001: /*
002: * Copyright 1998-2007 Sun Microsystems, Inc. All Rights Reserved.
003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004: *
005: * This code is free software; you can redistribute it and/or modify it
006: * under the terms of the GNU General Public License version 2 only, as
007: * published by the Free Software Foundation. Sun designates this
008: * particular file as subject to the "Classpath" exception as provided
009: * by Sun in the LICENSE file that accompanied this code.
010: *
011: * This code is distributed in the hope that it will be useful, but WITHOUT
012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014: * version 2 for more details (a copy is included in the LICENSE file that
015: * accompanied this code).
016: *
017: * You should have received a copy of the GNU General Public License version
018: * 2 along with this work; if not, write to the Free Software Foundation,
019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020: *
021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022: * CA 95054 USA or visit www.sun.com if you need additional information or
023: * have any questions.
024: */
025:
026: package sun.awt;
027:
028: import java.awt.EventQueue;
029: import java.awt.Window;
030: import java.awt.SystemTray;
031: import java.awt.TrayIcon;
032: import java.awt.Toolkit;
033: import java.awt.GraphicsEnvironment;
034: import java.awt.event.InvocationEvent;
035: import java.security.AccessController;
036: import java.security.PrivilegedAction;
037: import java.util.Collections;
038: import java.util.HashMap;
039: import java.util.IdentityHashMap;
040: import java.util.Map;
041: import java.util.Set;
042: import java.util.HashSet;
043: import java.beans.PropertyChangeSupport;
044: import java.beans.PropertyChangeListener;
045:
046: /**
047: * The AppContext is a table referenced by ThreadGroup which stores
048: * application service instances. (If you are not writing an application
049: * service, or don't know what one is, please do not use this class.)
050: * The AppContext allows applet access to what would otherwise be
051: * potentially dangerous services, such as the ability to peek at
052: * EventQueues or change the look-and-feel of a Swing application.<p>
053: *
054: * Most application services use a singleton object to provide their
055: * services, either as a default (such as getSystemEventQueue or
056: * getDefaultToolkit) or as static methods with class data (System).
057: * The AppContext works with the former method by extending the concept
058: * of "default" to be ThreadGroup-specific. Application services
059: * lookup their singleton in the AppContext.<p>
060: *
061: * For example, here we have a Foo service, with its pre-AppContext
062: * code:<p>
063: * <code><pre>
064: * public class Foo {
065: * private static Foo defaultFoo = new Foo();
066: *
067: * public static Foo getDefaultFoo() {
068: * return defaultFoo;
069: * }
070: *
071: * ... Foo service methods
072: * }</pre></code><p>
073: *
074: * The problem with the above is that the Foo service is global in scope,
075: * so that applets and other untrusted code can execute methods on the
076: * single, shared Foo instance. The Foo service therefore either needs
077: * to block its use by untrusted code using a SecurityManager test, or
078: * restrict its capabilities so that it doesn't matter if untrusted code
079: * executes it.<p>
080: *
081: * Here's the Foo class written to use the AppContext:<p>
082: * <code><pre>
083: * public class Foo {
084: * public static Foo getDefaultFoo() {
085: * Foo foo = (Foo)AppContext.getAppContext().get(Foo.class);
086: * if (foo == null) {
087: * foo = new Foo();
088: * getAppContext().put(Foo.class, foo);
089: * }
090: * return foo;
091: * }
092: *
093: * ... Foo service methods
094: * }</pre></code><p>
095: *
096: * Since a separate AppContext can exist for each ThreadGroup, trusted
097: * and untrusted code have access to different Foo instances. This allows
098: * untrusted code access to "system-wide" services -- the service remains
099: * within the AppContext "sandbox". For example, say a malicious applet
100: * wants to peek all of the key events on the EventQueue to listen for
101: * passwords; if separate EventQueues are used for each ThreadGroup
102: * using AppContexts, the only key events that applet will be able to
103: * listen to are its own. A more reasonable applet request would be to
104: * change the Swing default look-and-feel; with that default stored in
105: * an AppContext, the applet's look-and-feel will change without
106: * disrupting other applets or potentially the browser itself.<p>
107: *
108: * Because the AppContext is a facility for safely extending application
109: * service support to applets, none of its methods may be blocked by a
110: * a SecurityManager check in a valid Java implementation. Applets may
111: * therefore safely invoke any of its methods without worry of being
112: * blocked.
113: *
114: * Note: If a SecurityManager is installed which derives from
115: * sun.awt.AWTSecurityManager, it may override the
116: * AWTSecurityManager.getAppContext() method to return the proper
117: * AppContext based on the execution context, in the case where
118: * the default ThreadGroup-based AppContext indexing would return
119: * the main "system" AppContext. For example, in an applet situation,
120: * if a system thread calls into an applet, rather than returning the
121: * main "system" AppContext (the one corresponding to the system thread),
122: * an installed AWTSecurityManager may return the applet's AppContext
123: * based on the execution context.
124: *
125: * @author Thomas Ball
126: * @author Fred Ecks
127: * @version 1.47 05/28/07
128: */
129: public final class AppContext {
130:
131: /* Since the contents of an AppContext are unique to each Java
132: * session, this class should never be serialized. */
133:
134: /* The key to put()/get() the Java EventQueue into/from the AppContext.
135: */
136: public static final Object EVENT_QUEUE_KEY = new StringBuffer(
137: "EventQueue");
138:
139: /* A map of AppContexts, referenced by ThreadGroup.
140: */
141: private static final Map<ThreadGroup, AppContext> threadGroup2appContext = Collections
142: .synchronizedMap(new IdentityHashMap<ThreadGroup, AppContext>());
143:
144: /**
145: * Returns a set containing all <code>AppContext</code>s.
146: */
147: public static Set<AppContext> getAppContexts() {
148: return new HashSet<AppContext>(threadGroup2appContext.values());
149: }
150:
151: /* The main "system" AppContext, used by everything not otherwise
152: contained in another AppContext.
153: */
154: private static AppContext mainAppContext = null;
155:
156: /*
157: * The hash map associated with this AppContext. A private delegate
158: * is used instead of subclassing HashMap so as to avoid all of
159: * HashMap's potentially risky methods, such as clear(), elements(),
160: * putAll(), etc.
161: */
162: private final HashMap table = new HashMap();
163:
164: private final ThreadGroup threadGroup;
165:
166: /**
167: * If any <code>PropertyChangeListeners</code> have been registered,
168: * the <code>changeSupport</code> field describes them.
169: *
170: * @see #addPropertyChangeListener
171: * @see #removePropertyChangeListener
172: * @see #firePropertyChange
173: */
174: private PropertyChangeSupport changeSupport = null;
175:
176: public static final String DISPOSED_PROPERTY_NAME = "disposed";
177: public static final String GUI_DISPOSED = "guidisposed";
178:
179: private boolean isDisposed = false; // true if AppContext is disposed
180:
181: public boolean isDisposed() {
182: return isDisposed;
183: }
184:
185: static {
186: // On the main Thread, we get the ThreadGroup, make a corresponding
187: // AppContext, and instantiate the Java EventQueue. This way, legacy
188: // code is unaffected by the move to multiple AppContext ability.
189: AccessController.doPrivileged(new PrivilegedAction() {
190: public Object run() {
191: ThreadGroup currentThreadGroup = Thread.currentThread()
192: .getThreadGroup();
193: ThreadGroup parentThreadGroup = currentThreadGroup
194: .getParent();
195: while (parentThreadGroup != null) {
196: // Find the root ThreadGroup to construct our main AppContext
197: currentThreadGroup = parentThreadGroup;
198: parentThreadGroup = currentThreadGroup.getParent();
199: }
200: mainAppContext = new AppContext(currentThreadGroup);
201: numAppContexts = 1;
202: return mainAppContext;
203: }
204: });
205: }
206:
207: /*
208: * The total number of AppContexts, system-wide. This number is
209: * incremented at the beginning of the constructor, and decremented
210: * at the end of dispose(). getAppContext() checks to see if this
211: * number is 1. If so, it returns the sole AppContext without
212: * checking Thread.currentThread().
213: */
214: private static int numAppContexts;
215:
216: /*
217: * The context ClassLoader that was used to create this AppContext.
218: */
219: private final ClassLoader contextClassLoader;
220:
221: /**
222: * Constructor for AppContext. This method is <i>not</i> public,
223: * nor should it ever be used as such. The proper way to construct
224: * an AppContext is through the use of SunToolkit.createNewAppContext.
225: * A ThreadGroup is created for the new AppContext, a Thread is
226: * created within that ThreadGroup, and that Thread calls
227: * SunToolkit.createNewAppContext before calling anything else.
228: * That creates both the new AppContext and its EventQueue.
229: *
230: * @param threadGroup The ThreadGroup for the new AppContext
231: * @see sun.awt.SunToolkit
232: * @since 1.2
233: */
234: AppContext(ThreadGroup threadGroup) {
235: numAppContexts++;
236:
237: this .threadGroup = threadGroup;
238: threadGroup2appContext.put(threadGroup, this );
239:
240: this .contextClassLoader = (ClassLoader) AccessController
241: .doPrivileged(new PrivilegedAction() {
242: public Object run() {
243: return Thread.currentThread()
244: .getContextClassLoader();
245: }
246: });
247: }
248:
249: private static MostRecentThreadAppContext mostRecentThreadAppContext = null;
250:
251: /**
252: * Returns the appropriate AppContext for the caller,
253: * as determined by its ThreadGroup. If the main "system" AppContext
254: * would be returned and there's an AWTSecurityManager installed, it
255: * is called to get the proper AppContext based on the execution
256: * context.
257: *
258: * @return the AppContext for the caller.
259: * @see java.lang.ThreadGroup
260: * @since 1.2
261: */
262: public final static AppContext getAppContext() {
263: if (numAppContexts == 1) // If there's only one system-wide,
264: return mainAppContext; // return the main system AppContext.
265:
266: final Thread currentThread = Thread.currentThread();
267:
268: AppContext appContext = null;
269:
270: // Note: this most recent Thread/AppContext caching is thread-hot.
271: // A simple test using SwingSet found that 96.8% of lookups
272: // were matched using the most recent Thread/AppContext. By
273: // instantiating a simple MostRecentThreadAppContext object on
274: // cache misses, the cache hits can be processed without
275: // synchronization.
276:
277: MostRecentThreadAppContext recent = mostRecentThreadAppContext;
278: if ((recent != null) && (recent.thread == currentThread)) {
279: appContext = recent.appContext; // Cache hit
280: } else {
281: appContext = (AppContext) AccessController
282: .doPrivileged(new PrivilegedAction() {
283: public Object run() {
284: // Get the current ThreadGroup, and look for it and its
285: // parents in the hash from ThreadGroup to AppContext --
286: // it should be found, because we use createNewContext()
287: // when new AppContext objects are created.
288: ThreadGroup currentThreadGroup = currentThread
289: .getThreadGroup();
290: ThreadGroup threadGroup = currentThreadGroup;
291: AppContext context = threadGroup2appContext
292: .get(threadGroup);
293: while (context == null) {
294: threadGroup = threadGroup.getParent();
295: if (threadGroup == null) {
296: // If we get here, we're running under a ThreadGroup that
297: // has no AppContext associated with it. This should never
298: // happen, because createNewContext() should be used by the
299: // toolkit to create the ThreadGroup that everything runs
300: // under.
301: throw new RuntimeException(
302: "Invalid ThreadGroup");
303: }
304: context = threadGroup2appContext
305: .get(threadGroup);
306: }
307: // In case we did anything in the above while loop, we add
308: // all the intermediate ThreadGroups to threadGroup2appContext
309: // so we won't spin again.
310: for (ThreadGroup tg = currentThreadGroup; tg != threadGroup; tg = tg
311: .getParent()) {
312: threadGroup2appContext.put(tg, context);
313: }
314: // Now we're done, so we cache the latest key/value pair.
315: // (we do this before checking with any AWTSecurityManager, so if
316: // this Thread equates with the main AppContext in the cache, it
317: // still will)
318: mostRecentThreadAppContext = new MostRecentThreadAppContext(
319: currentThread, 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
331: .getSecurityManager();
332: if ((securityManager != null)
333: && (securityManager instanceof AWTSecurityManager)) {
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:
353: // Default to 1-second timeout for all
354: // interrupted Threads to exit, and another
355: // 1 second for all stopped Threads to die.
356:
357: /**
358: * Disposes of this AppContext, all of its top-level Frames, and
359: * all Threads and ThreadGroups contained within it.
360: *
361: * This method must be called from a Thread which is not contained
362: * within this AppContext.
363: *
364: * @exception IllegalThreadStateException if the current thread is
365: * contained within this AppContext
366: * @since 1.2
367: */
368: public void dispose() throws IllegalThreadStateException {
369: // Check to be sure that the current Thread isn't in this AppContext
370: if (this .threadGroup.parentOf(Thread.currentThread()
371: .getThreadGroup())) {
372: throw new IllegalThreadStateException(
373: "Current Thread is contained within AppContext to be disposed.");
374: }
375:
376: synchronized (this ) {
377: if (this .isDisposed) {
378: return; // If already disposed, bail.
379: }
380: this .isDisposed = true;
381: }
382:
383: final PropertyChangeSupport changeSupport = this .changeSupport;
384: if (changeSupport != null) {
385: changeSupport.firePropertyChange(DISPOSED_PROPERTY_NAME,
386: false, true);
387: }
388:
389: // First, we post an InvocationEvent to be run on the
390: // EventDispatchThread which disposes of all top-level Frames and TrayIcons
391:
392: final Object notificationLock = new Object();
393:
394: Runnable runnable = new Runnable() {
395: public void run() {
396: Window[] windowsToDispose = Window
397: .getOwnerlessWindows();
398: for (Window w : windowsToDispose) {
399: w.dispose();
400: }
401: AccessController.doPrivileged(new PrivilegedAction() {
402: public Object run() {
403: if (!GraphicsEnvironment.isHeadless()
404: && SystemTray.isSupported()) {
405: SystemTray systemTray = SystemTray
406: .getSystemTray();
407: TrayIcon[] trayIconsToDispose = systemTray
408: .getTrayIcons();
409: for (TrayIcon ti : trayIconsToDispose) {
410: systemTray.remove(ti);
411: }
412: }
413: return null;
414: }
415: });
416: // Alert PropertyChangeListeners that the GUI has been disposed.
417: if (changeSupport != null) {
418: changeSupport.firePropertyChange(GUI_DISPOSED,
419: false, true);
420: }
421: synchronized (notificationLock) {
422: notificationLock.notifyAll(); // Notify caller that we're done
423: }
424: }
425: };
426: synchronized (notificationLock) {
427: SunToolkit.postEvent(this , new InvocationEvent(Toolkit
428: .getDefaultToolkit(), runnable));
429: try {
430: notificationLock.wait(DISPOSAL_TIMEOUT);
431: } catch (InterruptedException e) {
432: }
433: }
434:
435: // Next, we post another InvocationEvent to the end of the
436: // EventQueue. When it's executed, we know we've executed all
437: // events in the queue.
438:
439: runnable = new Runnable() {
440: public void run() {
441: synchronized (notificationLock) {
442: notificationLock.notifyAll(); // Notify caller that we're done
443: }
444: }
445: };
446: synchronized (notificationLock) {
447: SunToolkit.postEvent(this , new InvocationEvent(Toolkit
448: .getDefaultToolkit(), runnable));
449: try {
450: notificationLock.wait(DISPOSAL_TIMEOUT);
451: } catch (InterruptedException e) {
452: }
453: }
454:
455: // Next, we interrupt all Threads in the ThreadGroup
456: this .threadGroup.interrupt();
457: // Note, the EventDispatchThread we've interrupted may dump an
458: // InterruptedException to the console here. This needs to be
459: // fixed in the EventDispatchThread, not here.
460:
461: // Next, we sleep 10ms at a time, waiting for all of the active
462: // Threads in the ThreadGroup to exit.
463:
464: long startTime = System.currentTimeMillis();
465: long endTime = startTime + (long) THREAD_INTERRUPT_TIMEOUT;
466: while ((this .threadGroup.activeCount() > 0)
467: && (System.currentTimeMillis() < endTime)) {
468: try {
469: Thread.sleep(10);
470: } catch (InterruptedException e) {
471: }
472: }
473:
474: // Then, we stop any remaining Threads
475: this .threadGroup.stop();
476:
477: // Next, we sleep 10ms at a time, waiting for all of the active
478: // Threads in the ThreadGroup to die.
479:
480: startTime = System.currentTimeMillis();
481: endTime = startTime + (long) THREAD_INTERRUPT_TIMEOUT;
482: while ((this .threadGroup.activeCount() > 0)
483: && (System.currentTimeMillis() < endTime)) {
484: try {
485: Thread.sleep(10);
486: } catch (InterruptedException e) {
487: }
488: }
489:
490: // Next, we remove this and all subThreadGroups from threadGroup2appContext
491: int numSubGroups = this .threadGroup.activeGroupCount();
492: if (numSubGroups > 0) {
493: ThreadGroup[] subGroups = new ThreadGroup[numSubGroups];
494: numSubGroups = this .threadGroup.enumerate(subGroups);
495: for (int subGroup = 0; subGroup < numSubGroups; subGroup++) {
496: threadGroup2appContext.remove(subGroups[subGroup]);
497: }
498: }
499: threadGroup2appContext.remove(this .threadGroup);
500:
501: MostRecentThreadAppContext recent = mostRecentThreadAppContext;
502: if ((recent != null) && (recent.appContext == this ))
503: mostRecentThreadAppContext = null;
504: // If the "most recent" points to this, clear it for GC
505:
506: // Finally, we destroy the ThreadGroup entirely.
507: try {
508: this .threadGroup.destroy();
509: } catch (IllegalThreadStateException e) {
510: // Fired if not all the Threads died, ignore it and proceed
511: }
512:
513: synchronized (table) {
514: this .table.clear(); // Clear out the Hashtable to ease garbage collection
515: }
516:
517: numAppContexts--;
518:
519: mostRecentKeyValue = null;
520: }
521:
522: static final class PostShutdownEventRunnable implements Runnable {
523: private final AppContext appContext;
524:
525: public PostShutdownEventRunnable(AppContext ac) {
526: appContext = ac;
527: }
528:
529: public void run() {
530: final EventQueue eq = (EventQueue) appContext
531: .get(EVENT_QUEUE_KEY);
532: if (eq != null) {
533: eq.postEvent(AWTAutoShutdown.getShutdownEvent());
534: }
535: }
536: }
537:
538: static final class CreateThreadAction implements PrivilegedAction {
539: private final AppContext appContext;
540: private final Runnable runnable;
541:
542: public CreateThreadAction(AppContext ac, Runnable r) {
543: appContext = ac;
544: runnable = r;
545: }
546:
547: public Object run() {
548: Thread t = new Thread(appContext.getThreadGroup(), runnable);
549: t.setContextClassLoader(appContext.getContextClassLoader());
550: t.setPriority(Thread.NORM_PRIORITY + 1);
551: t.setDaemon(true);
552: return t;
553: }
554: }
555:
556: static void stopEventDispatchThreads() {
557: for (AppContext appContext : getAppContexts()) {
558: if (appContext.isDisposed()) {
559: continue;
560: }
561: Runnable r = new PostShutdownEventRunnable(appContext);
562: // For security reasons EventQueue.postEvent should only be called
563: // on a thread that belongs to the corresponding thread group.
564: if (appContext != AppContext.getAppContext()) {
565: // Create a thread that belongs to the thread group associated
566: // with the AppContext and invokes EventQueue.postEvent.
567: PrivilegedAction action = new CreateThreadAction(
568: appContext, r);
569: Thread thread = (Thread) AccessController
570: .doPrivileged(action);
571: thread.start();
572: } else {
573: r.run();
574: }
575: }
576: }
577:
578: private MostRecentKeyValue mostRecentKeyValue = null;
579: private MostRecentKeyValue shadowMostRecentKeyValue = null;
580:
581: /**
582: * Returns the value to which the specified key is mapped in this context.
583: *
584: * @param key a key in the AppContext.
585: * @return the value to which the key is mapped in this AppContext;
586: * <code>null</code> if the key is not mapped to any value.
587: * @see #put(Object, Object)
588: * @since 1.2
589: */
590: public Object get(Object key) {
591: /*
592: * The most recent reference should be updated inside a synchronized
593: * block to avoid a race when put() and get() are executed in
594: * parallel on different threads.
595: */
596: synchronized (table) {
597: // Note: this most recent key/value caching is thread-hot.
598: // A simple test using SwingSet found that 72% of lookups
599: // were matched using the most recent key/value. By instantiating
600: // a simple MostRecentKeyValue object on cache misses, the
601: // cache hits can be processed without synchronization.
602:
603: MostRecentKeyValue recent = mostRecentKeyValue;
604: if ((recent != null) && (recent.key == key)) {
605: return recent.value;
606: }
607:
608: Object value = table.get(key);
609: if (mostRecentKeyValue == null) {
610: mostRecentKeyValue = new MostRecentKeyValue(key, value);
611: shadowMostRecentKeyValue = new MostRecentKeyValue(key,
612: value);
613: } else {
614: MostRecentKeyValue auxKeyValue = mostRecentKeyValue;
615: shadowMostRecentKeyValue.setPair(key, value);
616: mostRecentKeyValue = shadowMostRecentKeyValue;
617: shadowMostRecentKeyValue = auxKeyValue;
618: }
619: return value;
620: }
621: }
622:
623: /**
624: * Maps the specified <code>key</code> to the specified
625: * <code>value</code> in this AppContext. Neither the key nor the
626: * value can be <code>null</code>.
627: * <p>
628: * The value can be retrieved by calling the <code>get</code> method
629: * with a key that is equal to the original key.
630: *
631: * @param key the AppContext key.
632: * @param value the value.
633: * @return the previous value of the specified key in this
634: * AppContext, or <code>null</code> if it did not have one.
635: * @exception NullPointerException if the key or value is
636: * <code>null</code>.
637: * @see #get(Object)
638: * @since 1.2
639: */
640: public Object put(Object key, Object value) {
641: synchronized (table) {
642: MostRecentKeyValue recent = mostRecentKeyValue;
643: if ((recent != null) && (recent.key == key))
644: recent.value = value;
645: return table.put(key, value);
646: }
647: }
648:
649: /**
650: * Removes the key (and its corresponding value) from this
651: * AppContext. This method does nothing if the key is not in the
652: * AppContext.
653: *
654: * @param key the key that needs to be removed.
655: * @return the value to which the key had been mapped in this AppContext,
656: * or <code>null</code> if the key did not have a mapping.
657: * @since 1.2
658: */
659: public Object remove(Object key) {
660: synchronized (table) {
661: MostRecentKeyValue recent = mostRecentKeyValue;
662: if ((recent != null) && (recent.key == key))
663: recent.value = null;
664: return table.remove(key);
665: }
666: }
667:
668: /**
669: * Returns the root ThreadGroup for all Threads contained within
670: * this AppContext.
671: * @since 1.2
672: */
673: public ThreadGroup getThreadGroup() {
674: return threadGroup;
675: }
676:
677: /**
678: * Returns the context ClassLoader that was used to create this
679: * AppContext.
680: *
681: * @see java.lang.Thread#getContextClassLoader
682: */
683: public ClassLoader getContextClassLoader() {
684: return contextClassLoader;
685: }
686:
687: /**
688: * Returns a string representation of this AppContext.
689: * @since 1.2
690: */
691: public String toString() {
692: return getClass().getName() + "[threadGroup="
693: + threadGroup.getName() + "]";
694: }
695:
696: /**
697: * Returns an array of all the property change listeners
698: * registered on this component.
699: *
700: * @return all of this component's <code>PropertyChangeListener</code>s
701: * or an empty array if no property change
702: * listeners are currently registered
703: *
704: * @see #addPropertyChangeListener
705: * @see #removePropertyChangeListener
706: * @see #getPropertyChangeListeners(java.lang.String)
707: * @see java.beans.PropertyChangeSupport#getPropertyChangeListeners
708: * @since 1.4
709: */
710: public synchronized PropertyChangeListener[] getPropertyChangeListeners() {
711: if (changeSupport == null) {
712: return new PropertyChangeListener[0];
713: }
714: return changeSupport.getPropertyChangeListeners();
715: }
716:
717: /**
718: * Adds a PropertyChangeListener to the listener list for a specific
719: * property. The specified property may be one of the following:
720: * <ul>
721: * <li>if this AppContext is disposed ("disposed")</li>
722: * </ul>
723: * <ul>
724: * <li>if this AppContext's unowned Windows have been disposed
725: * ("guidisposed"). Code to cleanup after the GUI is disposed
726: * (such as LookAndFeel.uninitialize()) should execute in response to
727: * this property being fired. Notifications for the "guidisposed"
728: * property are sent on the event dispatch thread.</li>
729: * </ul>
730: * <p>
731: * If listener is null, no exception is thrown and no action is performed.
732: *
733: * @param propertyName one of the property names listed above
734: * @param listener the PropertyChangeListener to be added
735: *
736: * @see #removePropertyChangeListener(java.lang.String, java.beans.PropertyChangeListener)
737: * @see #getPropertyChangeListeners(java.lang.String)
738: * @see #addPropertyChangeListener(java.lang.String, java.beans.PropertyChangeListener)
739: */
740: public synchronized void addPropertyChangeListener(
741: String propertyName, PropertyChangeListener listener) {
742: if (listener == null) {
743: return;
744: }
745: if (changeSupport == null) {
746: changeSupport = new PropertyChangeSupport(this );
747: }
748: changeSupport.addPropertyChangeListener(propertyName, listener);
749: }
750:
751: /**
752: * Removes a PropertyChangeListener from the listener list for a specific
753: * property. This method should be used to remove PropertyChangeListeners
754: * that were registered for a specific bound property.
755: * <p>
756: * If listener is null, no exception is thrown and no action is performed.
757: *
758: * @param propertyName a valid property name
759: * @param listener the PropertyChangeListener to be removed
760: *
761: * @see #addPropertyChangeListener(java.lang.String, java.beans.PropertyChangeListener)
762: * @see #getPropertyChangeListeners(java.lang.String)
763: * @see #removePropertyChangeListener(java.beans.PropertyChangeListener)
764: */
765: public synchronized void removePropertyChangeListener(
766: String propertyName, PropertyChangeListener listener) {
767: if (listener == null || changeSupport == null) {
768: return;
769: }
770: changeSupport.removePropertyChangeListener(propertyName,
771: listener);
772: }
773:
774: /**
775: * Returns an array of all the listeners which have been associated
776: * with the named property.
777: *
778: * @return all of the <code>PropertyChangeListeners</code> associated with
779: * the named property or an empty array if no listeners have
780: * been added
781: *
782: * @see #addPropertyChangeListener(java.lang.String, java.beans.PropertyChangeListener)
783: * @see #removePropertyChangeListener(java.lang.String, java.beans.PropertyChangeListener)
784: * @see #getPropertyChangeListeners
785: * @since 1.4
786: */
787: public synchronized PropertyChangeListener[] getPropertyChangeListeners(
788: String propertyName) {
789: if (changeSupport == null) {
790: return new PropertyChangeListener[0];
791: }
792: return changeSupport.getPropertyChangeListeners(propertyName);
793: }
794: }
795:
796: final class MostRecentThreadAppContext {
797: final Thread thread;
798: final AppContext appContext;
799:
800: MostRecentThreadAppContext(Thread key, AppContext value) {
801: thread = key;
802: appContext = value;
803: }
804: }
805:
806: final class MostRecentKeyValue {
807: Object key;
808: Object value;
809:
810: MostRecentKeyValue(Object k, Object v) {
811: key = k;
812: value = v;
813: }
814:
815: void setPair(Object k, Object v) {
816: key = k;
817: value = v;
818: }
819: }
|