/* Wotonomy: OpenStep design patterns for pure Java applications. Copyright (C) 2001 Intersect Software Corporation This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, see http://www.gnu.org */ package net.wotonomy.foundation; import java.awt.AWTEvent; import java.awt.EventQueue; import java.awt.Toolkit; import java.awt.event.InvocationEvent; import java.util.EmptyStackException; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; /** * NSRunLoop is provided specifically for EODelayedObserverQueue and * EOEditingContext, which assume the existence of a prioritized event queue * that Java does not provide.
*
* * This extends java.awt.EventQueue and does not conform to the NSRunLoop * specifications. The only supported methods are NSRunLoop.currentRunLoop, * performSelectorWithOrder, and cancelSelectorWithOrder. Note that in Swing * there is only one AWT thread and one event queue; newly created threads will * not get their own run loop as in OpenStep.
*
* * That said, this event queue is servicable as a replacement for the default * event queue and will provide prioritized execution of selectors before and * after normal AWT events.
*
* * Each run loop dispatches the lowest order event from the queue. When queued * events have the same ordering, they are dispatched as first-in, first-out * (FIFO). Because all AWT events have the same ordering * (AWTEventsRunLoopOrdering), they are processed FIFO, just like the default * event queue.
*
* * Note that because EventQueue is not well-factored for subclassing, pushing a * new event queue onto the stack on top of this one will only copy the existing * AWT events to the new queue. For this reason, pushing new event queues onto * the stack is not supported and will throw an exception. * * @author michael@mpowers.net * @author $Author: cgruber $ * @version $Revision: 893 $ */ public class NSRunLoop extends EventQueue { /** * This is the ordering at which the conventional AWT event queue will be * executed. Selectors with this ordering or less will be executed before AWT * events, and selectors with ordering greater than this ordering will be be * executed after AWT events. */ public static final int AWTEventsRunLoopOrdering = 500000; /** * The singleton instance. */ protected static NSRunLoop instance; private LinkedList earlyQueue; private LinkedList lateQueue; /** * Needed because JDK1.4 made our lives more difficult. */ private static Toolkit toolkit; /** * Because SunToolkit.flushPendingEvents was changed to a static method in 1.4, * you can't compile the library in such a way that it works with both 1.3 and * 1.4. So we have to rely on dynamic method invocation, which is slower, but we * try to make it as fast as humanly possible. */ private static NSSelector flushPendingEvents; /** * Create a new instance of NSRunLoop. */ protected NSRunLoop() { earlyQueue = new LinkedList(); lateQueue = new LinkedList(); } /** * Returns the singleton instance of NSRunLoop. This returns the same instance * no matter what thread calls it, which is different from OpenStep. NSRunLoop * is limited to a singleton instance because there is no way of obtaining the * stack of event queues from EventQueue because it is private state. */ public synchronized static NSRunLoop currentRunLoop() { if (instance == null) { // create and initialize flushPendingEvents = new NSSelector("flushPendingEvents"); toolkit = Toolkit.getDefaultToolkit(); instance = new NSRunLoop(); toolkit.getSystemEventQueue().push(instance); } return instance; } /** * Post 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. */ public void postEvent(AWTEvent theEvent) { if (theEvent instanceof OrderedInvocationEvent) { OrderedInvocationEvent event = (OrderedInvocationEvent) theEvent; if (event.getOrdering() > AWTEventsRunLoopOrdering) { insertEventIntoQueue(event, lateQueue); } else { insertEventIntoQueue(event, earlyQueue); } } else { super.postEvent(theEvent); } } private synchronized void insertEventIntoQueue(OrderedInvocationEvent e, LinkedList q) { OrderedInvocationEvent o; int ordering = e.getOrdering(); ListIterator iterator = q.listIterator(); // iterate forwards until we find a priority // greater than our priority, // then insert ourself before that element. while (iterator.hasNext()) { o = (OrderedInvocationEvent) iterator.next(); if (o.getOrdering() > ordering) { // back up one iterator.previous(); break; } } // add after the current element iterator.add(e); } /** * Useful method, but not in the spec. Dispatches the next AWT event in the * queue. Returns whether a selector or an event was executed: if the event * queue is empty, returns false. */ public boolean dispatchNextEvent() { // check for empty queue to avoid blocking if (peekEvent() == null) { return false; } // queue not empty: dispatch the next event try { dispatchEvent(getNextEvent()); } catch (InterruptedException exc) { System.out.println("NSRunLoop: error while dispatching event: "); exc.printStackTrace(); } return true; } /** * Useful method, but not in the spec. Dispatches all events in the queue before * returning. */ public void dispatchAllEvents() { while (dispatchNextEvent()) ; } /** * Remove an event from the EventQueue and return it. This override will * dispatch all selectors up to 5000, and then check if there are AWT events on * the queue. If the queue is empty, all remaining selectors are dispatched. * Then, this method calls the super class' implementation. * * @return the next AWTEvent * @exception InterruptedException if another thread has interrupted this * thread. */ public AWTEvent getNextEvent() throws InterruptedException { // NOTE: it's currently unclear to me whether we should // be operating as a run loop or as a priority queue. // I'm opting for priority queue now, but that means that // selectors that requeue themselves could hang the application. // In the future, we could fake a run loop by putting a marker // event on the AWT queue to mark the boundary between loops. AWTEvent result; while (true) { // NOTE: as of java 1.4, we have to flush pending events // using this cheesy undocumented method on suntoolkit. // Unsurprisingly, java.awt.EventQueue got worse, not better. // See notes above about our use of an NSSelector. try { flushPendingEvents.invoke(toolkit); } catch (Throwable t) { System.out.println("NSRunLoop.getNextEvent: " + Thread.currentThread()); System.err.println("Unexpected error while flushing pending events: "); t.printStackTrace(); } ; synchronized (this) { result = popNextEarlyEvent(); if (result != null) { //System.out.println( "getNextEvent: early : " + result ); return result; } } if ((result = peekEvent()) != null) { //System.out.println( "getNextEvent: AWT : " + result ); return super.getNextEvent(); } synchronized (this) { result = popNextLateEvent(); if (result != null) { //System.out.println( "getNextEvent: late : " + result ); return result; } // yield //System.out.println( "getNextEvent: wait" ); wait(); //System.out.println( "getNextEvent: notified" ); } } } private AWTEvent popNextEarlyEvent() { if (earlyQueue == null) return null; // shouldn't be necessary, but is if (earlyQueue.isEmpty()) return null; return (AWTEvent) earlyQueue.removeFirst(); } private AWTEvent popNextLateEvent() { if (lateQueue == null) return null; // shouldn't be necessary, but is if (lateQueue.isEmpty()) return null; return (AWTEvent) lateQueue.removeFirst(); } /** * This implementation calls super and then throws an * UnsupportedOperationException. Catch that exception and ignore it if you know * what you are doing. */ public synchronized void push(EventQueue newEventQueue) { super.push(newEventQueue); throw new UnsupportedOperationException("NSRunLoop may not function properly with push()"); } /** * This implementation calls super and then throws an * UnsupportedOperationException. Catch that exception and ignore it if you know * what you are doing. */ protected void pop() throws EmptyStackException { super.pop(); throw new UnsupportedOperationException("NSRunLoop may not function properly with pop()"); } /** * Schedules the specified selector with the specified target and parameter to * be invoked on the next event loop with the specified ordering. The selector * must be able to be invoked on the target and the target method must accept * the parameter. aModeList is currently ignored. */ public void performSelectorWithOrder(NSSelector aSelector, Object aTarget, Object aParameter, int anOrdering, List aModeList) { postEvent(new OrderedInvocationEvent(aSelector, aTarget, aParameter, anOrdering, aModeList)); } /** * Cancels the next scheduled invocation of the specified selector, target, and * parameter. If no such invocation is scheduled, does nothing. */ public synchronized void cancelPerformSelectorWithOrder(NSSelector aSelector, Object aTarget, Object aParameter) { ListIterator i; i = earlyQueue.listIterator(); while (i.hasNext()) { if (((OrderedInvocationEvent) i.next()).compareTo(aSelector, aTarget, aParameter)) { i.remove(); return; } } i = lateQueue.listIterator(); while (i.hasNext()) { if (((OrderedInvocationEvent) i.next()).compareTo(aSelector, aTarget, aParameter)) { i.remove(); return; } } } /** * Causes runnable to have its run() method on the next event loop with the * specified priority ordering. */ public static void invokeLaterWithOrder(Runnable aRunnable, int anOrdering) { currentRunLoop().postEvent(new OrderedInvocationEvent(Toolkit.getDefaultToolkit(), aRunnable, anOrdering)); } /** * An invocation event that can specify a priority for execution. The * prioritization only works if the current event queue is an NSRunLoop; * otherwise, performs as a normal invocation event. */ private static class OrderedInvocationEvent extends InvocationEvent { int ordering; NSSelector selector = null; Object target = null; Object parameter = null; /** * Constructs an InvocationEvent with the specified source which will execute * the runnable's run() method when dispatched at the specified ordering. */ public OrderedInvocationEvent(Object source, Runnable runnable, int anOrdering) { super(source, runnable); ordering = anOrdering; } /** * Constructs an InvocationEvent with the specified source which will execute * the runnable's run() method when dispatched at the specified ordering. If * notifier is non-null, notifyAll() will be called on it immediately after * run() returns. */ public OrderedInvocationEvent(Object source, Runnable runnable, Object notifier, boolean catchExceptions, int anOrdering) { super(source, runnable, notifier, catchExceptions); ordering = anOrdering; } OrderedInvocationEvent(final NSSelector aSelector, final Object aTarget, final Object aParameter, int anOrdering, List aModeList) { this(Toolkit.getDefaultToolkit(), new Runnable() { public void run() { try { aSelector.invoke(aTarget, aParameter); } catch (Exception exc) { System.out.println("NSRunLoop: error invoking selector: "); exc.printStackTrace(); } } }, anOrdering); selector = aSelector; target = aTarget; parameter = aParameter; } /** * Called by cancelPerformSelectorWithOrder. Compares against the specified * arguments. */ boolean compareTo(NSSelector aSelector, Object aTarget, Object aParameter) { return (compareByValue(selector, aSelector) && compareByValue(target, aTarget) && compareByValue(parameter, aParameter)); } private boolean compareByValue(Object first, Object second) { if (first == second) return true; if (first == null) return second.equals(first); return first.equals(second); } /** * Returns the ordering for this event in the run loop. */ public int getOrdering() { return ordering; } } } /* * $Log$ Revision 1.2 2006/02/16 13:15:00 cgruber Check in all sources in * eclipse-friendly maven-enabled packages. * * Revision 1.13 2003/06/06 20:48:19 mpowers Fixed race condition when run loop * is started from main thread. That was causing the dispatch thread to call * getNextEvent before the static fields had been initialized. * * Revision 1.12 2003/06/03 14:52:11 mpowers Super constructor was calling * getNextEvent before selector was created. * * Revision 1.10 2002/05/28 21:59:19 mpowers We now can compile against 1.3 and * 1.4, as well as run against both too. * * Revision 1.9 2002/04/09 18:10:45 mpowers Fixes for 1.4. Commented out until * we start building on 1.4. * * Revision 1.8 2002/02/13 21:20:15 mpowers Updated comments. * * Revision 1.7 2001/11/01 15:48:49 mpowers Additional debug code. * * Revision 1.6 2001/10/30 22:14:35 mpowers Constructor is now protected, not * private. * * Revision 1.5 2001/10/29 20:41:49 mpowers Improved docs, better support for * potential subclassing, invokeLater. * * Revision 1.4 2001/10/26 18:46:30 mpowers Now running AWT events with the * appropriate ordering. Added invokeLaterWithOrder for java compatibility. * * Revision 1.3 2001/10/26 14:39:46 mpowers Completed implementation. * * Revision 1.2 2001/10/25 22:20:21 mpowers Got to check in an interim version - * this will briefly break the build. * * Revision 1.1 2001/10/24 19:30:38 mpowers Initial check-in: incomplete * implementation. * * */