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