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