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