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