package org.ophion.snitch.util; import java.util.ArrayList; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * EventSinks are an easy way to provide multicast delegates for event handling. * <p/> * In a nutshell, EventSinks allow you to quickly create event notification * points to which end users can subscribe by implementing the IEventHandler * interface * <p/> * Also, please note that most events are dispatch serially. The async dispatch * options is only provided for convenience. <br> * <p/> * How to use the event sink: * <p/> * <code> * public void testDispatch() throws Exception { <br> * EventSink es = new EventSink(); <br> * int a = es.appendHandler(new CountHandler()); <br> * es.dispatch(this,null); <br> * }<br> * </code> * * @author Rafael Ferreira <raf@uvasoftware.com> */ public class EventSink { private ArrayList<IEventHandler> listeners = new ArrayList<IEventHandler>(); private static Log log = new Log(); private static ExecutorService es = Executors.newCachedThreadPool(); private final Object lock = new Object(); /** * Returns the number of handlers currently registered with this event * * @return */ public int size() { return (listeners.size()); } /** * Simple worker runnable to assist with dispatching events async * * @author Rafael Ferreira <raf@uvasoftware.com> */ private class EventSinkWorker implements Runnable { EventSink target = null; Object eventSource, eventArgs; IEventHandler callback = null; public EventSinkWorker(EventSink ev, Object eventSource, Object eventArgs, IEventHandler callback) { target = ev; this.eventArgs = eventArgs; this.eventSource = eventSource; this.callback = callback; } public void run() { try { target.dispatch(eventSource, eventArgs); log.fine("calling callback"); if (callback != null) callback.handle(this, null); } catch (RuntimeException e) { log.severe("dispatch error:", e); } } } /** * Subscribe to this event by passing a delegate that implements the * IEventHandler interface * * @param handler the delegate * @return the id of the delegate in the delegate queue (use this id to * remove the delegate later) */ public int appendHandler(IEventHandler handler) { synchronized (lock) { listeners.add(handler); } return listeners.indexOf(handler); } /** * Dispatches the event queue by calling all the delegates serially * * @param eventSource the object that triggered the event * @param eventArgs the arguments needed to service the event (note that this is * in a per event basis) * @throws Exception the exception thrown by the event handler - note that if one * of the events throws an exception, the whole pipeline gets * killed */ public void dispatch(Object eventSource, Object eventArgs) { synchronized (lock) { for (IEventHandler i : listeners) { try { i.handle(eventSource, eventArgs); } catch (StopDispatchException e) { log.info("early chain termination caught", e); return; } } } } /** * Dispatches the event queue asynchronously and calls the callback when * done * * @param eventSource the object that triggered the event * @param eventArgs the arguments needed to service the event (note that this is * in a per event basis) * @throws Exception the exception thrown by the event handler - note that if one * of the events throws an exception, the whole pipeline gets * killed */ public void dispatchAsync(final Object eventSource, final Object eventArgs) { for (final IEventHandler i : listeners) { try { es.execute(new Runnable() { @Override public void run() { i.handle(eventSource, eventArgs); } }); } catch (StopDispatchException e) { log.info("early chain termination caught", e); return; } } } /** * Removes a handler/delegate identified by this id * * @param id the handler id * @return true if successful */ public synchronized boolean removerHandler(int id) { boolean r; synchronized (lock) { r = listeners.remove(id) != null; } return r; } }