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