summaryrefslogtreecommitdiff
path: root/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSRunLoop.java
blob: c72cd23289d3bbae0b62ceee0d2f6303fc8519cc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
/*
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.
 *
 *
 */