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