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