diff options
Diffstat (limited to 'projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSRunLoop.java')
| -rw-r--r-- | projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSRunLoop.java | 522 |
1 files changed, 522 insertions, 0 deletions
diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSRunLoop.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSRunLoop.java new file mode 100644 index 0000000..2d122aa --- /dev/null +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSRunLoop.java @@ -0,0 +1,522 @@ +/* +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. <br><br> +* +* 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.<br><br> +* +* 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. +* <br><br> +* +* 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. <br><br> +* +* 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. + * + * + */ + |
