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 java.lang.ref.SoftReference; 46 import sun.util.logging.PlatformLogger; 47 import java.util.concurrent.locks.Condition; 48 import java.util.concurrent.locks.Lock; 49 import java.util.concurrent.locks.ReentrantLock; 50 import java.util.concurrent.atomic.AtomicInteger; 51 import java.util.function.Supplier; 52 53 /** 54 * The AppContext is a table referenced by ThreadGroup which stores 55 * application service instances. (If you are not writing an application 56 * service, or don't know what one is, please do not use this class.) 57 * The AppContext allows applet access to what would otherwise be 58 * potentially dangerous services, such as the ability to peek at 59 * EventQueues or change the look-and-feel of a Swing application.<p> 60 * 61 * Most application services use a singleton object to provide their 62 * services, either as a default (such as getSystemEventQueue or 63 * getDefaultToolkit) or as static methods with class data (System). 64 * The AppContext works with the former method by extending the concept 65 * of "default" to be ThreadGroup-specific. Application services 66 * lookup their singleton in the AppContext.<p> 67 * 68 * For example, here we have a Foo service, with its pre-AppContext 69 * code:<p> 70 * <code><pre> 71 * public class Foo { 72 * private static Foo defaultFoo = new Foo(); 73 * 74 * public static Foo getDefaultFoo() { 75 * return defaultFoo; 76 * } 77 * 78 * ... Foo service methods 79 * }</pre></code><p> 80 * 81 * The problem with the above is that the Foo service is global in scope, 82 * so that applets and other untrusted code can execute methods on the 83 * single, shared Foo instance. The Foo service therefore either needs 84 * to block its use by untrusted code using a SecurityManager test, or 85 * restrict its capabilities so that it doesn't matter if untrusted code 86 * executes it.<p> 87 * 88 * Here's the Foo class written to use the AppContext:<p> 89 * <code><pre> 90 * public class Foo { 91 * public static Foo getDefaultFoo() { 92 * Foo foo = (Foo)AppContext.getAppContext().get(Foo.class); 93 * if (foo == null) { 94 * foo = new Foo(); 95 * getAppContext().put(Foo.class, foo); 96 * } 97 * return foo; 98 * } 99 * 100 * ... Foo service methods 101 * }</pre></code><p> 102 * 103 * Since a separate AppContext can exist for each ThreadGroup, trusted 104 * and untrusted code have access to different Foo instances. This allows 105 * untrusted code access to "system-wide" services -- the service remains 106 * within the AppContext "sandbox". For example, say a malicious applet 107 * wants to peek all of the key events on the EventQueue to listen for 108 * passwords; if separate EventQueues are used for each ThreadGroup 109 * using AppContexts, the only key events that applet will be able to 110 * listen to are its own. A more reasonable applet request would be to 111 * change the Swing default look-and-feel; with that default stored in 112 * an AppContext, the applet's look-and-feel will change without 113 * disrupting other applets or potentially the browser itself.<p> 114 * 115 * Because the AppContext is a facility for safely extending application 116 * service support to applets, none of its methods may be blocked by a 117 * a SecurityManager check in a valid Java implementation. Applets may 118 * therefore safely invoke any of its methods without worry of being 119 * blocked. 120 * 121 * Note: If a SecurityManager is installed which derives from 122 * sun.awt.AWTSecurityManager, it may override the 123 * AWTSecurityManager.getAppContext() method to return the proper 124 * AppContext based on the execution context, in the case where 125 * the default ThreadGroup-based AppContext indexing would return 126 * the main "system" AppContext. For example, in an applet situation, 127 * if a system thread calls into an applet, rather than returning the 128 * main "system" AppContext (the one corresponding to the system thread), 129 * an installed AWTSecurityManager may return the applet's AppContext 130 * based on the execution context. 131 * 132 * @author Thomas Ball 133 * @author Fred Ecks 134 */ 135 public final class AppContext { 136 private static final PlatformLogger log = PlatformLogger.getLogger("sun.awt.AppContext"); 137 138 /* Since the contents of an AppContext are unique to each Java 139 * session, this class should never be serialized. */ 140 141 /* 142 * The key to put()/get() the Java EventQueue into/from the AppContext. 143 */ 144 public static final Object EVENT_QUEUE_KEY = new StringBuffer("EventQueue"); 145 146 /* 147 * The keys to store EventQueue push/pop lock and condition. 148 */ 149 public final static Object EVENT_QUEUE_LOCK_KEY = new StringBuilder("EventQueue.Lock"); 150 public final static Object EVENT_QUEUE_COND_KEY = new StringBuilder("EventQueue.Condition"); 151 152 /* A map of AppContexts, referenced by ThreadGroup. 153 */ 154 private static final Map<ThreadGroup, AppContext> threadGroup2appContext = 155 Collections.synchronizedMap(new IdentityHashMap<ThreadGroup, AppContext>()); 156 157 /** 158 * Returns a set containing all <code>AppContext</code>s. 159 */ 160 public static Set<AppContext> getAppContexts() { 161 synchronized (threadGroup2appContext) { 162 return new HashSet<AppContext>(threadGroup2appContext.values()); 163 } 164 } 165 166 /* The main "system" AppContext, used by everything not otherwise 167 contained in another AppContext. It is implicitly created for 168 standalone apps only (i.e. not applets) 169 */ 170 private static volatile AppContext mainAppContext = null; 171 172 private static class GetAppContextLock {}; 173 private final static Object getAppContextLock = new GetAppContextLock(); 174 175 /* 176 * The hash map associated with this AppContext. A private delegate 177 * is used instead of subclassing HashMap so as to avoid all of 178 * HashMap's potentially risky methods, such as clear(), elements(), 179 * putAll(), etc. 180 */ 181 private final Map<Object, Object> table = new HashMap<>(); 182 183 private final ThreadGroup threadGroup; 184 185 /** 186 * If any <code>PropertyChangeListeners</code> have been registered, 187 * the <code>changeSupport</code> field describes them. 188 * 189 * @see #addPropertyChangeListener 190 * @see #removePropertyChangeListener 191 * @see #firePropertyChange 192 */ 193 private PropertyChangeSupport changeSupport = null; 194 195 public static final String DISPOSED_PROPERTY_NAME = "disposed"; 196 public static final String GUI_DISPOSED = "guidisposed"; 197 198 private enum State { 199 VALID, 200 BEING_DISPOSED, 201 DISPOSED 202 }; 203 204 private volatile State state = State.VALID; 205 206 public boolean isDisposed() { 207 return state == State.DISPOSED; 208 } 209 210 /* 211 * The total number of AppContexts, system-wide. This number is 212 * incremented at the beginning of the constructor, and decremented 213 * at the end of dispose(). getAppContext() checks to see if this 214 * number is 1. If so, it returns the sole AppContext without 215 * checking Thread.currentThread(). 216 */ 217 private static final AtomicInteger numAppContexts = new AtomicInteger(0); 218 219 220 /* 221 * The context ClassLoader that was used to create this AppContext. 222 */ 223 private final ClassLoader contextClassLoader; 224 225 /** 226 * Constructor for AppContext. This method is <i>not</i> public, 227 * nor should it ever be used as such. The proper way to construct 228 * an AppContext is through the use of SunToolkit.createNewAppContext. 229 * A ThreadGroup is created for the new AppContext, a Thread is 230 * created within that ThreadGroup, and that Thread calls 231 * SunToolkit.createNewAppContext before calling anything else. 232 * That creates both the new AppContext and its EventQueue. 233 * 234 * @param threadGroup The ThreadGroup for the new AppContext 235 * @see sun.awt.SunToolkit 236 * @since 1.2 237 */ 238 AppContext(ThreadGroup threadGroup) { 239 numAppContexts.incrementAndGet(); 240 241 this.threadGroup = threadGroup; 242 threadGroup2appContext.put(threadGroup, this); 243 244 this.contextClassLoader = 245 AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() { 246 public ClassLoader run() { 247 return Thread.currentThread().getContextClassLoader(); 248 } 249 }); 250 251 // Initialize push/pop lock and its condition to be used by all the 252 // EventQueues within this AppContext 253 Lock eventQueuePushPopLock = new ReentrantLock(); 254 put(EVENT_QUEUE_LOCK_KEY, eventQueuePushPopLock); 255 Condition eventQueuePushPopCond = eventQueuePushPopLock.newCondition(); 256 put(EVENT_QUEUE_COND_KEY, eventQueuePushPopCond); 257 } 258 259 private static final ThreadLocal<AppContext> threadAppContext = 260 new ThreadLocal<AppContext>(); 261 262 private static void initMainAppContext() { 263 // On the main Thread, we get the ThreadGroup, make a corresponding 264 // AppContext, and instantiate the Java EventQueue. This way, legacy 265 // code is unaffected by the move to multiple AppContext ability. 266 AccessController.doPrivileged(new PrivilegedAction<Void>() { 267 public Void run() { 268 ThreadGroup currentThreadGroup = 269 Thread.currentThread().getThreadGroup(); 270 ThreadGroup parentThreadGroup = currentThreadGroup.getParent(); 271 while (parentThreadGroup != null) { 272 // Find the root ThreadGroup to construct our main AppContext 273 currentThreadGroup = parentThreadGroup; 274 parentThreadGroup = currentThreadGroup.getParent(); 275 } 276 277 mainAppContext = SunToolkit.createNewAppContext(currentThreadGroup); 278 return null; 279 } 280 }); 281 } 282 283 /** 284 * Returns the appropriate AppContext for the caller, 285 * as determined by its ThreadGroup. If the main "system" AppContext 286 * would be returned and there's an AWTSecurityManager installed, it 287 * is called to get the proper AppContext based on the execution 288 * context. 289 * 290 * @return the AppContext for the caller. 291 * @see java.lang.ThreadGroup 292 * @since 1.2 293 */ 294 public static AppContext getAppContext() { 295 // we are standalone app, return the main app context 296 if (numAppContexts.get() == 1 && mainAppContext != null) { 297 return mainAppContext; 298 } 299 300 AppContext appContext = threadAppContext.get(); 301 302 if (null == appContext) { 303 appContext = AccessController.doPrivileged(new PrivilegedAction<AppContext>() 304 { 305 public AppContext run() { 306 // Get the current ThreadGroup, and look for it and its 307 // parents in the hash from ThreadGroup to AppContext -- 308 // it should be found, because we use createNewContext() 309 // when new AppContext objects are created. 310 ThreadGroup currentThreadGroup = Thread.currentThread().getThreadGroup(); 311 ThreadGroup threadGroup = currentThreadGroup; 312 313 // Special case: we implicitly create the main app context 314 // if no contexts have been created yet. This covers standalone apps 315 // and excludes applets because by the time applet starts 316 // a number of contexts have already been created by the plugin. 317 synchronized (getAppContextLock) { 318 if (numAppContexts.get() == 0) { 319 if (System.getProperty("javaplugin.version") == null && 320 System.getProperty("javawebstart.version") == null) { 321 initMainAppContext(); 322 } else if (System.getProperty("javafx.version") != null && 323 threadGroup.getParent() != null) { 324 // Swing inside JavaFX case 325 SunToolkit.createNewAppContext(); 326 } 327 } 328 } 329 330 AppContext context = threadGroup2appContext.get(threadGroup); 331 while (context == null) { 332 threadGroup = threadGroup.getParent(); 333 if (threadGroup == null) { 334 // We've got up to the root thread group and did not find an AppContext 335 // Try to get it from the security manager 336 SecurityManager securityManager = System.getSecurityManager(); 337 if (securityManager != null) { 338 ThreadGroup smThreadGroup = securityManager.getThreadGroup(); 339 if (smThreadGroup != null) { 340 /* 341 * If we get this far then it's likely that 342 * the ThreadGroup does not actually belong 343 * to the applet, so do not cache it. 344 */ 345 return threadGroup2appContext.get(smThreadGroup); 346 } 347 } 348 return null; 349 } 350 context = threadGroup2appContext.get(threadGroup); 351 } 352 353 // In case we did anything in the above while loop, we add 354 // all the intermediate ThreadGroups to threadGroup2appContext 355 // so we won't spin again. 356 for (ThreadGroup tg = currentThreadGroup; tg != threadGroup; tg = tg.getParent()) { 357 threadGroup2appContext.put(tg, context); 358 } 359 360 // Now we're done, so we cache the latest key/value pair. 361 threadAppContext.set(context); 362 363 return context; 364 } 365 }); 366 } 367 368 return appContext; 369 } 370 371 /** 372 * Returns true if the specified AppContext is the main AppContext. 373 * 374 * @param ctx the context to compare with the main context 375 * @return true if the specified AppContext is the main AppContext. 376 * @since 1.8 377 */ 378 public static boolean isMainContext(AppContext ctx) { 379 return (ctx != null && ctx == mainAppContext); 380 } 381 382 private static AppContext getExecutionAppContext() { 383 SecurityManager securityManager = System.getSecurityManager(); 384 if ((securityManager != null) && 385 (securityManager instanceof AWTSecurityManager)) 386 { 387 AWTSecurityManager awtSecMgr = (AWTSecurityManager) securityManager; 388 AppContext secAppContext = awtSecMgr.getAppContext(); 389 return secAppContext; // Return what we're told 390 } 391 return null; 392 } 393 394 private long DISPOSAL_TIMEOUT = 5000; // Default to 5-second timeout 395 // for disposal of all Frames 396 // (we wait for this time twice, 397 // once for dispose(), and once 398 // to clear the EventQueue). 399 400 private long THREAD_INTERRUPT_TIMEOUT = 1000; 401 // Default to 1-second timeout for all 402 // interrupted Threads to exit, and another 403 // 1 second for all stopped Threads to die. 404 405 /** 406 * Disposes of this AppContext, all of its top-level Frames, and 407 * all Threads and ThreadGroups contained within it. 408 * 409 * This method must be called from a Thread which is not contained 410 * within this AppContext. 411 * 412 * @exception IllegalThreadStateException if the current thread is 413 * contained within this AppContext 414 * @since 1.2 415 */ 416 public void dispose() throws IllegalThreadStateException { 417 // Check to be sure that the current Thread isn't in this AppContext 418 if (this.threadGroup.parentOf(Thread.currentThread().getThreadGroup())) { 419 throw new IllegalThreadStateException( 420 "Current Thread is contained within AppContext to be disposed." 421 ); 422 } 423 424 synchronized(this) { 425 if (this.state != State.VALID) { 426 return; // If already disposed or being disposed, bail. 427 } 428 429 this.state = State.BEING_DISPOSED; 430 } 431 432 final PropertyChangeSupport changeSupport = this.changeSupport; 433 if (changeSupport != null) { 434 changeSupport.firePropertyChange(DISPOSED_PROPERTY_NAME, false, true); 435 } 436 437 // First, we post an InvocationEvent to be run on the 438 // EventDispatchThread which disposes of all top-level Frames and TrayIcons 439 440 final Object notificationLock = new Object(); 441 442 Runnable runnable = new Runnable() { 443 public void run() { 444 Window[] windowsToDispose = Window.getOwnerlessWindows(); 445 for (Window w : windowsToDispose) { 446 try { 447 w.dispose(); 448 } catch (Throwable t) { 449 log.finer("exception occurred while disposing app context", t); 450 } 451 } 452 AccessController.doPrivileged(new PrivilegedAction<Void>() { 453 public Void run() { 454 if (!GraphicsEnvironment.isHeadless() && SystemTray.isSupported()) 455 { 456 SystemTray systemTray = SystemTray.getSystemTray(); 457 TrayIcon[] trayIconsToDispose = systemTray.getTrayIcons(); 458 for (TrayIcon ti : trayIconsToDispose) { 459 systemTray.remove(ti); 460 } 461 } 462 return null; 463 } 464 }); 465 // Alert PropertyChangeListeners that the GUI has been disposed. 466 if (changeSupport != null) { 467 changeSupport.firePropertyChange(GUI_DISPOSED, false, true); 468 } 469 synchronized(notificationLock) { 470 notificationLock.notifyAll(); // Notify caller that we're done 471 } 472 } 473 }; 474 synchronized(notificationLock) { 475 SunToolkit.postEvent(this, 476 new InvocationEvent(Toolkit.getDefaultToolkit(), runnable)); 477 try { 478 notificationLock.wait(DISPOSAL_TIMEOUT); 479 } catch (InterruptedException e) { } 480 } 481 482 // Next, we post another InvocationEvent to the end of the 483 // EventQueue. When it's executed, we know we've executed all 484 // events in the queue. 485 486 runnable = new Runnable() { public void run() { 487 synchronized(notificationLock) { 488 notificationLock.notifyAll(); // Notify caller that we're done 489 } 490 } }; 491 synchronized(notificationLock) { 492 SunToolkit.postEvent(this, 493 new InvocationEvent(Toolkit.getDefaultToolkit(), runnable)); 494 try { 495 notificationLock.wait(DISPOSAL_TIMEOUT); 496 } catch (InterruptedException e) { } 497 } 498 499 // We are done with posting events, so change the state to disposed 500 synchronized(this) { 501 this.state = State.DISPOSED; 502 } 503 504 // Next, we interrupt all Threads in the ThreadGroup 505 this.threadGroup.interrupt(); 506 // Note, the EventDispatchThread we've interrupted may dump an 507 // InterruptedException to the console here. This needs to be 508 // fixed in the EventDispatchThread, not here. 509 510 // Next, we sleep 10ms at a time, waiting for all of the active 511 // Threads in the ThreadGroup to exit. 512 513 long startTime = System.currentTimeMillis(); 514 long endTime = startTime + THREAD_INTERRUPT_TIMEOUT; 515 while ((this.threadGroup.activeCount() > 0) && 516 (System.currentTimeMillis() < endTime)) { 517 try { 518 Thread.sleep(10); 519 } catch (InterruptedException e) { } 520 } 521 522 // Then, we stop any remaining Threads 523 this.threadGroup.stop(); 524 525 // Next, we sleep 10ms at a time, waiting for all of the active 526 // Threads in the ThreadGroup to die. 527 528 startTime = System.currentTimeMillis(); 529 endTime = startTime + THREAD_INTERRUPT_TIMEOUT; 530 while ((this.threadGroup.activeCount() > 0) && 531 (System.currentTimeMillis() < endTime)) { 532 try { 533 Thread.sleep(10); 534 } catch (InterruptedException e) { } 535 } 536 537 // Next, we remove this and all subThreadGroups from threadGroup2appContext 538 int numSubGroups = this.threadGroup.activeGroupCount(); 539 if (numSubGroups > 0) { 540 ThreadGroup [] subGroups = new ThreadGroup[numSubGroups]; 541 numSubGroups = this.threadGroup.enumerate(subGroups); 542 for (int subGroup = 0; subGroup < numSubGroups; subGroup++) { 543 threadGroup2appContext.remove(subGroups[subGroup]); 544 } 545 } 546 threadGroup2appContext.remove(this.threadGroup); 547 548 threadAppContext.set(null); 549 550 // Finally, we destroy the ThreadGroup entirely. 551 try { 552 this.threadGroup.destroy(); 553 } catch (IllegalThreadStateException e) { 554 // Fired if not all the Threads died, ignore it and proceed 555 } 556 557 synchronized (table) { 558 this.table.clear(); // Clear out the Hashtable to ease garbage collection 559 } 560 561 numAppContexts.decrementAndGet(); 562 563 mostRecentKeyValue = null; 564 } 565 566 static final class PostShutdownEventRunnable implements Runnable { 567 private final AppContext appContext; 568 569 public PostShutdownEventRunnable(AppContext ac) { 570 appContext = ac; 571 } 572 573 public void run() { 574 final EventQueue eq = (EventQueue)appContext.get(EVENT_QUEUE_KEY); 575 if (eq != null) { 576 eq.postEvent(AWTAutoShutdown.getShutdownEvent()); 577 } 578 } 579 } 580 581 static final class CreateThreadAction implements PrivilegedAction<Thread> { 582 private final AppContext appContext; 583 private final Runnable runnable; 584 585 public CreateThreadAction(AppContext ac, Runnable r) { 586 appContext = ac; 587 runnable = r; 588 } 589 590 public Thread run() { 591 Thread t = new Thread(appContext.getThreadGroup(), runnable); 592 t.setContextClassLoader(appContext.getContextClassLoader()); 593 t.setPriority(Thread.NORM_PRIORITY + 1); 594 t.setDaemon(true); 595 return t; 596 } 597 } 598 599 static void stopEventDispatchThreads() { 600 for (AppContext appContext: getAppContexts()) { 601 if (appContext.isDisposed()) { 602 continue; 603 } 604 Runnable r = new PostShutdownEventRunnable(appContext); 605 // For security reasons EventQueue.postEvent should only be called 606 // on a thread that belongs to the corresponding thread group. 607 if (appContext != AppContext.getAppContext()) { 608 // Create a thread that belongs to the thread group associated 609 // with the AppContext and invokes EventQueue.postEvent. 610 PrivilegedAction<Thread> action = new CreateThreadAction(appContext, r); 611 Thread thread = AccessController.doPrivileged(action); 612 thread.start(); 613 } else { 614 r.run(); 615 } 616 } 617 } 618 619 private MostRecentKeyValue mostRecentKeyValue = null; 620 private MostRecentKeyValue shadowMostRecentKeyValue = null; 621 622 /** 623 * Returns the value to which the specified key is mapped in this context. 624 * 625 * @param key a key in the AppContext. 626 * @return the value to which the key is mapped in this AppContext; 627 * <code>null</code> if the key is not mapped to any value. 628 * @see #put(Object, Object) 629 * @since 1.2 630 */ 631 public Object get(Object key) { 632 /* 633 * The most recent reference should be updated inside a synchronized 634 * block to avoid a race when put() and get() are executed in 635 * parallel on different threads. 636 */ 637 synchronized (table) { 638 // Note: this most recent key/value caching is thread-hot. 639 // A simple test using SwingSet found that 72% of lookups 640 // were matched using the most recent key/value. By instantiating 641 // a simple MostRecentKeyValue object on cache misses, the 642 // cache hits can be processed without synchronization. 643 644 MostRecentKeyValue recent = mostRecentKeyValue; 645 if ((recent != null) && (recent.key == key)) { 646 return recent.value; 647 } 648 649 Object value = table.get(key); 650 if(mostRecentKeyValue == null) { 651 mostRecentKeyValue = new MostRecentKeyValue(key, value); 652 shadowMostRecentKeyValue = new MostRecentKeyValue(key, value); 653 } else { 654 MostRecentKeyValue auxKeyValue = mostRecentKeyValue; 655 shadowMostRecentKeyValue.setPair(key, value); 656 mostRecentKeyValue = shadowMostRecentKeyValue; 657 shadowMostRecentKeyValue = auxKeyValue; 658 } 659 return value; 660 } 661 } 662 663 /** 664 * Maps the specified <code>key</code> to the specified 665 * <code>value</code> in this AppContext. Neither the key nor the 666 * value can be <code>null</code>. 667 * <p> 668 * The value can be retrieved by calling the <code>get</code> method 669 * with a key that is equal to the original key. 670 * 671 * @param key the AppContext key. 672 * @param value the value. 673 * @return the previous value of the specified key in this 674 * AppContext, or <code>null</code> if it did not have one. 675 * @exception NullPointerException if the key or value is 676 * <code>null</code>. 677 * @see #get(Object) 678 * @since 1.2 679 */ 680 public Object put(Object key, Object value) { 681 synchronized (table) { 682 MostRecentKeyValue recent = mostRecentKeyValue; 683 if ((recent != null) && (recent.key == key)) 684 recent.value = value; 685 return table.put(key, value); 686 } 687 } 688 689 /** 690 * Removes the key (and its corresponding value) from this 691 * AppContext. This method does nothing if the key is not in the 692 * AppContext. 693 * 694 * @param key the key that needs to be removed. 695 * @return the value to which the key had been mapped in this AppContext, 696 * or <code>null</code> if the key did not have a mapping. 697 * @since 1.2 698 */ 699 public Object remove(Object key) { 700 synchronized (table) { 701 MostRecentKeyValue recent = mostRecentKeyValue; 702 if ((recent != null) && (recent.key == key)) 703 recent.value = null; 704 return table.remove(key); 705 } 706 } 707 708 /** 709 * Returns the root ThreadGroup for all Threads contained within 710 * this AppContext. 711 * @since 1.2 712 */ 713 public ThreadGroup getThreadGroup() { 714 return threadGroup; 715 } 716 717 /** 718 * Returns the context ClassLoader that was used to create this 719 * AppContext. 720 * 721 * @see java.lang.Thread#getContextClassLoader 722 */ 723 public ClassLoader getContextClassLoader() { 724 return contextClassLoader; 725 } 726 727 /** 728 * Returns a string representation of this AppContext. 729 * @since 1.2 730 */ 731 @Override 732 public String toString() { 733 return getClass().getName() + "[threadGroup=" + threadGroup.getName() + "]"; 734 } 735 736 /** 737 * Returns an array of all the property change listeners 738 * registered on this component. 739 * 740 * @return all of this component's <code>PropertyChangeListener</code>s 741 * or an empty array if no property change 742 * listeners are currently registered 743 * 744 * @see #addPropertyChangeListener 745 * @see #removePropertyChangeListener 746 * @see #getPropertyChangeListeners(java.lang.String) 747 * @see java.beans.PropertyChangeSupport#getPropertyChangeListeners 748 * @since 1.4 749 */ 750 public synchronized PropertyChangeListener[] getPropertyChangeListeners() { 751 if (changeSupport == null) { 752 return new PropertyChangeListener[0]; 753 } 754 return changeSupport.getPropertyChangeListeners(); 755 } 756 757 /** 758 * Adds a PropertyChangeListener to the listener list for a specific 759 * property. The specified property may be one of the following: 760 * <ul> 761 * <li>if this AppContext is disposed ("disposed")</li> 762 * </ul> 763 * <ul> 764 * <li>if this AppContext's unowned Windows have been disposed 765 * ("guidisposed"). Code to cleanup after the GUI is disposed 766 * (such as LookAndFeel.uninitialize()) should execute in response to 767 * this property being fired. Notifications for the "guidisposed" 768 * property are sent on the event dispatch thread.</li> 769 * </ul> 770 * <p> 771 * If listener is null, no exception is thrown and no action is performed. 772 * 773 * @param propertyName one of the property names listed above 774 * @param listener the PropertyChangeListener to be added 775 * 776 * @see #removePropertyChangeListener(java.lang.String, java.beans.PropertyChangeListener) 777 * @see #getPropertyChangeListeners(java.lang.String) 778 * @see #addPropertyChangeListener(java.lang.String, java.beans.PropertyChangeListener) 779 */ 780 public synchronized void addPropertyChangeListener( 781 String propertyName, 782 PropertyChangeListener listener) { 783 if (listener == null) { 784 return; 785 } 786 if (changeSupport == null) { 787 changeSupport = new PropertyChangeSupport(this); 788 } 789 changeSupport.addPropertyChangeListener(propertyName, listener); 790 } 791 792 /** 793 * Removes a PropertyChangeListener from the listener list for a specific 794 * property. This method should be used to remove PropertyChangeListeners 795 * that were registered for a specific bound property. 796 * <p> 797 * If listener is null, no exception is thrown and no action is performed. 798 * 799 * @param propertyName a valid property name 800 * @param listener the PropertyChangeListener to be removed 801 * 802 * @see #addPropertyChangeListener(java.lang.String, java.beans.PropertyChangeListener) 803 * @see #getPropertyChangeListeners(java.lang.String) 804 * @see #removePropertyChangeListener(java.beans.PropertyChangeListener) 805 */ 806 public synchronized void removePropertyChangeListener( 807 String propertyName, 808 PropertyChangeListener listener) { 809 if (listener == null || changeSupport == null) { 810 return; 811 } 812 changeSupport.removePropertyChangeListener(propertyName, listener); 813 } 814 815 /** 816 * Returns an array of all the listeners which have been associated 817 * with the named property. 818 * 819 * @return all of the <code>PropertyChangeListeners</code> associated with 820 * the named property or an empty array if no listeners have 821 * been added 822 * 823 * @see #addPropertyChangeListener(java.lang.String, java.beans.PropertyChangeListener) 824 * @see #removePropertyChangeListener(java.lang.String, java.beans.PropertyChangeListener) 825 * @see #getPropertyChangeListeners 826 * @since 1.4 827 */ 828 public synchronized PropertyChangeListener[] getPropertyChangeListeners( 829 String propertyName) { 830 if (changeSupport == null) { 831 return new PropertyChangeListener[0]; 832 } 833 return changeSupport.getPropertyChangeListeners(propertyName); 834 } 835 836 // Set up JavaAWTAccess in SharedSecrets 837 static { 838 sun.misc.SharedSecrets.setJavaAWTAccess(new sun.misc.JavaAWTAccess() { 839 private boolean hasRootThreadGroup(final AppContext ecx) { 840 return AccessController.doPrivileged(new PrivilegedAction<Boolean>() { 841 @Override 842 public Boolean run() { 843 return ecx.threadGroup.getParent() == null; 844 } 845 }); 846 } 847 848 /** 849 * Returns the AppContext used for applet logging isolation, or null if 850 * the default global context can be used. 851 * If there's no applet, or if the caller is a stand alone application, 852 * or running in the main app context, returns null. 853 * Otherwise, returns the AppContext of the calling applet. 854 * @return null if the global default context can be used, 855 * an AppContext otherwise. 856 **/ 857 public Object getAppletContext() { 858 // There's no AppContext: return null. 859 // No need to call getAppContext() if numAppContext == 0: 860 // it means that no AppContext has been created yet, and 861 // we don't want to trigger the creation of a main app 862 // context since we don't need it. 863 if (numAppContexts.get() == 0) return null; 864 865 // Get the context from the security manager 866 AppContext ecx = getExecutionAppContext(); 867 868 // Not sure we really need to re-check numAppContexts here. 869 // If all applets have gone away then we could have a 870 // numAppContexts coming back to 0. So we recheck 871 // it here because we don't want to trigger the 872 // creation of a main AppContext in that case. 873 // This is probably not 100% MT-safe but should reduce 874 // the window of opportunity in which that issue could 875 // happen. 876 if (numAppContexts.get() > 0) { 877 // Defaults to thread group caching. 878 // This is probably not required as we only really need 879 // isolation in a deployed applet environment, in which 880 // case ecx will not be null when we reach here 881 // However it helps emulate the deployed environment, 882 // in tests for instance. 883 ecx = ecx != null ? ecx : getAppContext(); 884 } 885 886 // getAppletContext() may be called when initializing the main 887 // app context - in which case mainAppContext will still be 888 // null. To work around this issue we simply use 889 // AppContext.threadGroup.getParent() == null instead, since 890 // mainAppContext is the only AppContext which should have 891 // the root TG as its thread group. 892 // See: JDK-8023258 893 final boolean isMainAppContext = ecx == null 894 || mainAppContext == ecx 895 || mainAppContext == null && hasRootThreadGroup(ecx); 896 897 return isMainAppContext ? null : ecx; 898 } 899 900 }); 901 } 902 903 public static <T> T getSoftReferenceValue(Object key, 904 Supplier<T> supplier) { 905 906 final AppContext appContext = AppContext.getAppContext(); 907 @SuppressWarnings("unchecked") 908 SoftReference<T> ref = (SoftReference<T>) appContext.get(key); 909 if (ref != null) { 910 final T object = ref.get(); 911 if (object != null) { 912 return object; 913 } 914 } 915 final T object = supplier.get(); 916 ref = new SoftReference<>(object); 917 appContext.put(key, ref); 918 return object; 919 } 920 } 921 922 final class MostRecentKeyValue { 923 Object key; 924 Object value; 925 MostRecentKeyValue(Object k, Object v) { 926 key = k; 927 value = v; 928 } 929 void setPair(Object k, Object v) { 930 key = k; 931 value = v; 932 } 933 }