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 import java.util.concurrent.atomic.AtomicInteger; 50 51 /** 52 * The AppContext is a table referenced by ThreadGroup which stores 53 * application service instances. (If you are not writing an application 54 * service, or don't know what one is, please do not use this class.) 55 * The AppContext allows applet access to what would otherwise be 56 * potentially dangerous services, such as the ability to peek at 57 * EventQueues or change the look-and-feel of a Swing application.<p> 58 * 59 * Most application services use a singleton object to provide their 60 * services, either as a default (such as getSystemEventQueue or 61 * getDefaultToolkit) or as static methods with class data (System). 62 * The AppContext works with the former method by extending the concept 63 * of "default" to be ThreadGroup-specific. Application services 64 * lookup their singleton in the AppContext.<p> 65 * 66 * For example, here we have a Foo service, with its pre-AppContext 67 * code:<p> 68 * <code><pre> 69 * public class Foo { 70 * private static Foo defaultFoo = new Foo(); 71 * 72 * public static Foo getDefaultFoo() { 73 * return defaultFoo; 74 * } 75 * 76 * ... Foo service methods 77 * }</pre></code><p> 78 * 79 * The problem with the above is that the Foo service is global in scope, 80 * so that applets and other untrusted code can execute methods on the 81 * single, shared Foo instance. The Foo service therefore either needs 82 * to block its use by untrusted code using a SecurityManager test, or 83 * restrict its capabilities so that it doesn't matter if untrusted code 84 * executes it.<p> 85 * 86 * Here's the Foo class written to use the AppContext:<p> 87 * <code><pre> 88 * public class Foo { 89 * public static Foo getDefaultFoo() { 90 * Foo foo = (Foo)AppContext.getAppContext().get(Foo.class); 91 * if (foo == null) { 92 * foo = new Foo(); 93 * getAppContext().put(Foo.class, foo); 94 * } 95 * return foo; 96 * } 97 * 98 * ... Foo service methods 99 * }</pre></code><p> 100 * 101 * Since a separate AppContext can exist for each ThreadGroup, trusted 102 * and untrusted code have access to different Foo instances. This allows 103 * untrusted code access to "system-wide" services -- the service remains 104 * within the AppContext "sandbox". For example, say a malicious applet 105 * wants to peek all of the key events on the EventQueue to listen for 106 * passwords; if separate EventQueues are used for each ThreadGroup 107 * using AppContexts, the only key events that applet will be able to 108 * listen to are its own. A more reasonable applet request would be to 109 * change the Swing default look-and-feel; with that default stored in 110 * an AppContext, the applet's look-and-feel will change without 111 * disrupting other applets or potentially the browser itself.<p> 112 * 113 * Because the AppContext is a facility for safely extending application 114 * service support to applets, none of its methods may be blocked by a 115 * a SecurityManager check in a valid Java implementation. Applets may 116 * therefore safely invoke any of its methods without worry of being 117 * blocked. 118 * 119 * Note: If a SecurityManager is installed which derives from 120 * sun.awt.AWTSecurityManager, it may override the 121 * AWTSecurityManager.getAppContext() method to return the proper 122 * AppContext based on the execution context, in the case where 123 * the default ThreadGroup-based AppContext indexing would return 124 * the main "system" AppContext. For example, in an applet situation, 125 * if a system thread calls into an applet, rather than returning the 126 * main "system" AppContext (the one corresponding to the system thread), 127 * an installed AWTSecurityManager may return the applet's AppContext 128 * based on the execution context. 129 * 130 * @author Thomas Ball 131 * @author Fred Ecks 132 */ 133 public final class AppContext { 134 private static final PlatformLogger log = PlatformLogger.getLogger("sun.awt.AppContext"); 135 136 /* Since the contents of an AppContext are unique to each Java 137 * session, this class should never be serialized. */ 138 139 /* 140 * The key to put()/get() the Java EventQueue into/from the AppContext. 141 */ 142 public static final Object EVENT_QUEUE_KEY = new StringBuffer("EventQueue"); 143 144 /* 145 * The keys to store EventQueue push/pop lock and condition. 146 */ 147 public final static Object EVENT_QUEUE_LOCK_KEY = new StringBuilder("EventQueue.Lock"); 148 public final static Object EVENT_QUEUE_COND_KEY = new StringBuilder("EventQueue.Condition"); 149 150 /* A map of AppContexts, referenced by ThreadGroup. 151 */ 152 private static final Map<ThreadGroup, AppContext> threadGroup2appContext = 153 Collections.synchronizedMap(new IdentityHashMap<ThreadGroup, AppContext>()); 154 155 /** 156 * Returns a set containing all <code>AppContext</code>s. 157 */ 158 public static Set<AppContext> getAppContexts() { 159 synchronized (threadGroup2appContext) { 160 return new HashSet<AppContext>(threadGroup2appContext.values()); 161 } 162 } 163 164 /* The main "system" AppContext, used by everything not otherwise 165 contained in another AppContext. 166 */ 167 private static volatile AppContext mainAppContext = null; 168 169 /* 170 * The hash map associated with this AppContext. A private delegate 171 * is used instead of subclassing HashMap so as to avoid all of 172 * HashMap's potentially risky methods, such as clear(), elements(), 173 * putAll(), etc. 174 */ 175 private final HashMap table = new HashMap(); 176 177 private final ThreadGroup threadGroup; 178 179 /** 180 * If any <code>PropertyChangeListeners</code> have been registered, 181 * the <code>changeSupport</code> field describes them. 182 * 183 * @see #addPropertyChangeListener 184 * @see #removePropertyChangeListener 185 * @see #firePropertyChange 186 */ 187 private PropertyChangeSupport changeSupport = null; 188 189 public static final String DISPOSED_PROPERTY_NAME = "disposed"; 190 public static final String GUI_DISPOSED = "guidisposed"; 191 192 private volatile boolean isDisposed = false; // true if AppContext is disposed 193 194 public boolean isDisposed() { 195 return isDisposed; 196 } 197 198 /* 199 * The total number of AppContexts, system-wide. This number is 200 * incremented at the beginning of the constructor, and decremented 201 * at the end of dispose(). getAppContext() checks to see if this 202 * number is 1. If so, it returns the sole AppContext without 203 * checking Thread.currentThread(). 204 */ 205 private static final AtomicInteger numAppContexts = new AtomicInteger(0); 206 207 static { 208 // On the main Thread, we get the ThreadGroup, make a corresponding 209 // AppContext, and instantiate the Java EventQueue. This way, legacy 210 // code is unaffected by the move to multiple AppContext ability. 211 AccessController.doPrivileged(new PrivilegedAction() { 212 public Object run() { 213 ThreadGroup currentThreadGroup = 214 Thread.currentThread().getThreadGroup(); 215 ThreadGroup parentThreadGroup = currentThreadGroup.getParent(); 216 while (parentThreadGroup != null) { 217 // Find the root ThreadGroup to construct our main AppContext 218 currentThreadGroup = parentThreadGroup; 219 parentThreadGroup = currentThreadGroup.getParent(); 220 } 221 mainAppContext = new AppContext(currentThreadGroup); 222 return mainAppContext; 223 } 224 }); 225 } 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.incrementAndGet(); 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.get() == 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.decrementAndGet(); 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 782 final 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 }