summaryrefslogtreecommitdiff
path: root/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSRunLoop.java
diff options
context:
space:
mode:
authorBenjamin Culkin <scorpress@gmail.com>2024-05-19 17:56:33 -0400
committerBenjamin Culkin <scorpress@gmail.com>2024-05-19 17:56:33 -0400
commitaedc34d55462a75e329bbf342251ff6504cd117e (patch)
treebcc8f1f2352582717b484df302aeea6696b8f000 /projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSRunLoop.java
Initial import from SVN
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.java522
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.
+ *
+ *
+ */
+