001    package org.LiveGraph.events;
002    
003    import java.util.ArrayList;
004    import java.util.Collections;
005    import java.util.LinkedList;
006    import java.util.List;
007    import java.util.Queue;
008    
009    import org.LiveGraph.LiveGraph;
010    
011    import com.softnetConsult.utils.exceptions.Bug;
012    import com.softnetConsult.utils.exceptions.ThrowableTools;
013    import com.softnetConsult.utils.exceptions.UnexpectedSwitchCase;
014    
015    
016    public class EventManager implements EventProducer {
017    
018    private List<EventListener> listeners;
019    private Queue<Event<? extends EventType>> eventQueue;
020    
021    private boolean shutDownWhenFinished;
022    private boolean shutDownImmediately;
023    private Thread eventDispatcher;
024    
025    private List<ShutDownHook> shutDownHooks;
026    
027    public EventManager() {
028            
029            listeners = new ArrayList<EventListener>();
030            eventQueue = new LinkedList<Event<? extends EventType>>();
031            shutDownWhenFinished = false;
032            shutDownImmediately = false;
033            eventDispatcher = null;
034            shutDownHooks = new ArrayList<ShutDownHook>();
035    }
036    
037    public boolean addShutDownHook(ShutDownHook hook) {
038            if (null == hook)
039                    return false;
040            if (shutDownHooks.contains(hook))
041                    return false;
042            shutDownHooks.add(hook);
043            return true;
044    }
045    
046    public synchronized void startDispatchingEvents() {
047            eventDispatcher = new Thread(new EventDispatcher(), "LiveGraph Event Dispatcher");
048            eventDispatcher.start();
049    }
050    
051    public synchronized void shutDownWhenFinished() {
052            if (null == eventDispatcher)
053                    throw new IllegalStateException("Cannot shut down event dispatching as it is not running");
054            shutDownWhenFinished = true;
055            synchronized (eventQueue) {
056                eventQueue.notifyAll();
057        }
058    }
059    
060    public synchronized void shutDownImmediately() {
061            if (null == eventDispatcher)
062                    throw new IllegalStateException("Cannot shut down event dispatching as it is not running");
063            shutDownImmediately = true;
064            synchronized (eventQueue) {
065                eventQueue.notifyAll();
066        }
067    }
068    
069    @SuppressWarnings("unused")
070    private void debug_printQueue() {
071            synchronized (eventQueue) {
072                    System.out.println("Queue length: " + eventQueue.size());
073                    int i = 0;
074                    for (Event<? extends EventType> event : eventQueue) {
075                            System.out.printf("%3d) %s %n", i++, event.toString());
076                    }
077            }
078            System.out.println();
079    }
080    
081    public void waitForEvents() {
082            // Note that this method cannot guarantee an empty queue to the calling method since as soon as
083            // this method is about to return, it looses the synchronisation lock on the event queue and a new
084            // event may be enqued by another thread immediately. However, this method does guarantee that
085            // all events that have been enqueued before this method was called have finished processing.
086            
087            Event<SystemEvent> noop = new Event<SystemEvent>(this, SystemEvent.class, SystemEvent.SYS_NoOp);
088            raiseEvent(noop);
089            synchronized (eventQueue) {
090                    while (!eventQueue.isEmpty() && !mustShutDown()) {
091                            eventQueue.notifyAll();
092                            try     { eventQueue.wait(1000); }
093                            catch(InterruptedException e) { }
094                    }
095        }
096    }
097    
098    private synchronized boolean mustShutDown() {
099            
100            if (shutDownImmediately)
101                    return true;
102            
103            synchronized (eventQueue) {
104                    if (shutDownWhenFinished)
105                            return eventQueue.isEmpty();
106                    return false;
107            }
108    }
109    
110    private synchronized void hasShutDown() {
111            eventDispatcher = null;
112            for (ShutDownHook hook : shutDownHooks)
113                    hook.hasShutDown(this);
114    }
115    
116    public boolean registerListener(EventListener listener) {
117            
118            if (null == listener)
119                    return false;
120            
121            synchronized(listeners) {
122                    
123                    if (managesListener(listener))
124                            return false;
125                    
126                    if (!listener.permissionRegisterWithEventManager(this))
127                            return false;
128                    
129                    listeners.add(listener);
130                    listener.completedRegisterWithEventManager(this);
131                    return true;
132            }
133    }
134    
135    public boolean managesListener(EventListener listener) {
136            
137            if (null == listener)
138                    return false;
139            
140            synchronized(listeners) {
141            
142                    for (EventListener listen : listeners) {
143                            if (listen == listener)
144                                    return true;
145                    }
146                    return false;
147            }
148    }
149    
150    public boolean unregisterListener(EventListener listener) {
151    
152            if (null == listener)
153                    return false;
154            
155            synchronized(listeners) {
156            
157                    for (int i = 0; i < listeners.size(); i++) {
158                    
159                            EventListener listen = listeners.get(i);
160                            if (listen == listener) {
161                                    if (!listen.permissionUnregisterWithEventManager(this))
162                                            return false;
163                                    
164                                    listeners.remove(i);
165                                    listen.completedRegisterWithEventManager(this);
166                                    return true;
167                            }               
168                    }
169                    return false;
170            }
171    }
172    
173    public int countAllListeners() {
174            synchronized(listeners) {
175                    return listeners.size();
176            }
177    }
178    
179    public List<EventListener> getInterestedListeners(Event<? extends EventType> event)
180                                                                                                                                            throws EventProcessingException {
181            
182            // Never work with null events:
183            if (null == event)
184                    throw new NullPointerException("Cannot get interested listeners for a null event");
185            
186            // Go for it:
187            synchronized(listeners) {
188                    
189                    List<EventListener> interested = new ArrayList<EventListener>();
190                    EventProcessingException exceptionContainer = null;
191                    
192                    for (EventListener listener : listeners) {
193                            
194                            try {
195                                    
196                                    if (listener.checkEventInterest(event))
197                                            interested.add(listener);
198                                    
199                            } catch(Exception ex) {
200                                    if (null == exceptionContainer)
201                                            exceptionContainer = new EventProcessingException();
202                                    exceptionContainer.addCause(event, listener, ex);
203                            }
204                    }
205                    
206                    if (null != exceptionContainer)
207                            throw exceptionContainer;
208                    
209                    return Collections.unmodifiableList(interested);
210            }
211    }
212    
213    public boolean validateEvent(Event<? extends EventType> event) throws EventProcessingException,
214                                                                                                                                              ValidationRequirementException {
215            
216            // Never work with null events:
217            if (null == event)
218                    throw new NullPointerException("Cannot validate a null event");
219            
220            // Check the validation annotation and make sure not to throw a never-validate type event: 
221            switch(event.getValidationRequirement()) {
222                    
223                    default:
224                            throw new UnexpectedSwitchCase(event.getValidationRequirement());
225                    
226                    case MUST_VALIDATE:
227                    case MAY_VALIDATE:
228                            break; // ok, lets validate.
229                            
230                    case NEVER_VALIDATE:
231                            throw new ValidationRequirementException(
232                                                                                    event, ValidationRequirementException.FailedOperation.VALIDATE);
233            }
234            
235            // Lock the listeners and perform validation:
236            synchronized(listeners) {
237    
238                    // Holders:
239                    EventProcessingException exceptionContainer = null;
240                    boolean valid = true;
241            
242                    // Perform validation for each listener:
243                    for (EventListener listener : listeners) {
244                    
245                            try {
246                                    
247                                    // Validate for current listener:
248                                    if (!listener.checkEventValid(event, valid))
249                                            valid = false;
250                                    
251                            } catch(Exception ex) {
252                                    // Catch all exceptions and remember them for later:
253                                    if (null == exceptionContainer)
254                                            exceptionContainer = new EventProcessingException();
255                                    exceptionContainer.addCause(event, listener, ex);
256                            }               
257                    }
258                    
259                    // If we had exception it is not time to them them inside a container:
260                    if (null != exceptionContainer)
261                            throw exceptionContainer;
262                    
263                    // Set and return the validation result:
264                    event.setValidated(valid);
265                    return valid;
266            }
267    }
268    
269    public void raiseEvent(Event<? extends EventType> event) throws ValidationRequirementException {
270            
271            
272            // Disallow null events:
273            if (null == event)
274                    throw new NullPointerException("Cannot raise a null event");
275            
276            // Check the validation annotation and make sure not to throw events that still require validation: 
277            switch(event.getValidationRequirement()) {
278                    
279                    default:
280                            throw new UnexpectedSwitchCase(event.getValidationRequirement());
281                    
282                    case MUST_VALIDATE:
283                            if (!event.validated()) {
284                                    throw new ValidationRequirementException(                                               
285                                                                                            event, ValidationRequirementException.FailedOperation.RAISE);
286                            }
287                            break;
288                            
289                    case MAY_VALIDATE:
290                    case NEVER_VALIDATE:
291                            break; // For these validation is not compulsory.
292            }
293            
294    
295            
296            // Enqueue the event and notify the dispatcher:
297            synchronized(eventQueue) {
298                    eventQueue.add(event);
299                    eventQueue.notifyAll();
300        }
301    }
302    
303    public boolean eventValidateRaise(Event<? extends EventType> event) throws EventProcessingException {
304    
305            if (!validateEvent(event))
306                    return false;
307            
308            raiseEvent(event);
309            return true;
310    }
311    
312    
313    // Called by EventDispatcher.run()
314    private void doRaiseEvent(Event<? extends EventType> event) throws EventProcessingException {
315            
316            // Synchromise on listeners:
317            synchronized(listeners) {
318                    
319                    // Will hold any excaptions that may occur:
320                    EventProcessingException exceptionContainer = null;
321                            
322                    // Notify every listener:
323                    for (EventListener listener : listeners) {
324                    
325                            try {
326                                    
327                                    // Invoke listener handler:
328                                    listener.eventRaised(event);
329                                    
330                            } catch(Throwable ex) {
331                                    
332                                    // If exception occured - save it: 
333                                    if (null == exceptionContainer)
334                                            exceptionContainer = new EventProcessingException();
335                                    exceptionContainer.addCause(event, listener, ex);
336                                    
337                                    // For exceptions - invoke all remaining listeners, for errors - break immediately:
338                                    if (ex instanceof Exception) {                                          
339                                    } else if (ex instanceof Error) {
340                                            break;
341                                    } else {
342                                            throw new Bug("Unexpected subclass of Throwable: " + ex.getClass().getName());
343                                    }
344                            }               
345                    } // for (EventListener listener : listeners)
346                    
347                    // If there was an exception - 
348                    if (null != exceptionContainer)
349                            throw exceptionContainer;
350            }
351    }
352    
353    private void raiseExceptionOccured(Event<? extends EventType> event, EventProcessingException exception) {
354            
355            justDisplayException(exception);
356            
357            if (event != null && SystemEvent.SYS_EventProcessingException == event.getType()) {
358                    Event<SystemEvent> exceptionEvent = new Event<SystemEvent>(this, SystemEvent.class,
359                                                                                                                                       SystemEvent.SYS_EventProcessingException,
360                                                                                                                                       exception);
361                    raiseEvent(exceptionEvent);
362            }
363    }
364    
365    private void justDisplayException(Throwable ex) {
366            ex.printStackTrace();
367            try {
368                    String err = ThrowableTools.stackTraceToString(ex);
369                    LiveGraph.application().guiManager().logErrorLn(err);
370            } catch(Throwable ex2) {
371                    ex2.printStackTrace();
372            }
373    }
374    
375    public void eventProcessingFinished(Event<? extends EventType> event) {   
376    }
377    
378    public boolean eventProcessingException(Event<? extends EventType> event, EventProcessingException exception) {
379            return false;
380    }
381    
382    private class EventDispatcher implements Runnable {
383    
384            public void run() {
385                    try {
386                            while(true) {
387                                    try {                   
388                                            if (mustShutDown())
389                                                    return;
390                                            runProtected();                         
391                                    } catch(Throwable e) {
392                                            justDisplayException(e);
393                                    }
394                            }
395                    } finally {
396                            hasShutDown();
397                    }
398            }
399    
400            private void runProtected() {
401                    
402                    // Save the next event here:
403                    Event<? extends EventType> event = null;
404                    
405                    // Get hold of event queue:
406                    synchronized (eventQueue) {
407                            
408                            // While event queue is empty - wait:
409                            while(eventQueue.isEmpty()) {
410                                    
411                                    // If must shut down - quit:
412                                    if (mustShutDown())
413                                            return;
414                                    
415                                    // Wait for the queue to fill:
416                                    try     { eventQueue.wait(1000); }
417                                    catch(InterruptedException e) { }
418                            }
419                            
420                            // Queue is not empty - remove head and proceed:
421                            event = eventQueue.poll();
422                            eventQueue.notifyAll();
423            }
424                                    
425                    try {
426                            // Raise the event:
427                            doRaiseEvent(event);
428                            event.getProducer().eventProcessingFinished(event);
429                            
430                    } catch(EventProcessingException ex) {
431                            
432                            // If an exception occured during RAISING (other exceptions fall through):
433                            
434                            // Notify producer that sent the event that lead to exception:
435                            boolean exceptionConsumed = false;
436                            try {
437                                    exceptionConsumed = event.getProducer().eventProcessingException(event, ex);                            
438                            } catch (Throwable ex2) {                               
439                                    // If another exception occured during notifying the producer, display it and recover: 
440                                    justDisplayException(ex2);
441                            }
442                            
443                            // Only IF the procuder of the event that lead to the exception does not signal that it
444                            // consumed the exception THEN notify ALL listeners of the exception (and also print it): 
445                            if (!exceptionConsumed)
446                                    raiseExceptionOccured(event, ex);
447                    }
448            }
449    
450    }  // private class EventDispatcher
451    
452    public static interface ShutDownHook {
453            public void hasShutDown(EventManager eventManager);
454    }  // public static class ShutDownHook
455    
456    }  // public class EventManager