1 /*
   2  * Copyright (c) 1996, 2014, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package java.awt;
  27 
  28 import java.awt.event.*;
  29 
  30 import java.awt.peer.ComponentPeer;
  31 
  32 import java.lang.ref.WeakReference;
  33 import java.lang.reflect.InvocationTargetException;
  34 
  35 import java.security.AccessController;
  36 import java.security.PrivilegedAction;
  37 
  38 import java.util.EmptyStackException;
  39 
  40 import sun.awt.dnd.SunDropTargetEvent;
  41 import sun.util.logging.PlatformLogger;
  42 
  43 import sun.awt.AppContext;
  44 import sun.awt.AWTAutoShutdown;
  45 import sun.awt.PeerEvent;
  46 import sun.awt.SunToolkit;
  47 import sun.awt.EventQueueItem;
  48 import sun.awt.AWTAccessor;
  49 
  50 import java.util.concurrent.locks.Condition;
  51 import java.util.concurrent.locks.Lock;
  52 import java.util.concurrent.atomic.AtomicInteger;
  53 
  54 import java.security.AccessControlContext;
  55 
  56 import sun.misc.SharedSecrets;
  57 import sun.misc.JavaSecurityAccess;
  58 
  59 /**
  60  * <code>EventQueue</code> is a platform-independent class
  61  * that queues events, both from the underlying peer classes
  62  * and from trusted application classes.
  63  * <p>
  64  * It encapsulates asynchronous event dispatch machinery which
  65  * extracts events from the queue and dispatches them by calling
  66  * {@link #dispatchEvent(AWTEvent) dispatchEvent(AWTEvent)} method
  67  * on this <code>EventQueue</code> with the event to be dispatched
  68  * as an argument.  The particular behavior of this machinery is
  69  * implementation-dependent.  The only requirements are that events
  70  * which were actually enqueued to this queue (note that events
  71  * being posted to the <code>EventQueue</code> can be coalesced)
  72  * are dispatched:
  73  * <dl>
  74  *   <dt> Sequentially.
  75  *   <dd> That is, it is not permitted that several events from
  76  *        this queue are dispatched simultaneously.
  77  *   <dt> In the same order as they are enqueued.
  78  *   <dd> That is, if <code>AWTEvent</code>&nbsp;A is enqueued
  79  *        to the <code>EventQueue</code> before
  80  *        <code>AWTEvent</code>&nbsp;B then event B will not be
  81  *        dispatched before event A.
  82  * </dl>
  83  * <p>
  84  * Some browsers partition applets in different code bases into
  85  * separate contexts, and establish walls between these contexts.
  86  * In such a scenario, there will be one <code>EventQueue</code>
  87  * per context. Other browsers place all applets into the same
  88  * context, implying that there will be only a single, global
  89  * <code>EventQueue</code> for all applets. This behavior is
  90  * implementation-dependent.  Consult your browser's documentation
  91  * for more information.
  92  * <p>
  93  * For information on the threading issues of the event dispatch
  94  * machinery, see <a href="doc-files/AWTThreadIssues.html#Autoshutdown"
  95  * >AWT Threading Issues</a>.
  96  *
  97  * @author Thomas Ball
  98  * @author Fred Ecks
  99  * @author David Mendenhall
 100  *
 101  * @since       1.1
 102  */
 103 public class EventQueue {
 104     private static final AtomicInteger threadInitNumber = new AtomicInteger(0);
 105 
 106     private static final int LOW_PRIORITY = 0;
 107     private static final int NORM_PRIORITY = 1;
 108     private static final int HIGH_PRIORITY = 2;
 109     private static final int ULTIMATE_PRIORITY = 3;
 110 
 111     private static final int NUM_PRIORITIES = ULTIMATE_PRIORITY + 1;
 112 
 113     /*
 114      * We maintain one Queue for each priority that the EventQueue supports.
 115      * That is, the EventQueue object is actually implemented as
 116      * NUM_PRIORITIES queues and all Events on a particular internal Queue
 117      * have identical priority. Events are pulled off the EventQueue starting
 118      * with the Queue of highest priority. We progress in decreasing order
 119      * across all Queues.
 120      */
 121     private Queue[] queues = new Queue[NUM_PRIORITIES];
 122 
 123     /*
 124      * The next EventQueue on the stack, or null if this EventQueue is
 125      * on the top of the stack.  If nextQueue is non-null, requests to post
 126      * an event are forwarded to nextQueue.
 127      */
 128     private EventQueue nextQueue;
 129 
 130     /*
 131      * The previous EventQueue on the stack, or null if this is the
 132      * "base" EventQueue.
 133      */
 134     private EventQueue previousQueue;
 135 
 136     /*
 137      * A single lock to synchronize the push()/pop() and related operations with
 138      * all the EventQueues from the AppContext. Synchronization on any particular
 139      * event queue(s) is not enough: we should lock the whole stack.
 140      */
 141     private final Lock pushPopLock;
 142     private final Condition pushPopCond;
 143 
 144     /*
 145      * Dummy runnable to wake up EDT from getNextEvent() after
 146      push/pop is performed
 147      */
 148     private final static Runnable dummyRunnable = new Runnable() {
 149         public void run() {
 150         }
 151     };
 152 
 153     private EventDispatchThread dispatchThread;
 154 
 155     private final ThreadGroup threadGroup =
 156         Thread.currentThread().getThreadGroup();
 157     private final ClassLoader classLoader =
 158         Thread.currentThread().getContextClassLoader();
 159 
 160     /*
 161      * The time stamp of the last dispatched InputEvent or ActionEvent.
 162      */
 163     private long mostRecentEventTime = System.currentTimeMillis();
 164 
 165     /**
 166      * The modifiers field of the current event, if the current event is an
 167      * InputEvent or ActionEvent.
 168      */
 169     private WeakReference currentEvent;
 170 
 171     /*
 172      * Non-zero if a thread is waiting in getNextEvent(int) for an event of
 173      * a particular ID to be posted to the queue.
 174      */
 175     private volatile int waitForID;
 176 
 177     /*
 178      * AppContext corresponding to the queue.
 179      */
 180     private final AppContext appContext;
 181 
 182     private final String name = "AWT-EventQueue-" + threadInitNumber.getAndIncrement();
 183 
 184     private static volatile PlatformLogger eventLog;
 185     private static Object eventLogCreationLock = new Object();
 186 
 187     private static final PlatformLogger getEventLog() {
 188         if(eventLog == null) {
 189             synchronized (eventLogCreationLock) {
 190                 if(eventLog == null){
 191                     eventLog = PlatformLogger.getLogger("java.awt.event.EventQueue");
 192                 }
 193             }
 194         }
 195         return eventLog;
 196     }
 197 
 198     static {
 199         AWTAccessor.setEventQueueAccessor(
 200             new AWTAccessor.EventQueueAccessor() {
 201                 public Thread getDispatchThread(EventQueue eventQueue) {
 202                     return eventQueue.getDispatchThread();
 203                 }
 204                 public boolean isDispatchThreadImpl(EventQueue eventQueue) {
 205                     return eventQueue.isDispatchThreadImpl();
 206                 }
 207                 public void removeSourceEvents(EventQueue eventQueue,
 208                                                Object source,
 209                                                boolean removeAllEvents)
 210                 {
 211                     eventQueue.removeSourceEvents(source, removeAllEvents);
 212                 }
 213                 public boolean noEvents(EventQueue eventQueue) {
 214                     return eventQueue.noEvents();
 215                 }
 216                 public void wakeup(EventQueue eventQueue, boolean isShutdown) {
 217                     eventQueue.wakeup(isShutdown);
 218                 }
 219                 public void invokeAndWait(Object source, Runnable r)
 220                     throws InterruptedException, InvocationTargetException
 221                 {
 222                     EventQueue.invokeAndWait(source, r);
 223                 }
 224 
 225                 @Override
 226                 public long getMostRecentEventTime(EventQueue eventQueue) {
 227                     return eventQueue.getMostRecentEventTimeImpl();
 228                 }
 229             });
 230     }
 231 
 232     public EventQueue() {
 233         for (int i = 0; i < NUM_PRIORITIES; i++) {
 234             queues[i] = new Queue();
 235         }
 236         /*
 237          * NOTE: if you ever have to start the associated event dispatch
 238          * thread at this point, be aware of the following problem:
 239          * If this EventQueue instance is created in
 240          * SunToolkit.createNewAppContext() the started dispatch thread
 241          * may call AppContext.getAppContext() before createNewAppContext()
 242          * completes thus causing mess in thread group to appcontext mapping.
 243          */
 244 
 245         appContext = AppContext.getAppContext();
 246         pushPopLock = (Lock)appContext.get(AppContext.EVENT_QUEUE_LOCK_KEY);
 247         pushPopCond = (Condition)appContext.get(AppContext.EVENT_QUEUE_COND_KEY);
 248     }
 249 
 250     /**
 251      * Posts a 1.1-style event to the <code>EventQueue</code>.
 252      * If there is an existing event on the queue with the same ID
 253      * and event source, the source <code>Component</code>'s
 254      * <code>coalesceEvents</code> method will be called.
 255      *
 256      * @param theEvent an instance of <code>java.awt.AWTEvent</code>,
 257      *          or a subclass of it
 258      * @throws NullPointerException if <code>theEvent</code> is <code>null</code>
 259      */
 260     public void postEvent(AWTEvent theEvent) {
 261         SunToolkit.flushPendingEvents(appContext);
 262         postEventPrivate(theEvent);
 263     }
 264 
 265     /**
 266      * Posts a 1.1-style event to the <code>EventQueue</code>.
 267      * If there is an existing event on the queue with the same ID
 268      * and event source, the source <code>Component</code>'s
 269      * <code>coalesceEvents</code> method will be called.
 270      *
 271      * @param theEvent an instance of <code>java.awt.AWTEvent</code>,
 272      *          or a subclass of it
 273      */
 274     private final void postEventPrivate(AWTEvent theEvent) {
 275         theEvent.isPosted = true;
 276         pushPopLock.lock();
 277         try {
 278             if (nextQueue != null) {
 279                 // Forward the event to the top of EventQueue stack
 280                 nextQueue.postEventPrivate(theEvent);
 281                 return;
 282             }
 283             if (dispatchThread == null) {
 284                 if (theEvent.getSource() == AWTAutoShutdown.getInstance()) {
 285                     return;
 286                 } else {
 287                     initDispatchThread();
 288                 }
 289             }
 290             postEvent(theEvent, getPriority(theEvent));
 291         } finally {
 292             pushPopLock.unlock();
 293         }
 294     }
 295 
 296     private static int getPriority(AWTEvent theEvent) {
 297         if (theEvent instanceof PeerEvent) {
 298             PeerEvent peerEvent = (PeerEvent)theEvent;
 299             if ((peerEvent.getFlags() & PeerEvent.ULTIMATE_PRIORITY_EVENT) != 0) {
 300                 return ULTIMATE_PRIORITY;
 301             }
 302             if ((peerEvent.getFlags() & PeerEvent.PRIORITY_EVENT) != 0) {
 303                 return HIGH_PRIORITY;
 304             }
 305             if ((peerEvent.getFlags() & PeerEvent.LOW_PRIORITY_EVENT) != 0) {
 306                 return LOW_PRIORITY;
 307             }
 308         }
 309         int id = theEvent.getID();
 310         if ((id >= PaintEvent.PAINT_FIRST) && (id <= PaintEvent.PAINT_LAST)) {
 311             return LOW_PRIORITY;
 312         }
 313         return NORM_PRIORITY;
 314     }
 315 
 316     /**
 317      * Posts the event to the internal Queue of specified priority,
 318      * coalescing as appropriate.
 319      *
 320      * @param theEvent an instance of <code>java.awt.AWTEvent</code>,
 321      *          or a subclass of it
 322      * @param priority  the desired priority of the event
 323      */
 324     private void postEvent(AWTEvent theEvent, int priority) {
 325         if (coalesceEvent(theEvent, priority)) {
 326             return;
 327         }
 328 
 329         EventQueueItem newItem = new EventQueueItem(theEvent);
 330 
 331         cacheEQItem(newItem);
 332 
 333         boolean notifyID = (theEvent.getID() == this.waitForID);
 334 
 335         if (queues[priority].head == null) {
 336             boolean shouldNotify = noEvents();
 337             queues[priority].head = queues[priority].tail = newItem;
 338 
 339             if (shouldNotify) {
 340                 if (theEvent.getSource() != AWTAutoShutdown.getInstance()) {
 341                     AWTAutoShutdown.getInstance().notifyThreadBusy(dispatchThread);
 342                 }
 343                 pushPopCond.signalAll();
 344             } else if (notifyID) {
 345                 pushPopCond.signalAll();
 346             }
 347         } else {
 348             // The event was not coalesced or has non-Component source.
 349             // Insert it at the end of the appropriate Queue.
 350             queues[priority].tail.next = newItem;
 351             queues[priority].tail = newItem;
 352             if (notifyID) {
 353                 pushPopCond.signalAll();
 354             }
 355         }
 356     }
 357 
 358     private boolean coalescePaintEvent(PaintEvent e) {
 359         ComponentPeer sourcePeer = ((Component)e.getSource()).peer;
 360         if (sourcePeer != null) {
 361             sourcePeer.coalescePaintEvent(e);
 362         }
 363         EventQueueItem[] cache = ((Component)e.getSource()).eventCache;
 364         if (cache == null) {
 365             return false;
 366         }
 367         int index = eventToCacheIndex(e);
 368 
 369         if (index != -1 && cache[index] != null) {
 370             PaintEvent merged = mergePaintEvents(e, (PaintEvent)cache[index].event);
 371             if (merged != null) {
 372                 cache[index].event = merged;
 373                 return true;
 374             }
 375         }
 376         return false;
 377     }
 378 
 379     private PaintEvent mergePaintEvents(PaintEvent a, PaintEvent b) {
 380         Rectangle aRect = a.getUpdateRect();
 381         Rectangle bRect = b.getUpdateRect();
 382         if (bRect.contains(aRect)) {
 383             return b;
 384         }
 385         if (aRect.contains(bRect)) {
 386             return a;
 387         }
 388         return null;
 389     }
 390 
 391     private boolean coalesceMouseEvent(MouseEvent e) {
 392         if (e instanceof SunDropTargetEvent) {
 393             // SunDropTargetEvent should not coalesce with MouseEvent
 394             return false;
 395         }
 396         EventQueueItem[] cache = ((Component)e.getSource()).eventCache;
 397         if (cache == null) {
 398             return false;
 399         }
 400         int index = eventToCacheIndex(e);
 401         if (index != -1 && cache[index] != null) {
 402             cache[index].event = e;
 403             return true;
 404         }
 405         return false;
 406     }
 407 
 408     private boolean coalescePeerEvent(PeerEvent e) {
 409         EventQueueItem[] cache = ((Component)e.getSource()).eventCache;
 410         if (cache == null) {
 411             return false;
 412         }
 413         int index = eventToCacheIndex(e);
 414         if (index != -1 && cache[index] != null) {
 415             e = e.coalesceEvents((PeerEvent)cache[index].event);
 416             if (e != null) {
 417                 cache[index].event = e;
 418                 return true;
 419             } else {
 420                 cache[index] = null;
 421             }
 422         }
 423         return false;
 424     }
 425 
 426     /*
 427      * Should avoid of calling this method by any means
 428      * as it's working time is dependant on EQ length.
 429      * In the wors case this method alone can slow down the entire application
 430      * 10 times by stalling the Event processing.
 431      * Only here by backward compatibility reasons.
 432      */
 433     private boolean coalesceOtherEvent(AWTEvent e, int priority) {
 434         int id = e.getID();
 435         Component source = (Component)e.getSource();
 436         for (EventQueueItem entry = queues[priority].head;
 437             entry != null; entry = entry.next)
 438         {
 439             // Give Component.coalesceEvents a chance
 440             if (entry.event.getSource() == source && entry.event.getID() == id) {
 441                 AWTEvent coalescedEvent = source.coalesceEvents(
 442                     entry.event, e);
 443                 if (coalescedEvent != null) {
 444                     entry.event = coalescedEvent;
 445                     return true;
 446                 }
 447             }
 448         }
 449         return false;
 450     }
 451 
 452     private boolean coalesceEvent(AWTEvent e, int priority) {
 453         if (!(e.getSource() instanceof Component)) {
 454             return false;
 455         }
 456         if (e instanceof PeerEvent) {
 457             return coalescePeerEvent((PeerEvent)e);
 458         }
 459         // The worst case
 460         if (((Component)e.getSource()).isCoalescingEnabled()
 461             && coalesceOtherEvent(e, priority))
 462         {
 463             return true;
 464         }
 465         if (e instanceof PaintEvent) {
 466             return coalescePaintEvent((PaintEvent)e);
 467         }
 468         if (e instanceof MouseEvent) {
 469             return coalesceMouseEvent((MouseEvent)e);
 470         }
 471         return false;
 472     }
 473 
 474     private void cacheEQItem(EventQueueItem entry) {
 475         if(entry.event instanceof SunDropTargetEvent) {
 476             // Do not cache SunDropTargetEvent, it should not coalesce
 477             return;
 478         }
 479         int index = eventToCacheIndex(entry.event);
 480         if (index != -1 && entry.event.getSource() instanceof Component) {
 481             Component source = (Component)entry.event.getSource();
 482             if (source.eventCache == null) {
 483                 source.eventCache = new EventQueueItem[CACHE_LENGTH];
 484             }
 485             source.eventCache[index] = entry;
 486         }
 487     }
 488 
 489     private void uncacheEQItem(EventQueueItem entry) {
 490         int index = eventToCacheIndex(entry.event);
 491         if (index != -1 && entry.event.getSource() instanceof Component) {
 492             Component source = (Component)entry.event.getSource();
 493             if (source.eventCache == null) {
 494                 return;
 495             }
 496             source.eventCache[index] = null;
 497         }
 498     }
 499 
 500     private static final int PAINT = 0;
 501     private static final int UPDATE = 1;
 502     private static final int MOVE = 2;
 503     private static final int DRAG = 3;
 504     private static final int PEER = 4;
 505     private static final int CACHE_LENGTH = 5;
 506 
 507     private static int eventToCacheIndex(AWTEvent e) {
 508         switch(e.getID()) {
 509         case PaintEvent.PAINT:
 510             return PAINT;
 511         case PaintEvent.UPDATE:
 512             return UPDATE;
 513         case MouseEvent.MOUSE_MOVED:
 514             return MOVE;
 515         case MouseEvent.MOUSE_DRAGGED:
 516             return DRAG;
 517         default:
 518             return e instanceof PeerEvent ? PEER : -1;
 519         }
 520     }
 521 
 522     /**
 523      * Returns whether an event is pending on any of the separate
 524      * Queues.
 525      * @return whether an event is pending on any of the separate Queues
 526      */
 527     private boolean noEvents() {
 528         for (int i = 0; i < NUM_PRIORITIES; i++) {
 529             if (queues[i].head != null) {
 530                 return false;
 531             }
 532         }
 533 
 534         return true;
 535     }
 536 
 537     /**
 538      * Removes an event from the <code>EventQueue</code> and
 539      * returns it.  This method will block until an event has
 540      * been posted by another thread.
 541      * @return the next <code>AWTEvent</code>
 542      * @exception InterruptedException
 543      *            if any thread has interrupted this thread
 544      */
 545     public AWTEvent getNextEvent() throws InterruptedException {
 546         do {
 547             /*
 548              * SunToolkit.flushPendingEvents must be called outside
 549              * of the synchronized block to avoid deadlock when
 550              * event queues are nested with push()/pop().
 551              */
 552             SunToolkit.flushPendingEvents(appContext);
 553             pushPopLock.lock();
 554             try {
 555                 AWTEvent event = getNextEventPrivate();
 556                 if (event != null) {
 557                     return event;
 558                 }
 559                 AWTAutoShutdown.getInstance().notifyThreadFree(dispatchThread);
 560                 pushPopCond.await();
 561             } finally {
 562                 pushPopLock.unlock();
 563             }
 564         } while(true);
 565     }
 566 
 567     /*
 568      * Must be called under the lock. Doesn't call flushPendingEvents()
 569      */
 570     AWTEvent getNextEventPrivate() throws InterruptedException {
 571         for (int i = NUM_PRIORITIES - 1; i >= 0; i--) {
 572             if (queues[i].head != null) {
 573                 EventQueueItem entry = queues[i].head;
 574                 queues[i].head = entry.next;
 575                 if (entry.next == null) {
 576                     queues[i].tail = null;
 577                 }
 578                 uncacheEQItem(entry);
 579                 return entry.event;
 580             }
 581         }
 582         return null;
 583     }
 584 
 585     AWTEvent getNextEvent(int id) throws InterruptedException {
 586         do {
 587             /*
 588              * SunToolkit.flushPendingEvents must be called outside
 589              * of the synchronized block to avoid deadlock when
 590              * event queues are nested with push()/pop().
 591              */
 592             SunToolkit.flushPendingEvents(appContext);
 593             pushPopLock.lock();
 594             try {
 595                 for (int i = 0; i < NUM_PRIORITIES; i++) {
 596                     for (EventQueueItem entry = queues[i].head, prev = null;
 597                          entry != null; prev = entry, entry = entry.next)
 598                     {
 599                         if (entry.event.getID() == id) {
 600                             if (prev == null) {
 601                                 queues[i].head = entry.next;
 602                             } else {
 603                                 prev.next = entry.next;
 604                             }
 605                             if (queues[i].tail == entry) {
 606                                 queues[i].tail = prev;
 607                             }
 608                             uncacheEQItem(entry);
 609                             return entry.event;
 610                         }
 611                     }
 612                 }
 613                 waitForID = id;
 614                 pushPopCond.await();
 615                 waitForID = 0;
 616             } finally {
 617                 pushPopLock.unlock();
 618             }
 619         } while(true);
 620     }
 621 
 622     /**
 623      * Returns the first event on the <code>EventQueue</code>
 624      * without removing it.
 625      * @return the first event
 626      */
 627     public AWTEvent peekEvent() {
 628         pushPopLock.lock();
 629         try {
 630             for (int i = NUM_PRIORITIES - 1; i >= 0; i--) {
 631                 if (queues[i].head != null) {
 632                     return queues[i].head.event;
 633                 }
 634             }
 635         } finally {
 636             pushPopLock.unlock();
 637         }
 638 
 639         return null;
 640     }
 641 
 642     /**
 643      * Returns the first event with the specified id, if any.
 644      * @param id the id of the type of event desired
 645      * @return the first event of the specified id or <code>null</code>
 646      *    if there is no such event
 647      */
 648     public AWTEvent peekEvent(int id) {
 649         pushPopLock.lock();
 650         try {
 651             for (int i = NUM_PRIORITIES - 1; i >= 0; i--) {
 652                 EventQueueItem q = queues[i].head;
 653                 for (; q != null; q = q.next) {
 654                     if (q.event.getID() == id) {
 655                         return q.event;
 656                     }
 657                 }
 658             }
 659         } finally {
 660             pushPopLock.unlock();
 661         }
 662 
 663         return null;
 664     }
 665 
 666     private static final JavaSecurityAccess javaSecurityAccess =
 667         SharedSecrets.getJavaSecurityAccess();
 668 
 669     /**
 670      * Dispatches an event. The manner in which the event is
 671      * dispatched depends upon the type of the event and the
 672      * type of the event's source object:
 673      * <p> </p>
 674      * <table border=1 summary="Event types, source types, and dispatch methods">
 675      * <tr>
 676      *     <th>Event Type</th>
 677      *     <th>Source Type</th>
 678      *     <th>Dispatched To</th>
 679      * </tr>
 680      * <tr>
 681      *     <td>ActiveEvent</td>
 682      *     <td>Any</td>
 683      *     <td>event.dispatch()</td>
 684      * </tr>
 685      * <tr>
 686      *     <td>Other</td>
 687      *     <td>Component</td>
 688      *     <td>source.dispatchEvent(AWTEvent)</td>
 689      * </tr>
 690      * <tr>
 691      *     <td>Other</td>
 692      *     <td>MenuComponent</td>
 693      *     <td>source.dispatchEvent(AWTEvent)</td>
 694      * </tr>
 695      * <tr>
 696      *     <td>Other</td>
 697      *     <td>Other</td>
 698      *     <td>No action (ignored)</td>
 699      * </tr>
 700      * </table>
 701      * <p> </p>
 702      * @param event an instance of <code>java.awt.AWTEvent</code>,
 703      *          or a subclass of it
 704      * @throws NullPointerException if <code>event</code> is <code>null</code>
 705      * @since           1.2
 706      */
 707     protected void dispatchEvent(final AWTEvent event) {
 708         final Object src = event.getSource();
 709         final PrivilegedAction<Void> action = new PrivilegedAction<Void>() {
 710             public Void run() {
 711                 dispatchEventImpl(event, src);
 712                 return null;
 713             }
 714         };
 715 
 716         final AccessControlContext stack = AccessController.getContext();
 717         final AccessControlContext srcAcc = getAccessControlContextFrom(src);
 718         final AccessControlContext eventAcc = event.getAccessControlContext();
 719         if (srcAcc == null) {
 720             javaSecurityAccess.doIntersectionPrivilege(action, stack, eventAcc);
 721         } else {
 722             javaSecurityAccess.doIntersectionPrivilege(
 723                 new PrivilegedAction<Void>() {
 724                     public Void run() {
 725                         javaSecurityAccess.doIntersectionPrivilege(action, eventAcc);
 726                         return null;
 727                     }
 728                 }, stack, srcAcc);
 729         }
 730     }
 731 
 732     private static AccessControlContext getAccessControlContextFrom(Object src) {
 733         return src instanceof Component ?
 734             ((Component)src).getAccessControlContext() :
 735             src instanceof MenuComponent ?
 736                 ((MenuComponent)src).getAccessControlContext() :
 737                 src instanceof TrayIcon ?
 738                     ((TrayIcon)src).getAccessControlContext() :
 739                     null;
 740     }
 741 
 742     /**
 743      * Called from dispatchEvent() under a correct AccessControlContext
 744      */
 745     private void dispatchEventImpl(final AWTEvent event, final Object src) {
 746         event.isPosted = true;
 747         if (event instanceof ActiveEvent) {
 748             // This could become the sole method of dispatching in time.
 749             setCurrentEventAndMostRecentTimeImpl(event);
 750             ((ActiveEvent)event).dispatch();
 751         } else if (src instanceof Component) {
 752             ((Component)src).dispatchEvent(event);
 753             event.dispatched();
 754         } else if (src instanceof MenuComponent) {
 755             ((MenuComponent)src).dispatchEvent(event);
 756         } else if (src instanceof TrayIcon) {
 757             ((TrayIcon)src).dispatchEvent(event);
 758         } else if (src instanceof AWTAutoShutdown) {
 759             if (noEvents()) {
 760                 dispatchThread.stopDispatching();
 761             }
 762         } else {
 763             if (getEventLog().isLoggable(PlatformLogger.FINE)) {
 764                 getEventLog().fine("Unable to dispatch event: " + event);
 765             }
 766         }
 767     }
 768 
 769     /**
 770      * Returns the timestamp of the most recent event that had a timestamp, and
 771      * that was dispatched from the <code>EventQueue</code> associated with the
 772      * calling thread. If an event with a timestamp is currently being
 773      * dispatched, its timestamp will be returned. If no events have yet
 774      * been dispatched, the EventQueue's initialization time will be
 775      * returned instead.In the current version of
 776      * the JDK, only <code>InputEvent</code>s,
 777      * <code>ActionEvent</code>s, and <code>InvocationEvent</code>s have
 778      * timestamps; however, future versions of the JDK may add timestamps to
 779      * additional event types. Note that this method should only be invoked
 780      * from an application's {@link #isDispatchThread event dispatching thread}.
 781      * If this method is
 782      * invoked from another thread, the current system time (as reported by
 783      * <code>System.currentTimeMillis()</code>) will be returned instead.
 784      *
 785      * @return the timestamp of the last <code>InputEvent</code>,
 786      *         <code>ActionEvent</code>, or <code>InvocationEvent</code> to be
 787      *         dispatched, or <code>System.currentTimeMillis()</code> if this
 788      *         method is invoked on a thread other than an event dispatching
 789      *         thread
 790      * @see java.awt.event.InputEvent#getWhen
 791      * @see java.awt.event.ActionEvent#getWhen
 792      * @see java.awt.event.InvocationEvent#getWhen
 793      * @see #isDispatchThread
 794      *
 795      * @since 1.4
 796      */
 797     public static long getMostRecentEventTime() {
 798         return Toolkit.getEventQueue().getMostRecentEventTimeImpl();
 799     }
 800     private long getMostRecentEventTimeImpl() {
 801         pushPopLock.lock();
 802         try {
 803             return (Thread.currentThread() == dispatchThread)
 804                 ? mostRecentEventTime
 805                 : System.currentTimeMillis();
 806         } finally {
 807             pushPopLock.unlock();
 808         }
 809     }
 810 
 811     /**
 812      * @return most recent event time on all threads.
 813      */
 814     long getMostRecentEventTimeEx() {
 815         pushPopLock.lock();
 816         try {
 817             return mostRecentEventTime;
 818         } finally {
 819             pushPopLock.unlock();
 820         }
 821     }
 822 
 823     /**
 824      * Returns the the event currently being dispatched by the
 825      * <code>EventQueue</code> associated with the calling thread. This is
 826      * useful if a method needs access to the event, but was not designed to
 827      * receive a reference to it as an argument. Note that this method should
 828      * only be invoked from an application's event dispatching thread. If this
 829      * method is invoked from another thread, null will be returned.
 830      *
 831      * @return the event currently being dispatched, or null if this method is
 832      *         invoked on a thread other than an event dispatching thread
 833      * @since 1.4
 834      */
 835     public static AWTEvent getCurrentEvent() {
 836         return Toolkit.getEventQueue().getCurrentEventImpl();
 837     }
 838     private AWTEvent getCurrentEventImpl() {
 839         pushPopLock.lock();
 840         try {
 841                 return (Thread.currentThread() == dispatchThread)
 842                 ? ((AWTEvent)currentEvent.get())
 843                 : null;
 844         } finally {
 845             pushPopLock.unlock();
 846         }
 847     }
 848 
 849     /**
 850      * Replaces the existing <code>EventQueue</code> with the specified one.
 851      * Any pending events are transferred to the new <code>EventQueue</code>
 852      * for processing by it.
 853      *
 854      * @param newEventQueue an <code>EventQueue</code>
 855      *          (or subclass thereof) instance to be use
 856      * @see      java.awt.EventQueue#pop
 857      * @throws NullPointerException if <code>newEventQueue</code> is <code>null</code>
 858      * @since           1.2
 859      */
 860     public void push(EventQueue newEventQueue) {
 861         if (getEventLog().isLoggable(PlatformLogger.FINE)) {
 862             getEventLog().fine("EventQueue.push(" + newEventQueue + ")");
 863         }
 864 
 865         pushPopLock.lock();
 866         try {
 867             EventQueue topQueue = this;
 868             while (topQueue.nextQueue != null) {
 869                 topQueue = topQueue.nextQueue;
 870             }
 871 
 872             if ((topQueue.dispatchThread != null) &&
 873                 (topQueue.dispatchThread.getEventQueue() == this))
 874             {
 875                 newEventQueue.dispatchThread = topQueue.dispatchThread;
 876                 topQueue.dispatchThread.setEventQueue(newEventQueue);
 877             }
 878 
 879             // Transfer all events forward to new EventQueue.
 880             while (topQueue.peekEvent() != null) {
 881                 try {
 882                     // Use getNextEventPrivate() as it doesn't call flushPendingEvents()
 883                     newEventQueue.postEventPrivate(topQueue.getNextEventPrivate());
 884                 } catch (InterruptedException ie) {
 885                     if (getEventLog().isLoggable(PlatformLogger.FINE)) {
 886                         getEventLog().fine("Interrupted push", ie);
 887                     }
 888                 }
 889             }
 890 
 891             // Wake up EDT waiting in getNextEvent(), so it can
 892             // pick up a new EventQueue. Post the waking event before
 893             // topQueue.nextQueue is assigned, otherwise the event would
 894             // go newEventQueue
 895             topQueue.postEventPrivate(new InvocationEvent(topQueue, dummyRunnable));
 896 
 897             newEventQueue.previousQueue = topQueue;
 898             topQueue.nextQueue = newEventQueue;
 899 
 900             if (appContext.get(AppContext.EVENT_QUEUE_KEY) == topQueue) {
 901                 appContext.put(AppContext.EVENT_QUEUE_KEY, newEventQueue);
 902             }
 903 
 904             pushPopCond.signalAll();
 905         } finally {
 906             pushPopLock.unlock();
 907         }
 908     }
 909 
 910     /**
 911      * Stops dispatching events using this <code>EventQueue</code>.
 912      * Any pending events are transferred to the previous
 913      * <code>EventQueue</code> for processing.
 914      * <p>
 915      * Warning: To avoid deadlock, do not declare this method
 916      * synchronized in a subclass.
 917      *
 918      * @exception EmptyStackException if no previous push was made
 919      *  on this <code>EventQueue</code>
 920      * @see      java.awt.EventQueue#push
 921      * @since           1.2
 922      */
 923     protected void pop() throws EmptyStackException {
 924         if (getEventLog().isLoggable(PlatformLogger.FINE)) {
 925             getEventLog().fine("EventQueue.pop(" + this + ")");
 926         }
 927 
 928         pushPopLock.lock();
 929         try {
 930             EventQueue topQueue = this;
 931             while (topQueue.nextQueue != null) {
 932                 topQueue = topQueue.nextQueue;
 933             }
 934             EventQueue prevQueue = topQueue.previousQueue;
 935             if (prevQueue == null) {
 936                 throw new EmptyStackException();
 937             }
 938 
 939             topQueue.previousQueue = null;
 940             prevQueue.nextQueue = null;
 941 
 942             // Transfer all events back to previous EventQueue.
 943             while (topQueue.peekEvent() != null) {
 944                 try {
 945                     prevQueue.postEventPrivate(topQueue.getNextEventPrivate());
 946                 } catch (InterruptedException ie) {
 947                     if (getEventLog().isLoggable(PlatformLogger.FINE)) {
 948                         getEventLog().fine("Interrupted pop", ie);
 949                     }
 950                 }
 951             }
 952 
 953             if ((topQueue.dispatchThread != null) &&
 954                 (topQueue.dispatchThread.getEventQueue() == this))
 955             {
 956                 prevQueue.dispatchThread = topQueue.dispatchThread;
 957                 topQueue.dispatchThread.setEventQueue(prevQueue);
 958             }
 959 
 960             if (appContext.get(AppContext.EVENT_QUEUE_KEY) == this) {
 961                 appContext.put(AppContext.EVENT_QUEUE_KEY, prevQueue);
 962             }
 963 
 964             // Wake up EDT waiting in getNextEvent(), so it can
 965             // pick up a new EventQueue
 966             topQueue.postEventPrivate(new InvocationEvent(topQueue, dummyRunnable));
 967 
 968             pushPopCond.signalAll();
 969         } finally {
 970             pushPopLock.unlock();
 971         }
 972     }
 973 
 974     /**
 975      * Creates a new {@code secondary loop} associated with this
 976      * event queue. Use the {@link SecondaryLoop#enter} and
 977      * {@link SecondaryLoop#exit} methods to start and stop the
 978      * event loop and dispatch the events from this queue.
 979      *
 980      * @return secondaryLoop A new secondary loop object, which can
 981      *                       be used to launch a new nested event
 982      *                       loop and dispatch events from this queue
 983      *
 984      * @see SecondaryLoop#enter
 985      * @see SecondaryLoop#exit
 986      *
 987      * @since 1.7
 988      */
 989     public SecondaryLoop createSecondaryLoop() {
 990         return createSecondaryLoop(null, null, 0);
 991     }
 992 
 993     SecondaryLoop createSecondaryLoop(Conditional cond, EventFilter filter, long interval) {
 994         pushPopLock.lock();
 995         try {
 996             if (nextQueue != null) {
 997                 // Forward the request to the top of EventQueue stack
 998                 return nextQueue.createSecondaryLoop(cond, filter, interval);
 999             }
1000             if (dispatchThread == null) {
1001                 initDispatchThread();
1002             }
1003             return new WaitDispatchSupport(dispatchThread, cond, filter, interval);
1004         } finally {
1005             pushPopLock.unlock();
1006         }
1007     }
1008 
1009     /**
1010      * Returns true if the calling thread is
1011      * {@link Toolkit#getSystemEventQueue the current AWT EventQueue}'s
1012      * dispatch thread. Use this method to ensure that a particular
1013      * task is being executed (or not being) there.
1014      * <p>
1015      * Note: use the {@link #invokeLater} or {@link #invokeAndWait}
1016      * methods to execute a task in
1017      * {@link Toolkit#getSystemEventQueue the current AWT EventQueue}'s
1018      * dispatch thread.
1019      * <p>
1020      *
1021      * @return true if running in
1022      * {@link Toolkit#getSystemEventQueue the current AWT EventQueue}'s
1023      * dispatch thread
1024      * @see             #invokeLater
1025      * @see             #invokeAndWait
1026      * @see             Toolkit#getSystemEventQueue
1027      * @since           1.2
1028      */
1029     public static boolean isDispatchThread() {
1030         EventQueue eq = Toolkit.getEventQueue();
1031         return eq.isDispatchThreadImpl();
1032     }
1033 
1034     final boolean isDispatchThreadImpl() {
1035         EventQueue eq = this;
1036         pushPopLock.lock();
1037         try {
1038             EventQueue next = eq.nextQueue;
1039             while (next != null) {
1040                 eq = next;
1041                 next = eq.nextQueue;
1042             }
1043             return (Thread.currentThread() == eq.dispatchThread);
1044         } finally {
1045             pushPopLock.unlock();
1046         }
1047     }
1048 
1049     final void initDispatchThread() {
1050         pushPopLock.lock();
1051         try {
1052             if (dispatchThread == null && !threadGroup.isDestroyed() && !appContext.isDisposed()) {
1053                 dispatchThread = AccessController.doPrivileged(
1054                     new PrivilegedAction<EventDispatchThread>() {
1055                         public EventDispatchThread run() {
1056                             EventDispatchThread t =
1057                                 new EventDispatchThread(threadGroup,
1058                                                         name,
1059                                                         EventQueue.this);
1060                             t.setContextClassLoader(classLoader);
1061                             t.setPriority(Thread.NORM_PRIORITY + 1);
1062                             t.setDaemon(false);
1063                             AWTAutoShutdown.getInstance().notifyThreadBusy(t);
1064                             return t;
1065                         }
1066                     }
1067                 );
1068                 dispatchThread.start();
1069             }
1070         } finally {
1071             pushPopLock.unlock();
1072         }
1073     }
1074 
1075     final boolean detachDispatchThread(EventDispatchThread edt, boolean forceDetach) {
1076         /*
1077          * This synchronized block is to secure that the event dispatch
1078          * thread won't die in the middle of posting a new event to the
1079          * associated event queue. It is important because we notify
1080          * that the event dispatch thread is busy after posting a new event
1081          * to its queue, so the EventQueue.dispatchThread reference must
1082          * be valid at that point.
1083          */
1084         pushPopLock.lock();
1085         try {
1086             if (edt == dispatchThread) {
1087                 /*
1088                  * Don't detach the thread if any events are pending. Not
1089                  * sure if it's a possible scenario, though.
1090                  *
1091                  * Fix for 4648733. Check both the associated java event
1092                  * queue and the PostEventQueue.
1093                  */
1094                 if (!forceDetach && (peekEvent() != null) || !SunToolkit.isPostEventQueueEmpty()) {
1095                     return false;
1096                 }
1097                 dispatchThread = null;
1098             }
1099             AWTAutoShutdown.getInstance().notifyThreadFree(edt);
1100             return true;
1101         } finally {
1102             pushPopLock.unlock();
1103         }
1104     }
1105 
1106     /*
1107      * Gets the <code>EventDispatchThread</code> for this
1108      * <code>EventQueue</code>.
1109      * @return the event dispatch thread associated with this event queue
1110      *         or <code>null</code> if this event queue doesn't have a
1111      *         working thread associated with it
1112      * @see    java.awt.EventQueue#initDispatchThread
1113      * @see    java.awt.EventQueue#detachDispatchThread
1114      */
1115     final EventDispatchThread getDispatchThread() {
1116         pushPopLock.lock();
1117         try {
1118             return dispatchThread;
1119         } finally {
1120             pushPopLock.unlock();
1121         }
1122     }
1123 
1124     /*
1125      * Removes any pending events for the specified source object.
1126      * If removeAllEvents parameter is <code>true</code> then all
1127      * events for the specified source object are removed, if it
1128      * is <code>false</code> then <code>SequencedEvent</code>, <code>SentEvent</code>,
1129      * <code>FocusEvent</code>, <code>WindowEvent</code>, <code>KeyEvent</code>,
1130      * and <code>InputMethodEvent</code> are kept in the queue, but all other
1131      * events are removed.
1132      *
1133      * This method is normally called by the source's
1134      * <code>removeNotify</code> method.
1135      */
1136     final void removeSourceEvents(Object source, boolean removeAllEvents) {
1137         SunToolkit.flushPendingEvents(appContext);
1138         pushPopLock.lock();
1139         try {
1140             for (int i = 0; i < NUM_PRIORITIES; i++) {
1141                 EventQueueItem entry = queues[i].head;
1142                 EventQueueItem prev = null;
1143                 while (entry != null) {
1144                     if ((entry.event.getSource() == source)
1145                         && (removeAllEvents
1146                             || ! (entry.event instanceof SequencedEvent
1147                                   || entry.event instanceof SentEvent
1148                                   || entry.event instanceof FocusEvent
1149                                   || entry.event instanceof WindowEvent
1150                                   || entry.event instanceof KeyEvent
1151                                   || entry.event instanceof InputMethodEvent)))
1152                     {
1153                         if (entry.event instanceof SequencedEvent) {
1154                             ((SequencedEvent)entry.event).dispose();
1155                         }
1156                         if (entry.event instanceof SentEvent) {
1157                             ((SentEvent)entry.event).dispose();
1158                         }
1159                         if (entry.event instanceof InvocationEvent) {
1160                             AWTAccessor.getInvocationEventAccessor()
1161                                     .dispose((InvocationEvent)entry.event);
1162                         }
1163                         if (prev == null) {
1164                             queues[i].head = entry.next;
1165                         } else {
1166                             prev.next = entry.next;
1167                         }
1168                         uncacheEQItem(entry);
1169                     } else {
1170                         prev = entry;
1171                     }
1172                     entry = entry.next;
1173                 }
1174                 queues[i].tail = prev;
1175             }
1176         } finally {
1177             pushPopLock.unlock();
1178         }
1179     }
1180 
1181     static void setCurrentEventAndMostRecentTime(AWTEvent e) {
1182         Toolkit.getEventQueue().setCurrentEventAndMostRecentTimeImpl(e);
1183     }
1184     private void setCurrentEventAndMostRecentTimeImpl(AWTEvent e) {
1185         pushPopLock.lock();
1186         try {
1187             if (Thread.currentThread() != dispatchThread) {
1188                 return;
1189             }
1190 
1191             currentEvent = new WeakReference(e);
1192 
1193             // This series of 'instanceof' checks should be replaced with a
1194             // polymorphic type (for example, an interface which declares a
1195             // getWhen() method). However, this would require us to make such
1196             // a type public, or to place it in sun.awt. Both of these approaches
1197             // have been frowned upon. So for now, we hack.
1198             //
1199             // In tiger, we will probably give timestamps to all events, so this
1200             // will no longer be an issue.
1201             long mostRecentEventTime2 = Long.MIN_VALUE;
1202             if (e instanceof InputEvent) {
1203                 InputEvent ie = (InputEvent)e;
1204                 mostRecentEventTime2 = ie.getWhen();
1205             } else if (e instanceof InputMethodEvent) {
1206                 InputMethodEvent ime = (InputMethodEvent)e;
1207                 mostRecentEventTime2 = ime.getWhen();
1208             } else if (e instanceof ActionEvent) {
1209                 ActionEvent ae = (ActionEvent)e;
1210                 mostRecentEventTime2 = ae.getWhen();
1211             } else if (e instanceof InvocationEvent) {
1212                 InvocationEvent ie = (InvocationEvent)e;
1213                 mostRecentEventTime2 = ie.getWhen();
1214             }
1215             mostRecentEventTime = Math.max(mostRecentEventTime, mostRecentEventTime2);
1216         } finally {
1217             pushPopLock.unlock();
1218         }
1219     }
1220 
1221     /**
1222      * Causes <code>runnable</code> to have its <code>run</code>
1223      * method called in the {@link #isDispatchThread dispatch thread} of
1224      * {@link Toolkit#getSystemEventQueue the system EventQueue}.
1225      * This will happen after all pending events are processed.
1226      *
1227      * @param runnable  the <code>Runnable</code> whose <code>run</code>
1228      *                  method should be executed
1229      *                  asynchronously in the
1230      *                  {@link #isDispatchThread event dispatch thread}
1231      *                  of {@link Toolkit#getSystemEventQueue the system EventQueue}
1232      * @see             #invokeAndWait
1233      * @see             Toolkit#getSystemEventQueue
1234      * @see             #isDispatchThread
1235      * @since           1.2
1236      */
1237     public static void invokeLater(Runnable runnable) {
1238         Toolkit.getEventQueue().postEvent(
1239             new InvocationEvent(Toolkit.getDefaultToolkit(), runnable));
1240     }
1241 
1242     /**
1243      * Causes <code>runnable</code> to have its <code>run</code>
1244      * method called in the {@link #isDispatchThread dispatch thread} of
1245      * {@link Toolkit#getSystemEventQueue the system EventQueue}.
1246      * This will happen after all pending events are processed.
1247      * The call blocks until this has happened.  This method
1248      * will throw an Error if called from the
1249      * {@link #isDispatchThread event dispatcher thread}.
1250      *
1251      * @param runnable  the <code>Runnable</code> whose <code>run</code>
1252      *                  method should be executed
1253      *                  synchronously in the
1254      *                  {@link #isDispatchThread event dispatch thread}
1255      *                  of {@link Toolkit#getSystemEventQueue the system EventQueue}
1256      * @exception       InterruptedException  if any thread has
1257      *                  interrupted this thread
1258      * @exception       InvocationTargetException  if an throwable is thrown
1259      *                  when running <code>runnable</code>
1260      * @see             #invokeLater
1261      * @see             Toolkit#getSystemEventQueue
1262      * @see             #isDispatchThread
1263      * @since           1.2
1264      */
1265     public static void invokeAndWait(Runnable runnable)
1266         throws InterruptedException, InvocationTargetException
1267     {
1268         invokeAndWait(Toolkit.getDefaultToolkit(), runnable);
1269     }
1270 
1271     static void invokeAndWait(Object source, Runnable runnable)
1272         throws InterruptedException, InvocationTargetException
1273     {
1274         if (EventQueue.isDispatchThread()) {
1275             throw new Error("Cannot call invokeAndWait from the event dispatcher thread");
1276         }
1277 
1278         class AWTInvocationLock {}
1279         Object lock = new AWTInvocationLock();
1280 
1281         InvocationEvent event =
1282             new InvocationEvent(source, runnable, lock, true);
1283 
1284         synchronized (lock) {
1285             Toolkit.getEventQueue().postEvent(event);
1286             while (!event.isDispatched()) {
1287                 lock.wait();
1288             }
1289         }
1290 
1291         Throwable eventThrowable = event.getThrowable();
1292         if (eventThrowable != null) {
1293             throw new InvocationTargetException(eventThrowable);
1294         }
1295     }
1296 
1297     /*
1298      * Called from PostEventQueue.postEvent to notify that a new event
1299      * appeared. First it proceeds to the EventQueue on the top of the
1300      * stack, then notifies the associated dispatch thread if it exists
1301      * or starts a new one otherwise.
1302      */
1303     private void wakeup(boolean isShutdown) {
1304         pushPopLock.lock();
1305         try {
1306             if (nextQueue != null) {
1307                 // Forward call to the top of EventQueue stack.
1308                 nextQueue.wakeup(isShutdown);
1309             } else if (dispatchThread != null) {
1310                 pushPopCond.signalAll();
1311             } else if (!isShutdown) {
1312                 initDispatchThread();
1313             }
1314         } finally {
1315             pushPopLock.unlock();
1316         }
1317     }
1318 }
1319 
1320 /**
1321  * The Queue object holds pointers to the beginning and end of one internal
1322  * queue. An EventQueue object is composed of multiple internal Queues, one
1323  * for each priority supported by the EventQueue. All Events on a particular
1324  * internal Queue have identical priority.
1325  */
1326 class Queue {
1327     EventQueueItem head;
1328     EventQueueItem tail;
1329 }