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