/* 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. * * */