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