1 /* 2 * Copyright (c) 1998, 2013, 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. It is implicitly created for 166 standalone apps only (i.e. not applets) 167 */ 168 private static volatile AppContext mainAppContext = null; 169 170 /* 171 * The hash map associated with this AppContext. A private delegate 172 * is used instead of subclassing HashMap so as to avoid all of 173 * HashMap's potentially risky methods, such as clear(), elements(), 174 * putAll(), etc. 175 */ 176 private final HashMap table = new HashMap(); 177 178 private final ThreadGroup threadGroup; 179 180 /** 181 * If any <code>PropertyChangeListeners</code> have been registered, 182 * the <code>changeSupport</code> field describes them. 183 * 184 * @see #addPropertyChangeListener 185 * @see #removePropertyChangeListener 186 * @see #firePropertyChange 187 */ 188 private PropertyChangeSupport changeSupport = null; 189 190 public static final String DISPOSED_PROPERTY_NAME = "disposed"; 191 public static final String GUI_DISPOSED = "guidisposed"; 192 193 private enum State { 194 VALID, 195 BEING_DISPOSED, 196 DISPOSED 197 }; 198 199 private volatile State state = State.VALID; 200 201 public boolean isDisposed() { 202 return state == State.DISPOSED; 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 final AtomicInteger numAppContexts = new AtomicInteger(0); 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.incrementAndGet(); 234 235 this.threadGroup = threadGroup; 236 threadGroup2appContext.put(threadGroup, this); 237 238 this.contextClassLoader = 239 AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() { 240 public ClassLoader run() { 241 return Thread.currentThread().getContextClassLoader(); 242 } 243 }); 244 245 // Initialize push/pop lock and its condition to be used by all the 246 // EventQueues within this AppContext 247 Lock eventQueuePushPopLock = new ReentrantLock(); 248 put(EVENT_QUEUE_LOCK_KEY, eventQueuePushPopLock); 249 Condition eventQueuePushPopCond = eventQueuePushPopLock.newCondition(); 250 put(EVENT_QUEUE_COND_KEY, eventQueuePushPopCond); 251 } 252 253 private static final ThreadLocal<AppContext> threadAppContext = 254 new ThreadLocal<AppContext>(); 255 256 private final static void initMainAppContext() { 257 // On the main Thread, we get the ThreadGroup, make a corresponding 258 // AppContext, and instantiate the Java EventQueue. This way, legacy 259 // code is unaffected by the move to multiple AppContext ability. 260 AccessController.doPrivileged(new PrivilegedAction<Void>() { 261 public Void run() { 262 ThreadGroup currentThreadGroup = 263 Thread.currentThread().getThreadGroup(); 264 ThreadGroup parentThreadGroup = currentThreadGroup.getParent(); 265 while (parentThreadGroup != null) { 266 // Find the root ThreadGroup to construct our main AppContext 267 currentThreadGroup = parentThreadGroup; 268 parentThreadGroup = currentThreadGroup.getParent(); 269 } 270 271 mainAppContext = SunToolkit.createNewAppContext(currentThreadGroup); 272 return null; 273 } 274 }); 275 } 276 277 /** 278 * Returns the appropriate AppContext for the caller, 279 * as determined by its ThreadGroup. If the main "system" AppContext 280 * would be returned and there's an AWTSecurityManager installed, it 281 * is called to get the proper AppContext based on the execution 282 * context. 283 * 284 * @return the AppContext for the caller. 285 * @see java.lang.ThreadGroup 286 * @since 1.2 287 */ 288 public final static AppContext getAppContext() { 289 // we are standalone app, return the main app context 290 if (numAppContexts.get() == 1 && mainAppContext != null) { 291 return mainAppContext; 292 } 293 294 AppContext appContext = threadAppContext.get(); 295 296 if (null == appContext) { 297 appContext = AccessController.doPrivileged(new PrivilegedAction<AppContext>() 298 { 299 public AppContext run() { 300 // Get the current ThreadGroup, and look for it and its 301 // parents in the hash from ThreadGroup to AppContext -- 302 // it should be found, because we use createNewContext() 303 // when new AppContext objects are created. 304 ThreadGroup currentThreadGroup = Thread.currentThread().getThreadGroup(); 305 ThreadGroup threadGroup = currentThreadGroup; 306 307 // Special case: we implicitly create the main app context 308 // if no contexts have been created yet. This covers standalone apps 309 // and excludes applets because by the time applet starts 310 // a number of contexts have already been created by the plugin. 311 if (numAppContexts.get() == 0) { 312 if (System.getProperty("javaplugin.version") == null && 313 System.getProperty("javawebstart.version") == null) { 314 initMainAppContext(); 315 } else if (System.getProperty("javafx.version") != null && 316 threadGroup.getParent() != null) { 317 // Swing inside JavaFX case 318 SunToolkit.createNewAppContext(); 319 } 320 } 321 322 AppContext context = threadGroup2appContext.get(threadGroup); 323 while (context == null) { 324 threadGroup = threadGroup.getParent(); 325 if (threadGroup == null) { 326 return null; 327 } 328 context = threadGroup2appContext.get(threadGroup); 329 } 330 331 // In case we did anything in the above while loop, we add 332 // all the intermediate ThreadGroups to threadGroup2appContext 333 // so we won't spin again. 334 for (ThreadGroup tg = currentThreadGroup; tg != threadGroup; tg = tg.getParent()) { 335 threadGroup2appContext.put(tg, context); 336 } 337 338 // Now we're done, so we cache the latest key/value pair. 339 threadAppContext.set(context); 340 341 return context; 342 } 343 }); 344 } 345 346 return appContext; 347 } 348 349 /** 350 * Returns true if the specified AppContext is the main AppContext. 351 * 352 * @param ctx the context to compare with the main context 353 * @return true if the specified AppContext is the main AppContext. 354 * @since 1.8 355 */ 356 public final static boolean isMainContext(AppContext ctx) { 357 return (ctx != null && ctx == mainAppContext); 358 } 359 360 private final static AppContext getExecutionAppContext() { 361 SecurityManager securityManager = System.getSecurityManager(); 362 if ((securityManager != null) && 363 (securityManager instanceof AWTSecurityManager)) 364 { 365 AWTSecurityManager awtSecMgr = (AWTSecurityManager) securityManager; 366 AppContext secAppContext = awtSecMgr.getAppContext(); 367 return secAppContext; // Return what we're told 368 } 369 return null; 370 } 371 372 private long DISPOSAL_TIMEOUT = 5000; // Default to 5-second timeout 373 // for disposal of all Frames 374 // (we wait for this time twice, 375 // once for dispose(), and once 376 // to clear the EventQueue). 377 378 private long THREAD_INTERRUPT_TIMEOUT = 1000; 379 // Default to 1-second timeout for all 380 // interrupted Threads to exit, and another 381 // 1 second for all stopped Threads to die. 382 383 /** 384 * Disposes of this AppContext, all of its top-level Frames, and 385 * all Threads and ThreadGroups contained within it. 386 * 387 * This method must be called from a Thread which is not contained 388 * within this AppContext. 389 * 390 * @exception IllegalThreadStateException if the current thread is 391 * contained within this AppContext 392 * @since 1.2 393 */ 394 public void dispose() throws IllegalThreadStateException { 395 // Check to be sure that the current Thread isn't in this AppContext 396 if (this.threadGroup.parentOf(Thread.currentThread().getThreadGroup())) { 397 throw new IllegalThreadStateException( 398 "Current Thread is contained within AppContext to be disposed." 399 ); 400 } 401 402 synchronized(this) { 403 if (this.state != State.VALID) { 404 return; // If already disposed or being disposed, bail. 405 } 406 407 this.state = State.BEING_DISPOSED; 408 } 409 410 final PropertyChangeSupport changeSupport = this.changeSupport; 411 if (changeSupport != null) { 412 changeSupport.firePropertyChange(DISPOSED_PROPERTY_NAME, false, true); 413 } 414 415 // First, we post an InvocationEvent to be run on the 416 // EventDispatchThread which disposes of all top-level Frames and TrayIcons 417 418 final Object notificationLock = new Object(); 419 420 Runnable runnable = new Runnable() { 421 public void run() { 422 Window[] windowsToDispose = Window.getOwnerlessWindows(); 423 for (Window w : windowsToDispose) { 424 try { 425 w.dispose(); 426 } catch (Throwable t) { 427 log.finer("exception occured while disposing app context", t); 428 } 429 } 430 AccessController.doPrivileged(new PrivilegedAction() { 431 public Object run() { 432 if (!GraphicsEnvironment.isHeadless() && SystemTray.isSupported()) 433 { 434 SystemTray systemTray = SystemTray.getSystemTray(); 435 TrayIcon[] trayIconsToDispose = systemTray.getTrayIcons(); 436 for (TrayIcon ti : trayIconsToDispose) { 437 systemTray.remove(ti); 438 } 439 } 440 return null; 441 } 442 }); 443 // Alert PropertyChangeListeners that the GUI has been disposed. 444 if (changeSupport != null) { 445 changeSupport.firePropertyChange(GUI_DISPOSED, false, true); 446 } 447 synchronized(notificationLock) { 448 notificationLock.notifyAll(); // Notify caller that we're done 449 } 450 } 451 }; 452 synchronized(notificationLock) { 453 SunToolkit.postEvent(this, 454 new InvocationEvent(Toolkit.getDefaultToolkit(), runnable)); 455 try { 456 notificationLock.wait(DISPOSAL_TIMEOUT); 457 } catch (InterruptedException e) { } 458 } 459 460 // Next, we post another InvocationEvent to the end of the 461 // EventQueue. When it's executed, we know we've executed all 462 // events in the queue. 463 464 runnable = new Runnable() { public void run() { 465 synchronized(notificationLock) { 466 notificationLock.notifyAll(); // Notify caller that we're done 467 } 468 } }; 469 synchronized(notificationLock) { 470 SunToolkit.postEvent(this, 471 new InvocationEvent(Toolkit.getDefaultToolkit(), runnable)); 472 try { 473 notificationLock.wait(DISPOSAL_TIMEOUT); 474 } catch (InterruptedException e) { } 475 } 476 477 // We are done with posting events, so change the state to disposed 478 synchronized(this) { 479 this.state = State.DISPOSED; 480 } 481 482 // Next, we interrupt all Threads in the ThreadGroup 483 this.threadGroup.interrupt(); 484 // Note, the EventDispatchThread we've interrupted may dump an 485 // InterruptedException to the console here. This needs to be 486 // fixed in the EventDispatchThread, not here. 487 488 // Next, we sleep 10ms at a time, waiting for all of the active 489 // Threads in the ThreadGroup to exit. 490 491 long startTime = System.currentTimeMillis(); 492 long endTime = startTime + THREAD_INTERRUPT_TIMEOUT; 493 while ((this.threadGroup.activeCount() > 0) && 494 (System.currentTimeMillis() < endTime)) { 495 try { 496 Thread.sleep(10); 497 } catch (InterruptedException e) { } 498 } 499 500 // Then, we stop any remaining Threads 501 this.threadGroup.stop(); 502 503 // Next, we sleep 10ms at a time, waiting for all of the active 504 // Threads in the ThreadGroup to die. 505 506 startTime = System.currentTimeMillis(); 507 endTime = startTime + THREAD_INTERRUPT_TIMEOUT; 508 while ((this.threadGroup.activeCount() > 0) && 509 (System.currentTimeMillis() < endTime)) { 510 try { 511 Thread.sleep(10); 512 } catch (InterruptedException e) { } 513 } 514 515 // Next, we remove this and all subThreadGroups from threadGroup2appContext 516 int numSubGroups = this.threadGroup.activeGroupCount(); 517 if (numSubGroups > 0) { 518 ThreadGroup [] subGroups = new ThreadGroup[numSubGroups]; 519 numSubGroups = this.threadGroup.enumerate(subGroups); 520 for (int subGroup = 0; subGroup < numSubGroups; subGroup++) { 521 threadGroup2appContext.remove(subGroups[subGroup]); 522 } 523 } 524 threadGroup2appContext.remove(this.threadGroup); 525 526 threadAppContext.set(null); 527 528 // Finally, we destroy the ThreadGroup entirely. 529 try { 530 this.threadGroup.destroy(); 531 } catch (IllegalThreadStateException e) { 532 // Fired if not all the Threads died, ignore it and proceed 533 } 534 535 synchronized (table) { 536 this.table.clear(); // Clear out the Hashtable to ease garbage collection 537 } 538 539 numAppContexts.decrementAndGet(); 540 541 mostRecentKeyValue = null; 542 } 543 544 static final class PostShutdownEventRunnable implements Runnable { 545 private final AppContext appContext; 546 547 public PostShutdownEventRunnable(AppContext ac) { 548 appContext = ac; 549 } 550 551 public void run() { 552 final EventQueue eq = (EventQueue)appContext.get(EVENT_QUEUE_KEY); 553 if (eq != null) { 554 eq.postEvent(AWTAutoShutdown.getShutdownEvent()); 555 } 556 } 557 } 558 559 static final class CreateThreadAction implements PrivilegedAction { 560 private final AppContext appContext; 561 private final Runnable runnable; 562 563 public CreateThreadAction(AppContext ac, Runnable r) { 564 appContext = ac; 565 runnable = r; 566 } 567 568 public Object run() { 569 Thread t = new Thread(appContext.getThreadGroup(), runnable); 570 t.setContextClassLoader(appContext.getContextClassLoader()); 571 t.setPriority(Thread.NORM_PRIORITY + 1); 572 t.setDaemon(true); 573 return t; 574 } 575 } 576 577 static void stopEventDispatchThreads() { 578 for (AppContext appContext: getAppContexts()) { 579 if (appContext.isDisposed()) { 580 continue; 581 } 582 Runnable r = new PostShutdownEventRunnable(appContext); 583 // For security reasons EventQueue.postEvent should only be called 584 // on a thread that belongs to the corresponding thread group. 585 if (appContext != AppContext.getAppContext()) { 586 // Create a thread that belongs to the thread group associated 587 // with the AppContext and invokes EventQueue.postEvent. 588 PrivilegedAction action = new CreateThreadAction(appContext, r); 589 Thread thread = (Thread)AccessController.doPrivileged(action); 590 thread.start(); 591 } else { 592 r.run(); 593 } 594 } 595 } 596 597 private MostRecentKeyValue mostRecentKeyValue = null; 598 private MostRecentKeyValue shadowMostRecentKeyValue = null; 599 600 /** 601 * Returns the value to which the specified key is mapped in this context. 602 * 603 * @param key a key in the AppContext. 604 * @return the value to which the key is mapped in this AppContext; 605 * <code>null</code> if the key is not mapped to any value. 606 * @see #put(Object, Object) 607 * @since 1.2 608 */ 609 public Object get(Object key) { 610 /* 611 * The most recent reference should be updated inside a synchronized 612 * block to avoid a race when put() and get() are executed in 613 * parallel on different threads. 614 */ 615 synchronized (table) { 616 // Note: this most recent key/value caching is thread-hot. 617 // A simple test using SwingSet found that 72% of lookups 618 // were matched using the most recent key/value. By instantiating 619 // a simple MostRecentKeyValue object on cache misses, the 620 // cache hits can be processed without synchronization. 621 622 MostRecentKeyValue recent = mostRecentKeyValue; 623 if ((recent != null) && (recent.key == key)) { 624 return recent.value; 625 } 626 627 Object value = table.get(key); 628 if(mostRecentKeyValue == null) { 629 mostRecentKeyValue = new MostRecentKeyValue(key, value); 630 shadowMostRecentKeyValue = new MostRecentKeyValue(key, value); 631 } else { 632 MostRecentKeyValue auxKeyValue = mostRecentKeyValue; 633 shadowMostRecentKeyValue.setPair(key, value); 634 mostRecentKeyValue = shadowMostRecentKeyValue; 635 shadowMostRecentKeyValue = auxKeyValue; 636 } 637 return value; 638 } 639 } 640 641 /** 642 * Maps the specified <code>key</code> to the specified 643 * <code>value</code> in this AppContext. Neither the key nor the 644 * value can be <code>null</code>. 645 * <p> 646 * The value can be retrieved by calling the <code>get</code> method 647 * with a key that is equal to the original key. 648 * 649 * @param key the AppContext key. 650 * @param value the value. 651 * @return the previous value of the specified key in this 652 * AppContext, or <code>null</code> if it did not have one. 653 * @exception NullPointerException if the key or value is 654 * <code>null</code>. 655 * @see #get(Object) 656 * @since 1.2 657 */ 658 public Object put(Object key, Object value) { 659 synchronized (table) { 660 MostRecentKeyValue recent = mostRecentKeyValue; 661 if ((recent != null) && (recent.key == key)) 662 recent.value = value; 663 return table.put(key, value); 664 } 665 } 666 667 /** 668 * Removes the key (and its corresponding value) from this 669 * AppContext. This method does nothing if the key is not in the 670 * AppContext. 671 * 672 * @param key the key that needs to be removed. 673 * @return the value to which the key had been mapped in this AppContext, 674 * or <code>null</code> if the key did not have a mapping. 675 * @since 1.2 676 */ 677 public Object remove(Object key) { 678 synchronized (table) { 679 MostRecentKeyValue recent = mostRecentKeyValue; 680 if ((recent != null) && (recent.key == key)) 681 recent.value = null; 682 return table.remove(key); 683 } 684 } 685 686 /** 687 * Returns the root ThreadGroup for all Threads contained within 688 * this AppContext. 689 * @since 1.2 690 */ 691 public ThreadGroup getThreadGroup() { 692 return threadGroup; 693 } 694 695 /** 696 * Returns the context ClassLoader that was used to create this 697 * AppContext. 698 * 699 * @see java.lang.Thread#getContextClassLoader 700 */ 701 public ClassLoader getContextClassLoader() { 702 return contextClassLoader; 703 } 704 705 /** 706 * Returns a string representation of this AppContext. 707 * @since 1.2 708 */ 709 @Override 710 public String toString() { 711 return getClass().getName() + "[threadGroup=" + threadGroup.getName() + "]"; 712 } 713 714 /** 715 * Returns an array of all the property change listeners 716 * registered on this component. 717 * 718 * @return all of this component's <code>PropertyChangeListener</code>s 719 * or an empty array if no property change 720 * listeners are currently registered 721 * 722 * @see #addPropertyChangeListener 723 * @see #removePropertyChangeListener 724 * @see #getPropertyChangeListeners(java.lang.String) 725 * @see java.beans.PropertyChangeSupport#getPropertyChangeListeners 726 * @since 1.4 727 */ 728 public synchronized PropertyChangeListener[] getPropertyChangeListeners() { 729 if (changeSupport == null) { 730 return new PropertyChangeListener[0]; 731 } 732 return changeSupport.getPropertyChangeListeners(); 733 } 734 735 /** 736 * Adds a PropertyChangeListener to the listener list for a specific 737 * property. The specified property may be one of the following: 738 * <ul> 739 * <li>if this AppContext is disposed ("disposed")</li> 740 * </ul> 741 * <ul> 742 * <li>if this AppContext's unowned Windows have been disposed 743 * ("guidisposed"). Code to cleanup after the GUI is disposed 744 * (such as LookAndFeel.uninitialize()) should execute in response to 745 * this property being fired. Notifications for the "guidisposed" 746 * property are sent on the event dispatch thread.</li> 747 * </ul> 748 * <p> 749 * If listener is null, no exception is thrown and no action is performed. 750 * 751 * @param propertyName one of the property names listed above 752 * @param listener the PropertyChangeListener to be added 753 * 754 * @see #removePropertyChangeListener(java.lang.String, java.beans.PropertyChangeListener) 755 * @see #getPropertyChangeListeners(java.lang.String) 756 * @see #addPropertyChangeListener(java.lang.String, java.beans.PropertyChangeListener) 757 */ 758 public synchronized void addPropertyChangeListener( 759 String propertyName, 760 PropertyChangeListener listener) { 761 if (listener == null) { 762 return; 763 } 764 if (changeSupport == null) { 765 changeSupport = new PropertyChangeSupport(this); 766 } 767 changeSupport.addPropertyChangeListener(propertyName, listener); 768 } 769 770 /** 771 * Removes a PropertyChangeListener from the listener list for a specific 772 * property. This method should be used to remove PropertyChangeListeners 773 * that were registered for a specific bound property. 774 * <p> 775 * If listener is null, no exception is thrown and no action is performed. 776 * 777 * @param propertyName a valid property name 778 * @param listener the PropertyChangeListener to be removed 779 * 780 * @see #addPropertyChangeListener(java.lang.String, java.beans.PropertyChangeListener) 781 * @see #getPropertyChangeListeners(java.lang.String) 782 * @see #removePropertyChangeListener(java.beans.PropertyChangeListener) 783 */ 784 public synchronized void removePropertyChangeListener( 785 String propertyName, 786 PropertyChangeListener listener) { 787 if (listener == null || changeSupport == null) { 788 return; 789 } 790 changeSupport.removePropertyChangeListener(propertyName, listener); 791 } 792 793 /** 794 * Returns an array of all the listeners which have been associated 795 * with the named property. 796 * 797 * @return all of the <code>PropertyChangeListeners</code> associated with 798 * the named property or an empty array if no listeners have 799 * been added 800 * 801 * @see #addPropertyChangeListener(java.lang.String, java.beans.PropertyChangeListener) 802 * @see #removePropertyChangeListener(java.lang.String, java.beans.PropertyChangeListener) 803 * @see #getPropertyChangeListeners 804 * @since 1.4 805 */ 806 public synchronized PropertyChangeListener[] getPropertyChangeListeners( 807 String propertyName) { 808 if (changeSupport == null) { 809 return new PropertyChangeListener[0]; 810 } 811 return changeSupport.getPropertyChangeListeners(propertyName); 812 } 813 814 // Set up JavaAWTAccess in SharedSecrets 815 static { 816 sun.misc.SharedSecrets.setJavaAWTAccess(new sun.misc.JavaAWTAccess() { 817 public Object get(Object key) { 818 AppContext ac = getAppContext(); 819 return (ac == null) ? null : ac.get(key); 820 } 821 public void put(Object key, Object value) { 822 AppContext ac = getAppContext(); 823 if (ac != null) { 824 ac.put(key, value); 825 } 826 } 827 public void remove(Object key) { 828 AppContext ac = getAppContext(); 829 if (ac != null) { 830 ac.remove(key); 831 } 832 } 833 public boolean isDisposed() { 834 AppContext ac = getAppContext(); 835 return (ac == null) ? true : ac.isDisposed(); 836 } 837 public boolean isMainAppContext() { 838 return (numAppContexts.get() == 1 && mainAppContext != null); 839 } 840 public Object getContext() { 841 return getAppContext(); 842 } 843 public Object getExecutionContext() { 844 return getExecutionAppContext(); 845 } 846 public Object get(Object context, Object key) { 847 return ((AppContext)context).get(key); 848 } 849 public void put(Object context, Object key, Object value) { 850 ((AppContext)context).put(key, value); 851 } 852 public void remove(Object context, Object key) { 853 ((AppContext)context).remove(key); 854 } 855 }); 856 } 857 } 858 859 final class MostRecentKeyValue { 860 Object key; 861 Object value; 862 MostRecentKeyValue(Object k, Object v) { 863 key = k; 864 value = v; 865 } 866 void setPair(Object k, Object v) { 867 key = k; 868 value = v; 869 } 870 }