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