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