/* * @(#)EventQueue.java 1.96 04/06/28 * * Copyright 2004 Sun Microsystems, Inc. All rights reserved. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. */ package java.awt; import java.awt.event.ActionEvent; import java.awt.event.FocusEvent; import java.awt.event.InputEvent; import java.awt.event.InputMethodEvent; import java.awt.event.InvocationEvent; import java.awt.event.KeyEvent; import java.awt.event.MouseEvent; import java.awt.event.PaintEvent; import java.awt.event.WindowEvent; import java.awt.ActiveEvent; import java.awt.peer.ComponentPeer; import java.awt.peer.LightweightPeer; import java.util.EmptyStackException; import java.lang.ref.WeakReference; import java.lang.reflect.InvocationTargetException; import java.security.AccessController; import java.security.PrivilegedAction; import sun.awt.PeerEvent; import sun.awt.SunToolkit; import sun.awt.DebugHelper; import sun.awt.AWTAutoShutdown; import sun.awt.AppContext; /** * EventQueue is a platform-independent class * that queues events, both from the underlying peer classes * and from trusted application classes. *

* It encapsulates asynchronous event dispatch machinery which * extracts events from the queue and dispatches them by calling * {@link #dispatchEvent(AWTEvent) dispatchEvent(AWTEvent)} method * on this EventQueue with the event to be dispatched * as an argument. The particular behavior of this machinery is * implementation-dependent. The only requirements are that events * which were actually enqueued to this queue (note that events * being posted to the EventQueue can be coalesced) * are dispatched: *

*
Sequentially. *
That is, it is not permitted that several events from * this queue are dispatched simultaneously. *
In the same order as they are enqueued. *
That is, if AWTEvent A is enqueued * to the EventQueue before * AWTEvent B then event B will not be * dispatched before event A. *
*

* Some browsers partition applets in different code bases into * separate contexts, and establish walls between these contexts. * In such a scenario, there will be one EventQueue * per context. Other browsers place all applets into the same * context, implying that there will be only a single, global * EventQueue for all applets. This behavior is * implementation-dependent. Consult your browser's documentation * for more information. *

* For information on the threading issues of the event dispatch * machinery, see AWT Threading * Issues. * * @author Thomas Ball * @author Fred Ecks * @author David Mendenhall * * @version 1.96, 06/28/04 * @since 1.1 */ public class EventQueue { private static final DebugHelper dbg = DebugHelper.create(EventQueue.class); // From Thread.java private static int threadInitNumber; private static synchronized int nextThreadNum() { return threadInitNumber++; } private static final int LOW_PRIORITY = 0; private static final int NORM_PRIORITY = 1; private static final int HIGH_PRIORITY = 2; private static final int ULTIMATE_PRIORITY = 3; private static final int NUM_PRIORITIES = ULTIMATE_PRIORITY + 1; /* * We maintain one Queue for each priority that the EventQueue supports. * That is, the EventQueue object is actually implemented as * NUM_PRIORITIES queues and all Events on a particular internal Queue * have identical priority. Events are pulled off the EventQueue starting * with the Queue of highest priority. We progress in decreasing order * across all Queues. */ private Queue[] queues = new Queue[NUM_PRIORITIES]; /* * The next EventQueue on the stack, or null if this EventQueue is * on the top of the stack. If nextQueue is non-null, requests to post * an event are forwarded to nextQueue. */ private EventQueue nextQueue; /* * The previous EventQueue on the stack, or null if this is the * "base" EventQueue. */ private EventQueue previousQueue; private EventDispatchThread dispatchThread; private final ThreadGroup threadGroup = Thread.currentThread().getThreadGroup(); private final ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); /* * Debugging flag -- set true and recompile to enable checking. */ private final static boolean debug = false; /* * The time stamp of the last dispatched InputEvent or ActionEvent. */ private long mostRecentEventTime = System.currentTimeMillis(); /** * The modifiers field of the current event, if the current event is an * InputEvent or ActionEvent. */ private WeakReference currentEvent; /* * Non-zero if a thread is waiting in getNextEvent(int) for an event of * a particular ID to be posted to the queue. */ private int waitForID; private final String name = "AWT-EventQueue-" + nextThreadNum(); public EventQueue() { for (int i = 0; i < NUM_PRIORITIES; i++) { queues[i] = new Queue(); } /* * NOTE: if you ever have to start the associated event dispatch * thread at this point, be aware of the following problem: * If this EventQueue instance is created in * SunToolkit.createNewAppContext() the started dispatch thread * may call AppContext.getAppContext() before createNewAppContext() * completes thus causing mess in thread group to appcontext mapping. */ } /** * Posts a 1.1-style event to the EventQueue. * If there is an existing event on the queue with the same ID * and event source, the source Component's * coalesceEvents method will be called. * * @param theEvent an instance of java.awt.AWTEvent, * or a subclass of it * @throws NullPointerException if theEvent is null */ public void postEvent(AWTEvent theEvent) { SunToolkit.flushPendingEvents(); postEventPrivate(theEvent); } /** * Posts a 1.1-style event to the EventQueue. * If there is an existing event on the queue with the same ID * and event source, the source Component's * coalesceEvents method will be called. * * @param theEvent an instance of java.awt.AWTEvent, * or a subclass of it */ final void postEventPrivate(AWTEvent theEvent) { theEvent.isPosted = true; synchronized(this) { int id = theEvent.getID(); if (nextQueue != null) { // Forward event to top of EventQueue stack. nextQueue.postEventPrivate(theEvent); } else if (theEvent instanceof PeerEvent && (((PeerEvent)theEvent).getFlags() & PeerEvent.ULTIMATE_PRIORITY_EVENT) != 0) { postEvent(theEvent, ULTIMATE_PRIORITY); } else if (theEvent instanceof PeerEvent && (((PeerEvent)theEvent).getFlags() & PeerEvent.PRIORITY_EVENT) != 0) { postEvent(theEvent, HIGH_PRIORITY); } else if (id == PaintEvent.PAINT || id == PaintEvent.UPDATE) { postEvent(theEvent, LOW_PRIORITY); } else { postEvent(theEvent, NORM_PRIORITY); } } } /** * Posts the event to the internal Queue of specified priority, * coalescing as appropriate. * * @param theEvent an instance of java.awt.AWTEvent, * or a subclass of it * @param priority the desired priority of the event */ private void postEvent(AWTEvent theEvent, int priority) { if (dispatchThread == null) { if (theEvent.getSource() == AWTAutoShutdown.getInstance()) { return; } else { initDispatchThread(); } } Object source = theEvent.getSource(); // Expanding RepaintArea if (source instanceof Component) { ComponentPeer sourcePeer = ((Component)source).peer; if (sourcePeer != null && theEvent instanceof PaintEvent && !(sourcePeer instanceof LightweightPeer)) { sourcePeer.coalescePaintEvent((PaintEvent)theEvent); } } EventQueueItem newItem = new EventQueueItem(theEvent); boolean notifyID = (theEvent.getID() == this.waitForID); if (queues[priority].head == null) { boolean shouldNotify = noEvents(); queues[priority].head = queues[priority].tail = newItem; if (shouldNotify) { if (theEvent.getSource() != AWTAutoShutdown.getInstance()) { AWTAutoShutdown.getInstance().notifyThreadBusy(dispatchThread); } notifyAll(); } else if (notifyID) { notifyAll(); } } else { boolean isPeerEvent = theEvent instanceof PeerEvent; // For Component source events, traverse the entire list, // trying to coalesce events if (source instanceof Component) { EventQueueItem q = queues[priority].head; if (theEvent.id == Event.MOUSE_MOVE || theEvent.id == Event.MOUSE_DRAG) { EventQueueItem qm; for(qm = q; qm != null; qm = qm.next) { if ((qm.event instanceof MouseEvent) && qm.id != theEvent.id) { q = qm; } } } for (; q != null; q = q.next) { // Give Component.coalesceEvents a chance if (q.event.getSource() == source && q.id == newItem.id) { AWTEvent coalescedEvent = ((Component)source).coalesceEvents(q.event, theEvent); if (isPeerEvent && coalescedEvent == null && q.event instanceof PeerEvent) { coalescedEvent = ((PeerEvent)q.event).coalesceEvents((PeerEvent)theEvent); } if (coalescedEvent != null) { // Remove debugging statement because // calling AWTEvent.toString here causes a // deadlock. q.event = coalescedEvent; return; } } } } // The event was not coalesced or has non-Component source. // Insert it at the end of the appropriate Queue. queues[priority].tail.next = newItem; queues[priority].tail = newItem; if (notifyID) { notifyAll(); } } } /** * Returns whether an event is pending on any of the separate * Queues. * @return whether an event is pending on any of the separate Queues */ private boolean noEvents() { for (int i = 0; i < NUM_PRIORITIES; i++) { if (queues[i].head != null) { return false; } } return true; } /** * Removes an event from the EventQueue and * returns it. This method will block until an event has * been posted by another thread. * @return the next AWTEvent * @exception InterruptedException * if another thread has interrupted this thread */ public AWTEvent getNextEvent() throws InterruptedException { do { /* * SunToolkit.flushPendingEvents must be called outside * of the synchronized block to avoid deadlock when * event queues are nested with push()/pop(). */ SunToolkit.flushPendingEvents(); synchronized (this) { for (int i = NUM_PRIORITIES - 1; i >= 0; i--) { if (queues[i].head != null) { EventQueueItem eqi = queues[i].head; queues[i].head = eqi.next; if (eqi.next == null) { queues[i].tail = null; } return eqi.event; } } AWTAutoShutdown.getInstance().notifyThreadFree(dispatchThread); wait(); } } while(true); } AWTEvent getNextEvent(int id) throws InterruptedException { do { /* * SunToolkit.flushPendingEvents must be called outside * of the synchronized block to avoid deadlock when * event queues are nested with push()/pop(). */ SunToolkit.flushPendingEvents(); synchronized (this) { for (int i = 0; i < NUM_PRIORITIES; i++) { for (EventQueueItem entry = queues[i].head, prev = null; entry != null; prev = entry, entry = entry.next) { if (entry.id == id) { if (prev == null) { queues[i].head = entry.next; } else { prev.next = entry.next; } if (queues[i].tail == entry) { queues[i].tail = prev; } return entry.event; } } } this.waitForID = id; wait(); this.waitForID = 0; } } while(true); } /** * Returns the first event on the EventQueue * without removing it. * @return the first event */ public synchronized AWTEvent peekEvent() { for (int i = NUM_PRIORITIES - 1; i >= 0; i--) { if (queues[i].head != null) { return queues[i].head.event; } } return null; } /** * Returns the first event with the specified id, if any. * @param id the id of the type of event desired * @return the first event of the specified id or null * if there is no such event */ public synchronized AWTEvent peekEvent(int id) { for (int i = NUM_PRIORITIES - 1; i >= 0; i--) { EventQueueItem q = queues[i].head; for (; q != null; q = q.next) { if (q.id == id) { return q.event; } } } return null; } /** * Dispatches an event. The manner in which the event is * dispatched depends upon the type of the event and the * type of the event's source object: *

* * * * * * * * * * * * * * * * * * * * * * * * * * *
Event TypeSource TypeDispatched To
ActiveEventAnyevent.dispatch()
OtherComponentsource.dispatchEvent(AWTEvent)
OtherMenuComponentsource.dispatchEvent(AWTEvent)
OtherOtherNo action (ignored)
*

* @param event an instance of java.awt.AWTEvent, * or a subclass of it * @throws NullPointerException if event is null */ protected void dispatchEvent(AWTEvent event) { event.isPosted = true; Object src = event.getSource(); if (event instanceof ActiveEvent) { // This could become the sole method of dispatching in time. setCurrentEventAndMostRecentTimeImpl(event); ((ActiveEvent)event).dispatch(); } else if (src instanceof Component) { ((Component)src).dispatchEvent(event); event.dispatched(); } else if (src instanceof MenuComponent) { ((MenuComponent)src).dispatchEvent(event); } else if (src instanceof AWTAutoShutdown) { if (noEvents()) { dispatchThread.stopDispatching(); } } else { System.err.println("unable to dispatch event: " + event); } } /** * Returns the timestamp of the most recent event that had a timestamp, and * that was dispatched from the EventQueue associated with the * calling thread. If an event with a timestamp is currently being * dispatched, its timestamp will be returned. If no events have yet * been dispatched, the EventQueue's initialization time will be * returned instead.In the current version of * the JDK, only InputEvents, * ActionEvents, and InvocationEvents have * timestamps; however, future versions of the JDK may add timestamps to * additional event types. Note that this method should only be invoked * from an application's event dispatching thread. If this method is * invoked from another thread, the current system time (as reported by * System.currentTimeMillis()) will be returned instead. * * @return the timestamp of the last InputEvent, * ActionEvent, or InvocationEvent to be * dispatched, or System.currentTimeMillis() if this * method is invoked on a thread other than an event dispatching * thread * @see java.awt.event.InputEvent#getWhen * @see java.awt.event.ActionEvent#getWhen * @see java.awt.event.InvocationEvent#getWhen * * @since 1.4 */ public static long getMostRecentEventTime() { return Toolkit.getEventQueue().getMostRecentEventTimeImpl(); } private synchronized long getMostRecentEventTimeImpl() { return (Thread.currentThread() == dispatchThread) ? mostRecentEventTime : System.currentTimeMillis(); } /** * @return most recent event time on all threads. */ synchronized long getMostRecentEventTimeEx() { return mostRecentEventTime; } /** * Returns the the event currently being dispatched by the * EventQueue associated with the calling thread. This is * useful if a method needs access to the event, but was not designed to * receive a reference to it as an argument. Note that this method should * only be invoked from an application's event dispatching thread. If this * method is invoked from another thread, null will be returned. * * @return the event currently being dispatched, or null if this method is * invoked on a thread other than an event dispatching thread * @since 1.4 */ public static AWTEvent getCurrentEvent() { return Toolkit.getEventQueue().getCurrentEventImpl(); } private synchronized AWTEvent getCurrentEventImpl() { return (Thread.currentThread() == dispatchThread) ? ((AWTEvent)currentEvent.get()) : null; } /** * Replaces the existing EventQueue with the specified one. * Any pending events are transferred to the new EventQueue * for processing by it. * * @param newEventQueue an EventQueue * (or subclass thereof) instance to be use * @see java.awt.EventQueue#pop * @throws NullPointerException if newEventQueue is null */ public synchronized void push(EventQueue newEventQueue) { if (debug) { System.out.println("EventQueue.push(" + newEventQueue + ")"); } if (nextQueue != null) { nextQueue.push(newEventQueue); return; } synchronized (newEventQueue) { // Transfer all events forward to new EventQueue. while (peekEvent() != null) { try { newEventQueue.postEventPrivate(getNextEvent()); } catch (InterruptedException ie) { if (debug) { System.err.println("interrupted push:"); ie.printStackTrace(System.err); } } } newEventQueue.previousQueue = this; } /* * Stop the event dispatch thread associated with the currently * active event queue, so that after the new queue is pushed * on the top this event dispatch thread won't prevent AWT from * being automatically shut down. * Use stopDispatchingLater() to avoid deadlock: stopDispatching() * waits for the dispatch thread to exit, so if the dispatch * thread attempts to synchronize on this EventQueue object * it will never exit since we already hold this lock. */ if (dispatchThread != null) { dispatchThread.stopDispatchingLater(); } nextQueue = newEventQueue; AppContext appContext = AppContext.getAppContext(); if (appContext.get(AppContext.EVENT_QUEUE_KEY) == this) { appContext.put(AppContext.EVENT_QUEUE_KEY, newEventQueue); } } /** * Stops dispatching events using this EventQueue. * Any pending events are transferred to the previous * EventQueue for processing. *

* Warning: To avoid deadlock, do not declare this method * synchronized in a subclass. * * @exception EmptyStackException if no previous push was made * on this EventQueue * @see java.awt.EventQueue#push */ protected void pop() throws EmptyStackException { if (debug) { System.out.println("EventQueue.pop(" + this + ")"); } // To prevent deadlock, we lock on the previous EventQueue before // this one. This uses the same locking order as everything else // in EventQueue.java, so deadlock isn't possible. EventQueue prev = previousQueue; synchronized ((prev != null) ? prev : this) { synchronized(this) { if (nextQueue != null) { nextQueue.pop(); return; } if (previousQueue == null) { throw new EmptyStackException(); } // Transfer all events back to previous EventQueue. previousQueue.nextQueue = null; while (peekEvent() != null) { try { previousQueue.postEventPrivate(getNextEvent()); } catch (InterruptedException ie) { if (debug) { System.err.println("interrupted pop:"); ie.printStackTrace(System.err); } } } AppContext appContext = AppContext.getAppContext(); if (appContext.get(AppContext.EVENT_QUEUE_KEY) == this) { appContext.put(AppContext.EVENT_QUEUE_KEY, previousQueue); } previousQueue = null; } } EventDispatchThread dt = this.dispatchThread; if (dt != null) { dt.stopDispatching(); // Must be done outside synchronized // block to avoid possible deadlock } } /** * Returns true if the calling thread is the current AWT * EventQueue's dispatch thread. Use this * call the ensure that a given * task is being executed (or not being) on the current AWT * EventDispatchThread. * * @return true if running on the current AWT * EventQueue's dispatch thread */ public static boolean isDispatchThread() { EventQueue eq = Toolkit.getEventQueue(); EventQueue next = eq.nextQueue; while (next != null) { eq = next; next = eq.nextQueue; } return (Thread.currentThread() == eq.dispatchThread); } final void initDispatchThread() { synchronized (this) { if (dispatchThread == null && !threadGroup.isDestroyed()) { dispatchThread = (EventDispatchThread) AccessController.doPrivileged(new PrivilegedAction() { public Object run() { EventDispatchThread t = new EventDispatchThread(threadGroup, name, EventQueue.this); t.setContextClassLoader(classLoader); t.setPriority(Thread.NORM_PRIORITY + 1); t.setDaemon(false); return t; } }); AWTAutoShutdown.getInstance().notifyThreadBusy(dispatchThread); dispatchThread.start(); } } } final void detachDispatchThread() { dispatchThread = null; } /* * Gets the EventDispatchThread for this * EventQueue. * @return the event dispatch thread associated with this event queue * or null if this event queue doesn't have a * working thread associated with it * @see java.awt.EventQueue#initDispatchThread * @see java.awt.EventQueue#detachDispatchThread */ final EventDispatchThread getDispatchThread() { return dispatchThread; } /* * Removes any pending events for the specified source object. * If removeAllEvents parameter is true then all * events for the specified source object are removed, if it * is false then SequencedEvent, SentEvent, * FocusEvent, WindowEvent, KeyEvent, * and InputMethodEvent are kept in the queue, but all other * events are removed. * * This method is normally called by the source's * removeNotify method. */ final void removeSourceEvents(Object source, boolean removeAllEvents) { SunToolkit.flushPendingEvents(); synchronized (this) { for (int i = 0; i < NUM_PRIORITIES; i++) { EventQueueItem entry = queues[i].head; EventQueueItem prev = null; while (entry != null) { if ((entry.event.getSource() == source) && (removeAllEvents || ! (entry.event instanceof SequencedEvent || entry.event instanceof SentEvent || entry.event instanceof FocusEvent || entry.event instanceof WindowEvent || entry.event instanceof KeyEvent || entry.event instanceof InputMethodEvent))) { if (entry.event instanceof SequencedEvent) { ((SequencedEvent)entry.event).dispose(); } if (entry.event instanceof SentEvent) { ((SentEvent)entry.event).dispose(); } if (prev == null) { queues[i].head = entry.next; } else { prev.next = entry.next; } } else { prev = entry; } entry = entry.next; } queues[i].tail = prev; } } } static void setCurrentEventAndMostRecentTime(AWTEvent e) { Toolkit.getEventQueue().setCurrentEventAndMostRecentTimeImpl(e); } private synchronized void setCurrentEventAndMostRecentTimeImpl(AWTEvent e) { if (Thread.currentThread() != dispatchThread) { return; } currentEvent = new WeakReference(e); // This series of 'instanceof' checks should be replaced with a // polymorphic type (for example, an interface which declares a // getWhen() method). However, this would require us to make such // a type public, or to place it in sun.awt. Both of these approaches // have been frowned upon. So for now, we hack. // // In tiger, we will probably give timestamps to all events, so this // will no longer be an issue. if (e instanceof InputEvent) { InputEvent ie = (InputEvent)e; mostRecentEventTime = ie.getWhen(); } else if (e instanceof InputMethodEvent) { InputMethodEvent ime = (InputMethodEvent)e; mostRecentEventTime = ime.getWhen(); } else if (e instanceof ActionEvent) { ActionEvent ae = (ActionEvent)e; mostRecentEventTime = ae.getWhen(); } else if (e instanceof InvocationEvent) { InvocationEvent ie = (InvocationEvent)e; mostRecentEventTime = ie.getWhen(); } } /** * Causes runnable to have its run * method called in the dispatch thread of the EventQueue. * This will happen after all pending events are processed. * * @param runnable the Runnable whose run * method should be executed * synchronously on the EventQueue * @see #invokeAndWait * @since 1.2 */ public static void invokeLater(Runnable runnable) { Toolkit.getEventQueue().postEvent( new InvocationEvent(Toolkit.getDefaultToolkit(), runnable)); } /** * Causes runnable to have its run * method called in the dispatch thread of the EventQueue. * This will happen after all pending events are processed. * The call blocks until this has happened. This method * will throw an Error if called from the event dispatcher thread. * * @param runnable the Runnable whose run * method should be executed * synchronously on the EventQueue * @exception InterruptedException if another thread has * interrupted this thread * @exception InvocationTargetException if an throwable is thrown * when running runnable * @see #invokeLater * @since 1.2 */ public static void invokeAndWait(Runnable runnable) throws InterruptedException, InvocationTargetException { if (EventQueue.isDispatchThread()) { throw new Error("Cannot call invokeAndWait from the event dispatcher thread"); } class AWTInvocationLock {} Object lock = new AWTInvocationLock(); InvocationEvent event = new InvocationEvent(Toolkit.getDefaultToolkit(), runnable, lock, true); synchronized (lock) { Toolkit.getEventQueue().postEvent(event); lock.wait(); } Throwable eventThrowable = event.getThrowable(); if (eventThrowable != null) { throw new InvocationTargetException(eventThrowable); } } /* * Called from PostEventQueue.postEvent to notify that a new event * appeared. First it proceeds to the EventQueue on the top of the * stack, then notifies the associated dispatch thread if it exists * or starts a new one otherwise. */ private void wakeup(boolean isShutdown) { synchronized(this) { if (nextQueue != null) { // Forward call to the top of EventQueue stack. nextQueue.wakeup(isShutdown); } else if (dispatchThread != null) { notifyAll(); } else if (!isShutdown) { initDispatchThread(); } } } } /** * The Queue object holds pointers to the beginning and end of one internal * queue. An EventQueue object is composed of multiple internal Queues, one * for each priority supported by the EventQueue. All Events on a particular * internal Queue have identical priority. */ class Queue { EventQueueItem head; EventQueueItem tail; } class EventQueueItem { AWTEvent event; int id; EventQueueItem next; EventQueueItem(AWTEvent evt) { event = evt; id = evt.getID(); } }