1 /* 2 * Copyright (c) 1998, 2008, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package sun.awt; 27 28 import java.awt.EventQueue; 29 import java.awt.Window; 30 import java.awt.SystemTray; 31 import java.awt.TrayIcon; 32 import java.awt.Toolkit; 33 import java.awt.GraphicsEnvironment; 34 import java.awt.event.InvocationEvent; 35 import java.security.AccessController; 36 import java.security.PrivilegedAction; 37 import java.util.Collections; 38 import java.util.HashMap; 39 import java.util.IdentityHashMap; 40 import java.util.Map; 41 import java.util.Set; 42 import java.util.HashSet; 43 import java.beans.PropertyChangeSupport; 44 import java.beans.PropertyChangeListener; 45 import sun.util.logging.PlatformLogger; 46 import java.util.concurrent.locks.Condition; 47 import java.util.concurrent.locks.Lock; 48 import java.util.concurrent.locks.ReentrantLock; 49 50 /** 51 * The AppContext is a table referenced by ThreadGroup which stores 52 * application service instances. (If you are not writing an application 53 * service, or don't know what one is, please do not use this class.) 54 * The AppContext allows applet access to what would otherwise be 55 * potentially dangerous services, such as the ability to peek at 56 * EventQueues or change the look-and-feel of a Swing application.<p> 57 * 58 * Most application services use a singleton object to provide their 59 * services, either as a default (such as getSystemEventQueue or 60 * getDefaultToolkit) or as static methods with class data (System). 61 * The AppContext works with the former method by extending the concept 62 * of "default" to be ThreadGroup-specific. Application services 63 * lookup their singleton in the AppContext.<p> 64 * 65 * For example, here we have a Foo service, with its pre-AppContext 66 * code:<p> 67 * <code><pre> 68 * public class Foo { 69 * private static Foo defaultFoo = new Foo(); 70 * 71 * public static Foo getDefaultFoo() { 72 * return defaultFoo; 73 * } 74 * 75 * ... Foo service methods 76 * }</pre></code><p> 77 * 78 * The problem with the above is that the Foo service is global in scope, 79 * so that applets and other untrusted code can execute methods on the 80 * single, shared Foo instance. The Foo service therefore either needs 81 * to block its use by untrusted code using a SecurityManager test, or 82 * restrict its capabilities so that it doesn't matter if untrusted code 83 * executes it.<p> 84 * 85 * Here's the Foo class written to use the AppContext:<p> 86 * <code><pre> 87 * public class Foo { 88 * public static Foo getDefaultFoo() { 89 * Foo foo = (Foo)AppContext.getAppContext().get(Foo.class); 90 * if (foo == null) { 91 * foo = new Foo(); 92 * getAppContext().put(Foo.class, foo); 93 * } 94 * return foo; 95 * } 96 * 97 * ... Foo service methods 98 * }</pre></code><p> 99 * 100 * Since a separate AppContext can exist for each ThreadGroup, trusted 101 * and untrusted code have access to different Foo instances. This allows 102 * untrusted code access to "system-wide" services -- the service remains 103 * within the AppContext "sandbox". For example, say a malicious applet 104 * wants to peek all of the key events on the EventQueue to listen for 105 * passwords; if separate EventQueues are used for each ThreadGroup 106 * using AppContexts, the only key events that applet will be able to 107 * listen to are its own. A more reasonable applet request would be to 108 * change the Swing default look-and-feel; with that default stored in 109 * an AppContext, the applet's look-and-feel will change without 110 * disrupting other applets or potentially the browser itself.<p> 111 * 112 * Because the AppContext is a facility for safely extending application 113 * service support to applets, none of its methods may be blocked by a 114 * a SecurityManager check in a valid Java implementation. Applets may 115 * therefore safely invoke any of its methods without worry of being 116 * blocked. 117 * 118 * Note: If a SecurityManager is installed which derives from 119 * sun.awt.AWTSecurityManager, it may override the 120 * AWTSecurityManager.getAppContext() method to return the proper 121 * AppContext based on the execution context, in the case where 122 * the default ThreadGroup-based AppContext indexing would return 123 * the main "system" AppContext. For example, in an applet situation, 124 * if a system thread calls into an applet, rather than returning the 125 * main "system" AppContext (the one corresponding to the system thread), 126 * an installed AWTSecurityManager may return the applet's AppContext 127 * based on the execution context. 128 * 129 * @author Thomas Ball 130 * @author Fred Ecks 131 */ 132 public final class AppContext { 133 private static final PlatformLogger log = PlatformLogger.getLogger("sun.awt.AppContext"); 134 135 /* Since the contents of an AppContext are unique to each Java 136 * session, this class should never be serialized. */ 137 138 /* 139 * The key to put()/get() the Java EventQueue into/from the AppContext. 140 */ 141 public static final Object EVENT_QUEUE_KEY = new StringBuffer("EventQueue"); 142 143 /* 144 * The keys to store EventQueue push/pop lock and condition. 145 */ 146 public final static Object EVENT_QUEUE_LOCK_KEY = new StringBuilder("EventQueue.Lock"); 147 public final static Object EVENT_QUEUE_COND_KEY = new StringBuilder("EventQueue.Condition"); 148 149 /* A map of AppContexts, referenced by ThreadGroup. 150 */ 151 private static final Map<ThreadGroup, AppContext> threadGroup2appContext = 152 Collections.synchronizedMap(new IdentityHashMap<ThreadGroup, AppContext>()); 153 154 /** 155 * Returns a set containing all <code>AppContext</code>s. 156 */ 157 public static Set<AppContext> getAppContexts() { 158 synchronized (threadGroup2appContext) { 159 return new HashSet<AppContext>(threadGroup2appContext.values()); 160 } 161 } 162 163 /* The main "system" AppContext, used by everything not otherwise 164 contained in another AppContext. 165 */ 166 private static volatile AppContext mainAppContext = null; 167 168 /* 169 * The hash map associated with this AppContext. A private delegate 170 * is used instead of subclassing HashMap so as to avoid all of 171 * HashMap's potentially risky methods, such as clear(), elements(), 172 * putAll(), etc. 173 */ 174 private final Map<Object, Object> 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<Void>() { 202 public Void 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 null; 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 /** 346 * Returns the main ("system") AppContext. 347 * 348 * @return the main AppContext 349 * @since 8 350 */ 351 final static AppContext getMainAppContext() { 352 return mainAppContext; 353 } 354 355 private long DISPOSAL_TIMEOUT = 5000; // Default to 5-second timeout 356 // for disposal of all Frames 357 // (we wait for this time twice, 358 // once for dispose(), and once 359 // to clear the EventQueue). 360 361 private long THREAD_INTERRUPT_TIMEOUT = 1000; 362 // Default to 1-second timeout for all 363 // interrupted Threads to exit, and another 364 // 1 second for all stopped Threads to die. 365 366 /** 367 * Disposes of this AppContext, all of its top-level Frames, and 368 * all Threads and ThreadGroups contained within it. 369 * 370 * This method must be called from a Thread which is not contained 371 * within this AppContext. 372 * 373 * @exception IllegalThreadStateException if the current thread is 374 * contained within this AppContext 375 * @since 1.2 376 */ 377 public void dispose() throws IllegalThreadStateException { 378 // Check to be sure that the current Thread isn't in this AppContext 379 if (this.threadGroup.parentOf(Thread.currentThread().getThreadGroup())) { 380 throw new IllegalThreadStateException( 381 "Current Thread is contained within AppContext to be disposed." 382 ); 383 } 384 385 synchronized(this) { 386 if (this.isDisposed) { 387 return; // If already disposed, bail. 388 } 389 this.isDisposed = true; 390 } 391 392 final PropertyChangeSupport changeSupport = this.changeSupport; 393 if (changeSupport != null) { 394 changeSupport.firePropertyChange(DISPOSED_PROPERTY_NAME, false, true); 395 } 396 397 // First, we post an InvocationEvent to be run on the 398 // EventDispatchThread which disposes of all top-level Frames and TrayIcons 399 400 final Object notificationLock = new Object(); 401 402 Runnable runnable = new Runnable() { 403 public void run() { 404 Window[] windowsToDispose = Window.getOwnerlessWindows(); 405 for (Window w : windowsToDispose) { 406 try { 407 w.dispose(); 408 } catch (Throwable t) { 409 log.finer("exception occured while disposing app context", t); 410 } 411 } 412 AccessController.doPrivileged(new PrivilegedAction<Void>() { 413 public Void run() { 414 if (!GraphicsEnvironment.isHeadless() && SystemTray.isSupported()) 415 { 416 SystemTray systemTray = SystemTray.getSystemTray(); 417 TrayIcon[] trayIconsToDispose = systemTray.getTrayIcons(); 418 for (TrayIcon ti : trayIconsToDispose) { 419 systemTray.remove(ti); 420 } 421 } 422 return null; 423 } 424 }); 425 // Alert PropertyChangeListeners that the GUI has been disposed. 426 if (changeSupport != null) { 427 changeSupport.firePropertyChange(GUI_DISPOSED, false, true); 428 } 429 synchronized(notificationLock) { 430 notificationLock.notifyAll(); // Notify caller that we're done 431 } 432 } 433 }; 434 synchronized(notificationLock) { 435 SunToolkit.postEvent(this, 436 new InvocationEvent(Toolkit.getDefaultToolkit(), runnable)); 437 try { 438 notificationLock.wait(DISPOSAL_TIMEOUT); 439 } catch (InterruptedException e) { } 440 } 441 442 // Next, we post another InvocationEvent to the end of the 443 // EventQueue. When it's executed, we know we've executed all 444 // events in the queue. 445 446 runnable = new Runnable() { public void run() { 447 synchronized(notificationLock) { 448 notificationLock.notifyAll(); // Notify caller that we're done 449 } 450 } }; 451 synchronized(notificationLock) { 452 SunToolkit.postEvent(this, 453 new InvocationEvent(Toolkit.getDefaultToolkit(), runnable)); 454 try { 455 notificationLock.wait(DISPOSAL_TIMEOUT); 456 } catch (InterruptedException e) { } 457 } 458 459 // Next, we interrupt all Threads in the ThreadGroup 460 this.threadGroup.interrupt(); 461 // Note, the EventDispatchThread we've interrupted may dump an 462 // InterruptedException to the console here. This needs to be 463 // fixed in the EventDispatchThread, not here. 464 465 // Next, we sleep 10ms at a time, waiting for all of the active 466 // Threads in the ThreadGroup to exit. 467 468 long startTime = System.currentTimeMillis(); 469 long endTime = startTime + THREAD_INTERRUPT_TIMEOUT; 470 while ((this.threadGroup.activeCount() > 0) && 471 (System.currentTimeMillis() < endTime)) { 472 try { 473 Thread.sleep(10); 474 } catch (InterruptedException e) { } 475 } 476 477 // Then, we stop any remaining Threads 478 this.threadGroup.stop(); 479 480 // Next, we sleep 10ms at a time, waiting for all of the active 481 // Threads in the ThreadGroup to die. 482 483 startTime = System.currentTimeMillis(); 484 endTime = startTime + THREAD_INTERRUPT_TIMEOUT; 485 while ((this.threadGroup.activeCount() > 0) && 486 (System.currentTimeMillis() < endTime)) { 487 try { 488 Thread.sleep(10); 489 } catch (InterruptedException e) { } 490 } 491 492 // Next, we remove this and all subThreadGroups from threadGroup2appContext 493 int numSubGroups = this.threadGroup.activeGroupCount(); 494 if (numSubGroups > 0) { 495 ThreadGroup [] subGroups = new ThreadGroup[numSubGroups]; 496 numSubGroups = this.threadGroup.enumerate(subGroups); 497 for (int subGroup = 0; subGroup < numSubGroups; subGroup++) { 498 threadGroup2appContext.remove(subGroups[subGroup]); 499 } 500 } 501 threadGroup2appContext.remove(this.threadGroup); 502 503 threadAppContext.set(null); 504 505 // Finally, we destroy the ThreadGroup entirely. 506 try { 507 this.threadGroup.destroy(); 508 } catch (IllegalThreadStateException e) { 509 // Fired if not all the Threads died, ignore it and proceed 510 } 511 512 synchronized (table) { 513 this.table.clear(); // Clear out the Hashtable to ease garbage collection 514 } 515 516 numAppContexts--; 517 518 mostRecentKeyValue = null; 519 } 520 521 static final class PostShutdownEventRunnable implements Runnable { 522 private final AppContext appContext; 523 524 public PostShutdownEventRunnable(AppContext ac) { 525 appContext = ac; 526 } 527 528 public void run() { 529 final EventQueue eq = (EventQueue)appContext.get(EVENT_QUEUE_KEY); 530 if (eq != null) { 531 eq.postEvent(AWTAutoShutdown.getShutdownEvent()); 532 } 533 } 534 } 535 536 static final class CreateThreadAction implements PrivilegedAction<Thread> { 537 private final AppContext appContext; 538 private final Runnable runnable; 539 540 public CreateThreadAction(AppContext ac, Runnable r) { 541 appContext = ac; 542 runnable = r; 543 } 544 545 public Thread run() { 546 Thread t = new Thread(appContext.getThreadGroup(), runnable); 547 t.setContextClassLoader(appContext.getContextClassLoader()); 548 t.setPriority(Thread.NORM_PRIORITY + 1); 549 t.setDaemon(true); 550 return t; 551 } 552 } 553 554 static void stopEventDispatchThreads() { 555 for (AppContext appContext: getAppContexts()) { 556 if (appContext.isDisposed()) { 557 continue; 558 } 559 Runnable r = new PostShutdownEventRunnable(appContext); 560 // For security reasons EventQueue.postEvent should only be called 561 // on a thread that belongs to the corresponding thread group. 562 if (appContext != AppContext.getAppContext()) { 563 // Create a thread that belongs to the thread group associated 564 // with the AppContext and invokes EventQueue.postEvent. 565 PrivilegedAction<Thread> action = new CreateThreadAction(appContext, r); 566 Thread thread = AccessController.doPrivileged(action); 567 thread.start(); 568 } else { 569 r.run(); 570 } 571 } 572 } 573 574 private MostRecentKeyValue mostRecentKeyValue = null; 575 private MostRecentKeyValue shadowMostRecentKeyValue = null; 576 577 /** 578 * Returns the value to which the specified key is mapped in this context. 579 * 580 * @param key a key in the AppContext. 581 * @return the value to which the key is mapped in this AppContext; 582 * <code>null</code> if the key is not mapped to any value. 583 * @see #put(Object, Object) 584 * @since 1.2 585 */ 586 public Object get(Object key) { 587 /* 588 * The most recent reference should be updated inside a synchronized 589 * block to avoid a race when put() and get() are executed in 590 * parallel on different threads. 591 */ 592 synchronized (table) { 593 // Note: this most recent key/value caching is thread-hot. 594 // A simple test using SwingSet found that 72% of lookups 595 // were matched using the most recent key/value. By instantiating 596 // a simple MostRecentKeyValue object on cache misses, the 597 // cache hits can be processed without synchronization. 598 599 MostRecentKeyValue recent = mostRecentKeyValue; 600 if ((recent != null) && (recent.key == key)) { 601 return recent.value; 602 } 603 604 Object value = table.get(key); 605 if(mostRecentKeyValue == null) { 606 mostRecentKeyValue = new MostRecentKeyValue(key, value); 607 shadowMostRecentKeyValue = new MostRecentKeyValue(key, value); 608 } else { 609 MostRecentKeyValue auxKeyValue = mostRecentKeyValue; 610 shadowMostRecentKeyValue.setPair(key, value); 611 mostRecentKeyValue = shadowMostRecentKeyValue; 612 shadowMostRecentKeyValue = auxKeyValue; 613 } 614 return value; 615 } 616 } 617 618 /** 619 * Maps the specified <code>key</code> to the specified 620 * <code>value</code> in this AppContext. Neither the key nor the 621 * value can be <code>null</code>. 622 * <p> 623 * The value can be retrieved by calling the <code>get</code> method 624 * with a key that is equal to the original key. 625 * 626 * @param key the AppContext key. 627 * @param value the value. 628 * @return the previous value of the specified key in this 629 * AppContext, or <code>null</code> if it did not have one. 630 * @exception NullPointerException if the key or value is 631 * <code>null</code>. 632 * @see #get(Object) 633 * @since 1.2 634 */ 635 public Object put(Object key, Object value) { 636 synchronized (table) { 637 MostRecentKeyValue recent = mostRecentKeyValue; 638 if ((recent != null) && (recent.key == key)) 639 recent.value = value; 640 return table.put(key, value); 641 } 642 } 643 644 /** 645 * Removes the key (and its corresponding value) from this 646 * AppContext. This method does nothing if the key is not in the 647 * AppContext. 648 * 649 * @param key the key that needs to be removed. 650 * @return the value to which the key had been mapped in this AppContext, 651 * or <code>null</code> if the key did not have a mapping. 652 * @since 1.2 653 */ 654 public Object remove(Object key) { 655 synchronized (table) { 656 MostRecentKeyValue recent = mostRecentKeyValue; 657 if ((recent != null) && (recent.key == key)) 658 recent.value = null; 659 return table.remove(key); 660 } 661 } 662 663 /** 664 * Returns the root ThreadGroup for all Threads contained within 665 * this AppContext. 666 * @since 1.2 667 */ 668 public ThreadGroup getThreadGroup() { 669 return threadGroup; 670 } 671 672 /** 673 * Returns the context ClassLoader that was used to create this 674 * AppContext. 675 * 676 * @see java.lang.Thread#getContextClassLoader 677 */ 678 public ClassLoader getContextClassLoader() { 679 return contextClassLoader; 680 } 681 682 /** 683 * Returns a string representation of this AppContext. 684 * @since 1.2 685 */ 686 @Override 687 public String toString() { 688 return getClass().getName() + "[threadGroup=" + threadGroup.getName() + "]"; 689 } 690 691 /** 692 * Returns an array of all the property change listeners 693 * registered on this component. 694 * 695 * @return all of this component's <code>PropertyChangeListener</code>s 696 * or an empty array if no property change 697 * listeners are currently registered 698 * 699 * @see #addPropertyChangeListener 700 * @see #removePropertyChangeListener 701 * @see #getPropertyChangeListeners(java.lang.String) 702 * @see java.beans.PropertyChangeSupport#getPropertyChangeListeners 703 * @since 1.4 704 */ 705 public synchronized PropertyChangeListener[] getPropertyChangeListeners() { 706 if (changeSupport == null) { 707 return new PropertyChangeListener[0]; 708 } 709 return changeSupport.getPropertyChangeListeners(); 710 } 711 712 /** 713 * Adds a PropertyChangeListener to the listener list for a specific 714 * property. The specified property may be one of the following: 715 * <ul> 716 * <li>if this AppContext is disposed ("disposed")</li> 717 * </ul> 718 * <ul> 719 * <li>if this AppContext's unowned Windows have been disposed 720 * ("guidisposed"). Code to cleanup after the GUI is disposed 721 * (such as LookAndFeel.uninitialize()) should execute in response to 722 * this property being fired. Notifications for the "guidisposed" 723 * property are sent on the event dispatch thread.</li> 724 * </ul> 725 * <p> 726 * If listener is null, no exception is thrown and no action is performed. 727 * 728 * @param propertyName one of the property names listed above 729 * @param listener the PropertyChangeListener to be added 730 * 731 * @see #removePropertyChangeListener(java.lang.String, java.beans.PropertyChangeListener) 732 * @see #getPropertyChangeListeners(java.lang.String) 733 * @see #addPropertyChangeListener(java.lang.String, java.beans.PropertyChangeListener) 734 */ 735 public synchronized void addPropertyChangeListener( 736 String propertyName, 737 PropertyChangeListener listener) { 738 if (listener == null) { 739 return; 740 } 741 if (changeSupport == null) { 742 changeSupport = new PropertyChangeSupport(this); 743 } 744 changeSupport.addPropertyChangeListener(propertyName, listener); 745 } 746 747 /** 748 * Removes a PropertyChangeListener from the listener list for a specific 749 * property. This method should be used to remove PropertyChangeListeners 750 * that were registered for a specific bound property. 751 * <p> 752 * If listener is null, no exception is thrown and no action is performed. 753 * 754 * @param propertyName a valid property name 755 * @param listener the PropertyChangeListener to be removed 756 * 757 * @see #addPropertyChangeListener(java.lang.String, java.beans.PropertyChangeListener) 758 * @see #getPropertyChangeListeners(java.lang.String) 759 * @see #removePropertyChangeListener(java.beans.PropertyChangeListener) 760 */ 761 public synchronized void removePropertyChangeListener( 762 String propertyName, 763 PropertyChangeListener listener) { 764 if (listener == null || changeSupport == null) { 765 return; 766 } 767 changeSupport.removePropertyChangeListener(propertyName, listener); 768 } 769 770 /** 771 * Returns an array of all the listeners which have been associated 772 * with the named property. 773 * 774 * @return all of the <code>PropertyChangeListeners</code> associated with 775 * the named property or an empty array if no listeners have 776 * been added 777 * 778 * @see #addPropertyChangeListener(java.lang.String, java.beans.PropertyChangeListener) 779 * @see #removePropertyChangeListener(java.lang.String, java.beans.PropertyChangeListener) 780 * @see #getPropertyChangeListeners 781 * @since 1.4 782 */ 783 public synchronized PropertyChangeListener[] getPropertyChangeListeners( 784 String propertyName) { 785 if (changeSupport == null) { 786 return new PropertyChangeListener[0]; 787 } 788 return changeSupport.getPropertyChangeListeners(propertyName); 789 } 790 } 791 792 final class MostRecentKeyValue { 793 Object key; 794 Object value; 795 MostRecentKeyValue(Object k, Object v) { 796 key = k; 797 value = v; 798 } 799 void setPair(Object k, Object v) { 800 key = k; 801 value = v; 802 } 803 }