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
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;
45import sun.util.logging.PlatformLogger;
46import java.util.concurrent.locks.Condition;
47import java.util.concurrent.locks.Lock;
48import 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 */
132public 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
782final 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}