View Javadoc

1   /*
2   Wotonomy: OpenStep design patterns for pure Java applications.
3   Copyright (C) 2001 Intersect Software Corporation
4   
5   This library is free software; you can redistribute it and/or
6   modify it under the terms of the GNU Lesser General Public
7   License as published by the Free Software Foundation; either
8   version 2.1 of the License, or (at your option) any later version.
9   
10  This library is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  Lesser General Public License for more details.
14  
15  You should have received a copy of the GNU Lesser General Public
16  License along with this library; if not, see http://www.gnu.org
17  */
18  
19  package net.wotonomy.foundation;
20  
21  import java.awt.AWTEvent;
22  import java.awt.EventQueue;
23  import java.awt.Toolkit;
24  import java.awt.event.InvocationEvent;
25  import java.util.EmptyStackException;
26  import java.util.LinkedList;
27  import java.util.List;
28  import java.util.ListIterator;
29  
30  /***
31  * NSRunLoop is provided specifically for EODelayedObserverQueue
32  * and EOEditingContext, which assume the existence of a 
33  * prioritized event queue that Java does not provide. <br><br>
34  *
35  * This extends java.awt.EventQueue and does not conform to the
36  * NSRunLoop specifications.  The only supported methods are 
37  * NSRunLoop.currentRunLoop, performSelectorWithOrder, and
38  * cancelSelectorWithOrder.  Note that in Swing there is only 
39  * one AWT thread and one event queue; newly created threads
40  * will not get their own run loop as in OpenStep.<br><br>
41  *
42  * That said, this event queue is servicable as a replacement
43  * for the default event queue and will provide prioritized
44  * execution of selectors before and after normal AWT events.
45  * <br><br>
46  *
47  * Each run loop dispatches the lowest order event from
48  * the queue.  When queued events have the same ordering,
49  * they are dispatched as first-in, first-out (FIFO).  Because
50  * all AWT events have the same ordering (AWTEventsRunLoopOrdering), 
51  * they are processed FIFO, just like the default event queue. <br><br>
52  *
53  * Note that because EventQueue is not well-factored for
54  * subclassing, pushing a new event queue onto the stack
55  * on top of this one will only copy the existing AWT events
56  * to the new queue.  For this reason, pushing new event
57  * queues onto the stack is not supported and will throw
58  * an exception.
59  *
60  * @author michael@mpowers.net
61  * @author $Author: cgruber $
62  * @version $Revision: 893 $
63  */
64  public class NSRunLoop extends EventQueue
65  {
66      /***
67      * This is the ordering at which the conventional AWT event
68      * queue will be executed.  Selectors with this ordering
69      * or less will be executed before AWT events, and 
70      * selectors with ordering greater than this ordering will be
71      * be executed after AWT events.
72      */
73      public static final int AWTEventsRunLoopOrdering = 500000;
74      
75      /***
76      * The singleton instance.
77      */
78      protected static NSRunLoop instance;
79      
80      private LinkedList earlyQueue;
81      private LinkedList lateQueue;
82      
83      /***
84      * Needed because JDK1.4 made our lives more difficult.
85      */
86      private static Toolkit toolkit;
87      
88      /***
89      * Because SunToolkit.flushPendingEvents was changed 
90      * to a static method in 1.4, you can't compile the library
91      * in such a way that it works with both 1.3 and 1.4.
92      * So we have to rely on dynamic method invocation,
93      * which is slower, but we try to make it as fast as 
94      * humanly possible.
95      */
96      private static NSSelector flushPendingEvents;
97      
98      /***
99      * Create a new instance of NSRunLoop.
100     */
101     protected NSRunLoop ()
102     {
103         earlyQueue = new LinkedList();
104         lateQueue  = new LinkedList();
105     }
106 
107     /***
108     * Returns the singleton instance of NSRunLoop.
109     * This returns the same instance no matter what
110     * thread calls it, which is different from OpenStep.
111     * NSRunLoop is limited to a singleton instance
112     * because there is no way of obtaining the stack
113     * of event queues from EventQueue because it is
114     * private state.
115     */
116     public synchronized static NSRunLoop currentRunLoop ()
117     {   
118         if ( instance == null )
119         {          
120              // create and initialize
121              flushPendingEvents = new NSSelector( "flushPendingEvents" );
122              toolkit = Toolkit.getDefaultToolkit();
123              instance = new NSRunLoop();
124              
125              toolkit.getSystemEventQueue().push( instance );
126         }
127             
128         return instance; 
129     }
130     
131     /***
132      * Post a 1.1-style event to the EventQueue.  If there is an
133      * existing event on the queue with the same ID and event source,
134      * the source Component's coalesceEvents method will be called.
135      *
136      * @param theEvent an instance of java.awt.AWTEvent, or a
137      * subclass of it.
138      */
139     public void postEvent(AWTEvent theEvent) 
140     {
141         if ( theEvent instanceof OrderedInvocationEvent )
142         {
143             OrderedInvocationEvent event = (OrderedInvocationEvent) theEvent;
144             if ( event.getOrdering() > AWTEventsRunLoopOrdering )
145             {
146                 insertEventIntoQueue( event, lateQueue );
147             }
148             else
149             {
150                 insertEventIntoQueue( event, earlyQueue );
151             }
152         }
153         else
154         {
155             super.postEvent( theEvent );
156         }
157     }
158     
159     private synchronized void insertEventIntoQueue( OrderedInvocationEvent e, LinkedList q )
160     {
161         OrderedInvocationEvent o;
162         int ordering = e.getOrdering();
163         ListIterator iterator = 
164             q.listIterator();
165     
166         // iterate forwards until we find a priority
167         //   greater than our priority,
168         //   then insert ourself before that element.
169         while ( iterator.hasNext() )
170         {
171             o = (OrderedInvocationEvent) iterator.next();   
172             if ( o.getOrdering() > ordering )
173             {
174                 // back up one
175                 iterator.previous();
176                 break;
177             }
178         }
179         // add after the current element
180         iterator.add( e );
181     }
182 
183     /***
184     * Useful method, but not in the spec.
185     * Dispatches the next AWT event in the queue.
186     * Returns whether a selector or an event was executed: 
187     * if the event queue is empty, returns false.
188     */
189     public boolean dispatchNextEvent()
190     {
191         // check for empty queue to avoid blocking
192         if ( peekEvent() == null ) 
193         {
194             return false;
195         }
196         
197         // queue not empty: dispatch the next event
198         try
199         {
200             dispatchEvent( getNextEvent() );
201         }
202         catch ( InterruptedException exc )
203         {
204             System.out.println( "NSRunLoop: error while dispatching event: " );
205             exc.printStackTrace();
206         }
207         return true;
208     }
209     
210     /***
211     * Useful method, but not in the spec.
212     * Dispatches all events in the queue before returning.
213     */
214     public void dispatchAllEvents()
215     {
216         while ( dispatchNextEvent() );
217     }
218     
219     /***
220      * Remove an event from the EventQueue and return it.  
221      * This override will dispatch all selectors up to 5000,
222      * and then check if there are AWT events on the queue.
223      * If the queue is empty, all remaining selectors 
224      * are dispatched.  Then, this method calls the 
225      * super class' implementation.
226      * @return the next AWTEvent
227      * @exception InterruptedException 
228      * if another thread has interrupted this thread.
229      */
230     public AWTEvent getNextEvent() throws InterruptedException
231     {
232         //NOTE: it's currently unclear to me whether we should 
233         // be operating as a run loop or as a priority queue.
234         // I'm opting for priority queue now, but that means that
235         // selectors that requeue themselves could hang the application.
236         // In the future, we could fake a run loop by putting a marker 
237         // event on the AWT queue to mark the boundary between loops.
238         
239         AWTEvent result;
240         
241         while ( true )
242         {
243             //NOTE: as of java 1.4, we have to flush pending events
244             // using this cheesy undocumented method on suntoolkit.
245             // Unsurprisingly, java.awt.EventQueue got worse, not better.
246             // See notes above about our use of an NSSelector.
247             try
248             {
249                 flushPendingEvents.invoke( toolkit );
250             } 
251             catch ( Throwable t ) 
252             {
253                 System.out.println( "NSRunLoop.getNextEvent: " + Thread.currentThread() );        
254                 System.err.println( "Unexpected error while flushing pending events: " );
255                 t.printStackTrace();
256             };
257             
258             synchronized( this )
259             {
260                 result = popNextEarlyEvent();
261                 if ( result != null )
262                 { 
263 //System.out.println( "getNextEvent: early : " + result );            
264                     return result;
265                 }
266             }
267             
268             if ( ( result = peekEvent() ) != null )
269             {
270 //System.out.println( "getNextEvent: AWT : " + result );      
271                 return super.getNextEvent();
272             }
273             
274             synchronized( this )
275             {
276                 result = popNextLateEvent();
277                 if ( result != null )
278                 {
279 //System.out.println( "getNextEvent: late : " + result );            
280                     return result;
281                 }
282                 
283                 // yield
284 //System.out.println( "getNextEvent: wait" );            
285                 wait();
286 //System.out.println( "getNextEvent: notified" );            
287             }
288         }
289     }
290 
291     private AWTEvent popNextEarlyEvent()
292     {
293         if ( earlyQueue == null ) return null; // shouldn't be necessary, but is
294         if ( earlyQueue.isEmpty() ) return null;
295         return (AWTEvent) earlyQueue.removeFirst();
296     }
297     
298     private AWTEvent popNextLateEvent()
299     {
300         if ( lateQueue == null ) return null; // shouldn't be necessary, but is
301         if ( lateQueue.isEmpty() ) return null;
302         return (AWTEvent) lateQueue.removeFirst();
303     }
304     
305     /***
306      * This implementation calls super and then throws an
307      * UnsupportedOperationException.  Catch that exception
308      * and ignore it if you know what you are doing.
309      */
310     public synchronized void push(EventQueue newEventQueue)
311     {
312         super.push( newEventQueue );
313         throw new UnsupportedOperationException(
314             "NSRunLoop may not function properly with push()" );
315     }
316 
317     /***
318      * This implementation calls super and then throws an
319      * UnsupportedOperationException.  Catch that exception
320      * and ignore it if you know what you are doing.
321      */
322     protected void pop() throws EmptyStackException
323     {
324         super.pop();
325         throw new UnsupportedOperationException(
326             "NSRunLoop may not function properly with pop()" );
327     }        
328     
329     /***
330     * Schedules the specified selector with the specified target and parameter 
331     * to be invoked on the next event loop with the specified ordering.  
332     * The selector must be able to be invoked on the target and the target method
333     * must accept the parameter.  aModeList is currently ignored.
334     */
335     public void performSelectorWithOrder( 
336         NSSelector aSelector, Object aTarget, Object aParameter, int anOrdering, List aModeList )
337     {
338         postEvent( new OrderedInvocationEvent( aSelector, aTarget, aParameter, anOrdering, aModeList ) );
339     }
340     
341     /***
342     * Cancels the next scheduled invocation of the specified selector, target, and parameter.
343     * If no such invocation is scheduled, does nothing.
344     */
345     public synchronized void cancelPerformSelectorWithOrder( 
346         NSSelector aSelector, Object aTarget, Object aParameter )
347     {
348         ListIterator i;
349         i = earlyQueue.listIterator();
350         while ( i.hasNext() )
351         {
352             if ( ((OrderedInvocationEvent)i.next()).compareTo(
353                 aSelector, aTarget, aParameter ) )
354             {
355                 i.remove();
356                 return;
357             }
358         }
359         i = lateQueue.listIterator();
360         while ( i.hasNext() )
361         {
362             if ( ((OrderedInvocationEvent)i.next()).compareTo(
363                 aSelector, aTarget, aParameter ) )
364             {
365                 i.remove();
366                 return;
367             }
368         }
369     }
370     
371     /***
372      * Causes runnable to have its run() method on the next 
373      * event loop with the specified priority ordering.
374      */
375     public static void invokeLaterWithOrder(Runnable aRunnable, int anOrdering) {
376         currentRunLoop().postEvent( 
377             new OrderedInvocationEvent( Toolkit.getDefaultToolkit(), aRunnable, anOrdering ) );
378     }
379     
380     /***
381     * An invocation event that can specify a priority for execution.
382     * The prioritization only works if the current event queue is an
383     * NSRunLoop; otherwise, performs as a normal invocation event.
384     */
385     private static class OrderedInvocationEvent extends InvocationEvent
386     {
387         int ordering;
388         NSSelector selector = null;
389         Object target = null;
390         Object parameter = null;
391          
392         /***
393         * Constructs an InvocationEvent with the specified source which will 
394         * execute the runnable's run() method when dispatched at the specified ordering.
395         */
396         public OrderedInvocationEvent(Object source,
397                        Runnable runnable, int anOrdering)
398         {
399             super( source, runnable );
400             ordering = anOrdering;
401         }
402                    
403         /***
404         * Constructs an InvocationEvent with the specified source which will 
405         * execute the runnable's run() method when dispatched at the specified ordering.
406         * If notifier is non-null, notifyAll() will be called on it immediately after run() returns.
407         */
408         public OrderedInvocationEvent(Object source,
409                        Runnable runnable,
410                        Object notifier,
411                        boolean catchExceptions, int anOrdering)
412         {
413             super( source, runnable, notifier, catchExceptions );
414             ordering = anOrdering;
415         }
416         
417         OrderedInvocationEvent( 
418             final NSSelector aSelector, 
419             final Object aTarget, 
420             final Object aParameter, 
421             int anOrdering, List aModeList)
422         {
423             this( Toolkit.getDefaultToolkit(), new Runnable() 
424             {
425                 public void run()
426                 {
427                     try
428                     {
429                         aSelector.invoke( aTarget, aParameter );
430                     } 
431                     catch ( Exception exc )
432                     {
433                         System.out.println( "NSRunLoop: error invoking selector: " );
434                         exc.printStackTrace();
435                     }
436                 }
437             }, anOrdering );
438             
439             selector = aSelector;
440             target = aTarget;
441             parameter = aParameter;
442         }
443          
444         /***
445         * Called by cancelPerformSelectorWithOrder.
446         * Compares against the specified arguments.
447         */
448         boolean compareTo( NSSelector aSelector, Object aTarget, Object aParameter )
449         {
450             return (
451                 compareByValue( selector, aSelector ) &&
452                 compareByValue( target, aTarget ) &&
453                 compareByValue( parameter, aParameter ) );
454         }
455         
456         private boolean compareByValue( Object first, Object second )
457         {
458             if ( first == second ) return true;
459             if ( first == null ) return second.equals( first );
460             return first.equals( second );
461             
462         }
463          
464         /***
465         * Returns the ordering for this event in the run loop.
466         */
467         public int getOrdering()
468         {
469             return ordering;
470         }
471          
472     }
473     
474 }
475 
476 /*
477  * $Log$
478  * Revision 1.2  2006/02/16 13:15:00  cgruber
479  * Check in all sources in eclipse-friendly maven-enabled packages.
480  *
481  * Revision 1.13  2003/06/06 20:48:19  mpowers
482  * Fixed race condition when run loop is started from main thread.
483  * That was causing the dispatch thread to call getNextEvent before the
484  * static fields had been initialized.
485  *
486  * Revision 1.12  2003/06/03 14:52:11  mpowers
487  * Super constructor was calling getNextEvent before selector was created.
488  *
489  * Revision 1.10  2002/05/28 21:59:19  mpowers
490  * We now can compile against 1.3 and 1.4, as well as run against both too.
491  *
492  * Revision 1.9  2002/04/09 18:10:45  mpowers
493  * Fixes for 1.4.  Commented out until we start building on 1.4.
494  *
495  * Revision 1.8  2002/02/13 21:20:15  mpowers
496  * Updated comments.
497  *
498  * Revision 1.7  2001/11/01 15:48:49  mpowers
499  * Additional debug code.
500  *
501  * Revision 1.6  2001/10/30 22:14:35  mpowers
502  * Constructor is now protected, not private.
503  *
504  * Revision 1.5  2001/10/29 20:41:49  mpowers
505  * Improved docs, better support for potential subclassing, invokeLater.
506  *
507  * Revision 1.4  2001/10/26 18:46:30  mpowers
508  * Now running AWT events with the appropriate ordering.
509  * Added invokeLaterWithOrder for java compatibility.
510  *
511  * Revision 1.3  2001/10/26 14:39:46  mpowers
512  * Completed implementation.
513  *
514  * Revision 1.2  2001/10/25 22:20:21  mpowers
515  * Got to check in an interim version - this will briefly break the build.
516  *
517  * Revision 1.1  2001/10/24 19:30:38  mpowers
518  * Initial check-in: incomplete implementation.
519  *
520  *
521  */
522