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