瀏覽代碼

new: EventObserver

Ranides Atterwim 10 年之前
父節點
當前提交
22844f1ba3

+ 2 - 0
assira/pom.xml

@@ -47,6 +47,8 @@
                                 <include>net/ranides/assira/collection/maps/ArrayMap*</include>
                                 <include>net/ranides/assira/collection/query/**</include>
                                 
+                                <include>net/ranides/assira/events/EventObserver*</include>
+                                
 <!--                                <include>net/ranides/assira/io/PathUtils*</include>-->
                                 
                                 <include>net/ranides/assira/lexer/**</include>

+ 251 - 222
assira/src/main/java/net/ranides/assira/events/EventDispatcher.java

@@ -1,222 +1,251 @@
-/*
- * @author Ranides Atterwim <ranides@gmail.com>
- * @copyright Ranides Atterwim
- * @license WTFPL
- * @url http://ranides.net/projects/assira
- */
-package net.ranides.assira.events;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import net.ranides.assira.trace.LoggerUtils;
-import net.ranides.assira.trace.ThreadUtils;
-import org.slf4j.Logger;
-
-/**
- * Beznarzutowy EventRouter, nie kolejkuje zdarzeń.
- * Wywołuje procedury obsługi natychmiast po wywołaniu zdarzenia, w wątku, który
- * zasygnalizował event.
- * <p>
- * Użyteczny wszędzie tam, gdzie mamy pewność, że nadawcy i odbiorcy działają
- * w jednym wątku, albo wtedy, gdy rejestrowani obserwatorzy zdarzeń realizują
- * obsługę komunikatów w taki sposób, że mogą ruszyć w dowolnym wątku (bo wykonują
- * operacje thread-safe, albo używają innych metod synchronizacji).
- * </p>
- * @author ranides
- */
-public class EventDispatcher implements EventRouter {
-
-    private static final Logger LOGGER = LoggerUtils.getLogger();
-
-    private Object[] listeners = null;
-
-    @Override
-    public <T extends Event> void addEventListener(Class<T> event, EventListener<? super T> listener) {
-        // atomic operation, signal update
-        synchronized(this) {
-            if(null == listeners) {
-                listeners = new Object[]{event, listener};
-            } else {
-                int size = listeners.length;
-                Object[] realloc = new Object[size+2];
-                System.arraycopy(listeners, 0, realloc, 0, size);
-                realloc[size] = event;
-                realloc[size+1] = listener;
-                listeners = realloc;
-            }
-        }
-    }
-
-    @Override
-    public <T extends Event> void removeEventListener(Class<T> event, EventListener<? super T> listener) {
-        // atomic operation, signal update
-        synchronized(this) {
-            if(null == listeners) {
-                return;
-            }
-            int index = -1;
-            for (int i = listeners.length-2; i>=0; i-=2) {
-                if ((listeners[i]==event) && listeners[i+1].equals(listener) ) {
-                    index = i;
-                    break;
-                }
-            }
-
-            if (index != -1) {
-                if(listeners.length==2) {
-                    listeners = null;
-                } else {
-                    int size = listeners.length-2;
-                    Object[] realloc = new Object[size];
-                    System.arraycopy(listeners, 0, realloc, 0, index);
-                    if (index < size) {
-                        System.arraycopy(listeners, index+2, realloc, index, size-index);
-                    }
-                    listeners = realloc;
-                }
-            }
-        }
-    }
-
-    @Override
-    public void removeAllEventListeners() {
-        // atomic operation, signal update
-        synchronized(this) {
-            listeners = null;
-        }
-    }
-
-    @Override
-    @SuppressWarnings("unchecked")
-    public Collection<EventBinding<?>> getEventListeners() {
-        Object[] array;
-        // read updated
-        synchronized(this) {
-            array = listeners;
-        }
-        // read update
-        if(null == array) {
-            return Collections.emptyList();
-        }
-        List<EventBinding<?>> list = new ArrayList<>(array.length/2);
-        for(int i=0, n=array.length; i<n; i+=2) {
-            list.add(new EventBinding.Immutable((Class)array[i], (EventListener)array[i+1]));
-        }
-        return list;
-    }
-    
-    @Override
-    public int getEventListenersCount() {
-        synchronized(this) {
-            return null == listeners ? 0 : listeners.length / 2;
-        }
-    }
-
-    /**
-     * Metoda blokująca, uruchamia kolejno obsługę zdarzenia u wszystkich
-     * zarejestrowanych obserwatorów, które na zdarzenie o określonym typie oczekują.
-     * Domyślna implementacja wywołuje tę metodę bezpośrednio w {@link #signalEvent}.
-     * Metody pochodne mogą użyć poniższej metody w zupełnie inny sposób i w
-     * zupełnie innym wątku, aby zaimplementować inną strategię przetwarzania zdarzeń.
-     *
-     * <div class="message-note">thread-safe method</div>
-     * @param event
-     */
-    protected void dispatchEvent(Event event) {
-        dispatchEvent(event, false);
-    }
-
-    @SuppressWarnings("unchecked")
-    protected void dispatchEvent(Event event, boolean direct) {
-        Object[] array;
-        // read updated
-        synchronized(this) {
-            array = this.listeners;
-        }
-        if(array == null) {
-            return;
-        }
-        Class<?> clazz = event.getClass();
-        for(int i=0, n=array.length; i<n; i+=2) {
-            if( ((Class)array[i]).isAssignableFrom(clazz) ) {
-                try {
-                    if(direct) {
-                        ((EventListener)array[i+1]).handleEvent(event);
-                    } else {
-                        dispatchEvent((EventListener)array[i+1], event);
-                    }
-                } catch(Exception ex) {
-					String message = String.format("Exception thrown from event listener. Thread: %s. Listener: %s. Event: %s", 
-						Thread.currentThread().getName(), 
-						array[i+1], event
-					);
-					LOGGER.debug(message, ex);
-                    signalEvent(Events.failure(ex));
-                }
-            }
-        }
-    }
-
-    protected <T extends Event> void dispatchEvent(EventListener<? super T> listener, T event) {
-        listener.handleEvent(event);
-    }
-
-    /**
-     * {@inheritDoc}
-     * <p>
-     * Metoda czeka na obsłużenie komunikatu przez wszystkich zarejestrowanych obserwatorów.
-     * Obsługa komunikatu jest uruchamiana natychmiastowo, w bieżącym wątku
-     * (t.j. w tym samym, który zasygnalizował zdarzenie).
-     * </p>
-     * <p>Interesującym przypadkiem jest przekazanie Dispatcher'owi obserwatora,
-     * który jest {@code EventRouterThread}'em. Taki obserwator w bieżącym wątku
-     * obsłuży zdarzenie <i>w swoim imieniu</i>, ale ta obsługa, to nic więcej, niż zapis do
-     * wewnętrznej kolejki. Dokładniej, zgodnie z dokumentacją:</p>
-     *  <ul>
-     *      <li>ruszy metoda {@link EventRouterThread#handleEvent}</li>
-     *      <li>metoda wydeleguje obsługę do {@link EventRouterThread#signalEvent}</li>
-     *      <li>{@code signalEvent} wstawi komunikat do kolejki zdarzeń oczekujących</li>
-     *      <li>wstawiony komunikat zostanie przekazany do obserwatorów w zupełnie
-     *      innym wątku</li>
-     *      <li>w szczególności, czas obsługi jest całkowicie niezdefiniowany.
-     *          Może nastąpić przed zakończeniem metody signal z bieżącego wątku.
-     *          A może być odwrotnie - nastapić długo po jej zakończeniu</li>
-     *  </ul>
-     *
-     * <div class="message-note">thread-safe method</div>
-     * @param event
-     */
-    @Override
-    public boolean signalEvent(Event event) {
-		ThreadUtils.dump(LOGGER.isTraceEnabled(), LOGGER::trace);
-        dispatchEvent(event);
-        return true;
-    }
-
-    /**
-     * Rozsyła otrzymany komunikat do wszystkich obserwatorów, delegując
-     * obsługę zdarzenia do metody {@link #signalEvent}
-     * <div class="message-note">thread-safe method</div>
-     * @param event
-     */
-    @Override
-    public void handleEvent(Event event) {
-        signalEvent(event);
-    }
-
-    protected void dispose(boolean direct) {
-        dispatchEvent(Events.dispose(this), direct);
-        // signal update
-        synchronized(this) {
-            this.listeners = null;
-        }
-    }
-
-    @Override
-    public void dispose() {
-        dispose(true);
-    }
-    
-}
+/*
+ * @author Ranides Atterwim <ranides@gmail.com>
+ * @copyright Ranides Atterwim
+ * @license WTFPL
+ * @url http://ranides.net/projects/assira
+ */
+package net.ranides.assira.events;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import net.ranides.assira.collection.maps.HashMap;
+import net.ranides.assira.trace.LoggerUtils;
+import net.ranides.assira.trace.ThreadUtils;
+import org.slf4j.Logger;
+
+/**
+ * Beznarzutowy EventRouter, nie kolejkuje zdarzeń.
+ * Wywołuje procedury obsługi natychmiast po wywołaniu zdarzenia, w wątku, który
+ * zasygnalizował event.
+ * <p>
+ * Użyteczny wszędzie tam, gdzie mamy pewność, że nadawcy i odbiorcy działają
+ * w jednym wątku, albo wtedy, gdy rejestrowani obserwatorzy zdarzeń realizują
+ * obsługę komunikatów w taki sposób, że mogą ruszyć w dowolnym wątku (bo wykonują
+ * operacje thread-safe, albo używają innych metod synchronizacji).
+ * </p>
+ * @author ranides
+ */
+public class EventDispatcher implements EventRouter {
+
+    private static final Logger LOGGER = LoggerUtils.getLogger();
+    
+    private static final Map<String, WeakReference<EventDispatcher>> DISPATCHERS = new HashMap<>();
+
+    private Object[] listeners = null;
+    
+    private final String name;
+    
+    public EventDispatcher() {
+        this.name = "";
+    }
+
+    public EventDispatcher(String name) {
+        this.name = name;
+        synchronized(DISPATCHERS) {
+            DISPATCHERS.put(name, new WeakReference<>(this));
+        }
+    }
+    
+    public static EventDispatcher find(String name) {
+        WeakReference<EventDispatcher> ref = DISPATCHERS.get(name);
+        return null!=ref ? ref.get() : null;
+    }
+
+    @Override
+    public String name() {
+        return name;
+    }
+
+    @Override
+    public <T extends Event> void addEventListener(Class<T> event, EventListener<? super T> listener) {
+        // atomic operation, signal update
+        synchronized(this) {
+            if(null == listeners) {
+                listeners = new Object[]{event, listener};
+            } else {
+                int size = listeners.length;
+                Object[] realloc = new Object[size+2];
+                System.arraycopy(listeners, 0, realloc, 0, size);
+                realloc[size] = event;
+                realloc[size+1] = listener;
+                listeners = realloc;
+            }
+        }
+    }
+
+    @Override
+    public <T extends Event> void removeEventListener(Class<T> event, EventListener<? super T> listener) {
+        // atomic operation, signal update
+        synchronized(this) {
+            if(null == listeners) {
+                return;
+            }
+            int index = -1;
+            for (int i = listeners.length-2; i>=0; i-=2) {
+                if ((listeners[i]==event) && listeners[i+1].equals(listener) ) {
+                    index = i;
+                    break;
+                }
+            }
+
+            if (index != -1) {
+                if(listeners.length==2) {
+                    listeners = null;
+                } else {
+                    int size = listeners.length-2;
+                    Object[] realloc = new Object[size];
+                    System.arraycopy(listeners, 0, realloc, 0, index);
+                    if (index < size) {
+                        System.arraycopy(listeners, index+2, realloc, index, size-index);
+                    }
+                    listeners = realloc;
+                }
+            }
+        }
+    }
+
+    @Override
+    public void removeAllEventListeners() {
+        // atomic operation, signal update
+        synchronized(this) {
+            listeners = null;
+        }
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public Collection<EventBinding<?>> getEventListeners() {
+        Object[] array;
+        // read updated
+        synchronized(this) {
+            array = listeners;
+        }
+        // read update
+        if(null == array) {
+            return Collections.emptyList();
+        }
+        List<EventBinding<?>> list = new ArrayList<>(array.length/2);
+        for(int i=0, n=array.length; i<n; i+=2) {
+            list.add(new EventBinding.Immutable((Class)array[i], (EventListener)array[i+1]));
+        }
+        return list;
+    }
+    
+    @Override
+    public int getEventListenersCount() {
+        synchronized(this) {
+            return null == listeners ? 0 : listeners.length / 2;
+        }
+    }
+
+    /**
+     * Metoda blokująca, uruchamia kolejno obsługę zdarzenia u wszystkich
+     * zarejestrowanych obserwatorów, które na zdarzenie o określonym typie oczekują.
+     * Domyślna implementacja wywołuje tę metodę bezpośrednio w {@link #signalEvent}.
+     * Metody pochodne mogą użyć poniższej metody w zupełnie inny sposób i w
+     * zupełnie innym wątku, aby zaimplementować inną strategię przetwarzania zdarzeń.
+     *
+     * <div class="message-note">thread-safe method</div>
+     * @param event
+     */
+    protected void dispatchEvent(Event event) {
+        dispatchEvent(event, false);
+    }
+
+    @SuppressWarnings("unchecked")
+    protected void dispatchEvent(Event event, boolean direct) {
+        Object[] array;
+        // read updated
+        synchronized(this) {
+            array = this.listeners;
+        }
+        if(array == null) {
+            return;
+        }
+        Class<?> clazz = event.getClass();
+        for(int i=0, n=array.length; i<n; i+=2) {
+            if( ((Class)array[i]).isAssignableFrom(clazz) ) {
+                try {
+                    if(direct) {
+                        ((EventListener)array[i+1]).handleEvent(event);
+                    } else {
+                        dispatchEvent((EventListener)array[i+1], event);
+                    }
+                } catch(Exception ex) {
+					String message = String.format("Exception thrown from event listener. Thread: %s. Listener: %s. Event: %s", 
+						Thread.currentThread().getName(), 
+						array[i+1], event
+					);
+					LOGGER.debug(message, ex);
+                    signalEvent(Events.failure(ex));
+                }
+            }
+        }
+    }
+
+    protected <T extends Event> void dispatchEvent(EventListener<? super T> listener, T event) {
+        listener.handleEvent(event);
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p>
+     * Metoda czeka na obsłużenie komunikatu przez wszystkich zarejestrowanych obserwatorów.
+     * Obsługa komunikatu jest uruchamiana natychmiastowo, w bieżącym wątku
+     * (t.j. w tym samym, który zasygnalizował zdarzenie).
+     * </p>
+     * <p>Interesującym przypadkiem jest przekazanie Dispatcher'owi obserwatora,
+     * który jest {@code EventRouterThread}'em. Taki obserwator w bieżącym wątku
+     * obsłuży zdarzenie <i>w swoim imieniu</i>, ale ta obsługa, to nic więcej, niż zapis do
+     * wewnętrznej kolejki. Dokładniej, zgodnie z dokumentacją:</p>
+     *  <ul>
+     *      <li>ruszy metoda {@link EventRouterThread#handleEvent}</li>
+     *      <li>metoda wydeleguje obsługę do {@link EventRouterThread#signalEvent}</li>
+     *      <li>{@code signalEvent} wstawi komunikat do kolejki zdarzeń oczekujących</li>
+     *      <li>wstawiony komunikat zostanie przekazany do obserwatorów w zupełnie
+     *      innym wątku</li>
+     *      <li>w szczególności, czas obsługi jest całkowicie niezdefiniowany.
+     *          Może nastąpić przed zakończeniem metody signal z bieżącego wątku.
+     *          A może być odwrotnie - nastapić długo po jej zakończeniu</li>
+     *  </ul>
+     *
+     * <div class="message-note">thread-safe method</div>
+     * @param event
+     */
+    @Override
+    public boolean signalEvent(Event event) {
+		ThreadUtils.dump(LOGGER.isTraceEnabled(), LOGGER::trace);
+        dispatchEvent(event);
+        return true;
+    }
+
+    /**
+     * Rozsyła otrzymany komunikat do wszystkich obserwatorów, delegując
+     * obsługę zdarzenia do metody {@link #signalEvent}
+     * <div class="message-note">thread-safe method</div>
+     * @param event
+     */
+    @Override
+    public void handleEvent(Event event) {
+        signalEvent(event);
+    }
+
+    protected final void dispose(boolean direct) {
+        dispatchEvent(Events.dispose(this), direct);
+        // signal update
+        synchronized(this) {
+            this.listeners = null;
+            DISPATCHERS.remove(name());
+        }
+    }
+
+    @Override
+    public void dispose() {
+        dispose(true);
+    }
+    
+}

+ 30 - 28
assira/src/main/java/net/ranides/assira/events/EventHandler.java

@@ -1,28 +1,30 @@
-/*
- * @author Ranides Atterwim <ranides@gmail.com>
- * @copyright Ranides Atterwim
- * @license WTFPL
- * @url http://ranides.net/projects/assira
- */
-package net.ranides.assira.events;
-
-import java.lang.annotation.Documented;
-import static java.lang.annotation.ElementType.METHOD;
-import java.lang.annotation.Retention;
-import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import java.lang.annotation.Target;
-
-/**
- *
- * @author Ranides Atterwim <ranides@gmail.com>
- */
-@Retention(RUNTIME)
-@Target(METHOD)
-@Documented
-public @interface EventHandler {
-    
-    Class<?> event();
-    
-    String router() default "awt!250";
-    
-}
+/*
+ * @author Ranides Atterwim <ranides@gmail.com>
+ * @copyright Ranides Atterwim
+ * @license WTFPL
+ * @url http://ranides.net/projects/assira
+ */
+package net.ranides.assira.events;
+
+import java.lang.annotation.Documented;
+import static java.lang.annotation.ElementType.METHOD;
+import java.lang.annotation.Retention;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+import java.lang.annotation.Target;
+
+/**
+ *
+ * @author Ranides Atterwim <ranides@gmail.com>
+ */
+@Retention(RUNTIME)
+@Target(METHOD)
+@Documented
+public @interface EventHandler {
+    
+    static final String UI = "awt";
+    
+    static final String UI_WAIT = "awt:wait";
+    
+    String router() default "";
+    
+}

+ 168 - 0
assira/src/main/java/net/ranides/assira/events/EventObserver.java

@@ -0,0 +1,168 @@
+/*
+ * @author Ranides Atterwim <ranides@gmail.com>
+ * @copyright Ranides Atterwim
+ * @license WTFPL
+ * @url http://ranides.net/projects/assira
+ */
+package net.ranides.assira.events;
+
+import java.util.List;
+import net.ranides.assira.awt.AWTInvoker;
+import net.ranides.assira.functional.VarFunction;
+import net.ranides.assira.reflection.IAttribute;
+import net.ranides.assira.reflection.IClass;
+import net.ranides.assira.reflection.IMethod;
+
+/**
+ *
+ * @author Ranides Atterwim <ranides@gmail.com>
+ */
+public class EventObserver implements EventListener<Event> {
+    
+    List<IHandler> listeners;
+
+    public EventObserver() {
+        listeners = init(this);
+    }
+    
+    public EventObserver(Object delegator) {
+        listeners = init(delegator);
+    }
+
+    private static List<IHandler> init(Object delegator) {
+        return IClass.typefor(delegator)
+            .methods()
+            .require(EventHandler.class)
+            .require(IAttribute.VOID)
+            .matches(IClass.typeinfo(Event.class).collect())
+            .list(m -> listener(delegator, m));
+    }
+    
+    private static IHandler listener(Object delegator, IMethod m) {
+        EventHandler info = m.annotations().first(EventHandler.class);
+        IClass type = m.arguments().first().type();
+        String router = info.router();
+        switch(router) {
+            case "":
+                return new THandler(type, m.bind(delegator));
+            case EventHandler.UI:
+                return new UIHandler(type, false, m.bind(delegator));
+            case EventHandler.UI_WAIT:
+                return new UIHandler(type, true, m.bind(delegator));
+            default:
+                return new NHandler(router, type, m.bind(delegator));
+        }
+        
+    }
+
+    @Override
+    public void handleEvent(Event event) {
+        for(IHandler handler : listeners) {
+            if(handler.matches(event)) {
+                handler.call(event);
+            }
+        }
+    }
+    
+    private static abstract class IHandler {
+        
+        private final IClass type;
+
+        public IHandler(IClass type) {
+            this.type = type;
+        }
+
+        public boolean matches(Event event) {
+            return type.isInstance(event);
+        }
+        
+        public abstract void call(Event event);
+        
+    }
+    
+    private static final class THandler extends IHandler {
+        
+        private final VarFunction handle;
+
+        public THandler(IClass type, VarFunction handle) {
+            super(type);
+            this.handle = handle;
+        }
+        
+        @Override
+        public void call(Event event) {
+            handle.call(event);
+        }
+        
+    }
+    
+    private static final class UIHandler extends IHandler {
+        
+        private final boolean sync;
+        
+        private final VarFunction handle;
+
+        public UIHandler(IClass type, boolean sync, VarFunction handle) {
+            super(type);
+            this.handle = handle;
+            this.sync = sync;
+        }
+        
+        @Override
+        public void call(Event event) {
+            AWTInvoker.run(sync, () -> handle.call(event));
+        }
+        
+    }
+    
+    private static final class NHandler extends IHandler {
+        
+        private final EventDispatcher dispatcher;
+        
+        public NHandler(String name, IClass type, VarFunction handle) {
+            super(type);
+            this.dispatcher = EventDispatcher.find(name);
+            this.dispatcher.addEventListener(DEvent.class, new DEventListener(this, handle));
+        }
+        
+        @Override
+        public void call(Event event) {
+            this.dispatcher.signalEvent(new DEvent(this, event));
+        }
+        
+    }
+    
+    private static final class DEventListener implements EventListener<DEvent> {
+        
+        private final Object tag;
+        
+        private final VarFunction handle;
+
+        public DEventListener(Object tag, VarFunction handle) {
+            this.tag = tag;
+            this.handle = handle;
+        }
+        
+        @Override
+        public void handleEvent(DEvent event) {
+            if(event.tag == tag) {
+                handle.call(event.src);
+            }
+        }
+        
+    }
+    
+    private static final class DEvent implements Event {
+    
+        public final Object tag;
+        
+        public final Event src;
+
+        public DEvent(Object tag, Event event) {
+            this.tag = tag;
+            this.src = event;
+        }
+        
+    }
+    
+}

+ 113 - 118
assira/src/main/java/net/ranides/assira/events/EventProactor.java

@@ -1,118 +1,113 @@
-/*
- * @author Ranides Atterwim <ranides@gmail.com>
- * @copyright Ranides Atterwim
- * @license WTFPL
- * @url http://ranides.net/projects/assira
- */
-package net.ranides.assira.events;
-
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
-import net.ranides.assira.trace.LoggerUtils;
-import org.slf4j.Logger;
-
-/**
- * EventProactor, obsługuje każdego listenera i każde zdarzenie asynchronicznie.
- * Korzysta z puli wątków, żeby nie powodować eksplozji wątków.
- *
- * Standardowy ThreadPoolExecutor ma w sumie głupią tę strategię zarządzania pulą,
- * napisać *kiedyś* własną wersję, albo przemyśleć konfigurację.
- *
- * <div class="message-note">thread-safe method</div>
- * @author ranides
- */
-public class EventProactor extends EventDispatcher {
-
-    private static final Logger LOGGER = LoggerUtils.getLogger();
-
-    private final Object lock = new Object();
-    private final ThreadPoolExecutor executor;
-    private final String name;
-    private final AtomicInteger threads = new AtomicInteger(0);
-    private final BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();
-
-    public static EventProactor newInstance(String name, int size) {
-        return new EventProactor(name, size);
-    }
-
-    protected EventProactor(final String name, final int size) {
-        this.name = name;
-        this.executor = new ThreadPoolExecutor(
-            size, size, 
-            0L, TimeUnit.MILLISECONDS, 
-            queue, 
-            this::newThread, 
-            this::rejectedExecution
-        );
-    }
-    
-    public EventProactor stop() {
-        // read updated, signal update, in atomic
-        synchronized(lock) {
-            if( executor.isShutdown()) {
-                return this;
-            }
-            signalEvent(Events.stop(this));
-            executor.shutdown();
-        }
-        this.join();
-        dispatchEvent(Events.shutdown(EventProactor.this), true);
-        dispose(true);
-        return this;
-    }
-
-    public EventProactor join() {
-        try { executor.awaitTermination(Integer.MAX_VALUE, TimeUnit.MILLISECONDS); }
-        catch (InterruptedException _e) { /* do nothing */ }
-        return this;
-    }
-
-    public String name() {
-        return name;
-    }
-
-    @Override
-    protected <T extends Event> void dispatchEvent(final EventListener<? super T> listener, final T event) {
-        executor.execute(()->listener.handleEvent(event));
-    }
-
-    @Override
-    public void dispose() {
-        // read updated, signal update, in atomic
-        synchronized(lock) {
-            if( executor.isShutdown()) {
-                return;
-            }
-            signalEvent(Events.stop(this));
-            executor.shutdown();
-        }
-
-        new Thread(){
-            @Override
-            public void run() {
-                EventProactor.this.join();
-                dispatchEvent(Events.shutdown(EventProactor.this), true);
-                dispose(true);
-            }
-        }.start();
-    }
-
-    @Override
-    public String toString() {
-        return "EventProactor<" + name + ":" + threads.get() + ">";
-    }
-    
-    private Thread newThread(Runnable runnable) {
-        String tid = name + "-" + threads.getAndIncrement();
-        LOGGER.debug("new thread: {}", tid);
-        return new Thread(runnable, tid);
-    }
-    
-    private void rejectedExecution(Runnable task, ThreadPoolExecutor $0) {
-        LOGGER.warn("ignored task: {}", task);
-    }
-
-}
+/*
+ * @author Ranides Atterwim <ranides@gmail.com>
+ * @copyright Ranides Atterwim
+ * @license WTFPL
+ * @url http://ranides.net/projects/assira
+ */
+package net.ranides.assira.events;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import net.ranides.assira.trace.LoggerUtils;
+import org.slf4j.Logger;
+
+/**
+ * EventProactor, obsługuje każdego listenera i każde zdarzenie asynchronicznie.
+ * Korzysta z puli wątków, żeby nie powodować eksplozji wątków.
+ *
+ * Standardowy ThreadPoolExecutor ma w sumie głupią tę strategię zarządzania pulą,
+ * napisać *kiedyś* własną wersję, albo przemyśleć konfigurację.
+ *
+ * <div class="message-note">thread-safe method</div>
+ * @author ranides
+ */
+public class EventProactor extends EventDispatcher {
+
+    private static final Logger LOGGER = LoggerUtils.getLogger();
+
+    private final Object lock = new Object();
+    private final ThreadPoolExecutor executor;
+    private final AtomicInteger threads = new AtomicInteger(0);
+    private final BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();
+
+    public static EventProactor newInstance(String name, int size) {
+        return new EventProactor(name, size);
+    }
+
+    protected EventProactor(final String name, final int size) {
+        super(name);
+        this.executor = new ThreadPoolExecutor(
+            size, size, 
+            0L, TimeUnit.MILLISECONDS, 
+            queue, 
+            this::newThread, 
+            this::rejectedExecution
+        );
+    }
+    
+    public EventProactor stop() {
+        // read updated, signal update, in atomic
+        synchronized(lock) {
+            if( executor.isShutdown()) {
+                return this;
+            }
+            signalEvent(Events.stop(this));
+            executor.shutdown();
+        }
+        this.join();
+        dispatchEvent(Events.shutdown(EventProactor.this), true);
+        dispose(true);
+        return this;
+    }
+
+    public EventProactor join() {
+        try { executor.awaitTermination(Integer.MAX_VALUE, TimeUnit.MILLISECONDS); }
+        catch (InterruptedException _e) { /* do nothing */ }
+        return this;
+    }
+
+    @Override
+    protected <T extends Event> void dispatchEvent(final EventListener<? super T> listener, final T event) {
+        executor.execute(()->listener.handleEvent(event));
+    }
+
+    @Override
+    public void dispose() {
+        // read updated, signal update, in atomic
+        synchronized(lock) {
+            if( executor.isShutdown()) {
+                return;
+            }
+            signalEvent(Events.stop(this));
+            executor.shutdown();
+        }
+
+        new Thread(){
+            @Override
+            public void run() {
+                EventProactor.this.join();
+                dispatchEvent(Events.shutdown(EventProactor.this), true);
+                dispose(true);
+            }
+        }.start();
+    }
+
+    @Override
+    public String toString() {
+        return "EventProactor<" + name() + ":" + threads.get() + ">";
+    }
+    
+    private Thread newThread(Runnable runnable) {
+        String tid = name() + "-" + threads.getAndIncrement();
+        LOGGER.debug("new thread: {}", tid);
+        return new Thread(runnable, tid);
+    }
+    
+    private void rejectedExecution(Runnable task, ThreadPoolExecutor $0) {
+        LOGGER.warn("ignored task: {}", task);
+    }
+
+}

+ 236 - 241
assira/src/main/java/net/ranides/assira/events/EventReactor.java

@@ -1,241 +1,236 @@
-/*
- * @author Ranides Atterwim <ranides@gmail.com>
- * @copyright Ranides Atterwim
- * @license WTFPL
- * @url http://ranides.net/projects/assira
- */
-package net.ranides.assira.events;
-
-import java.util.concurrent.ArrayBlockingQueue;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.TimeUnit;
-import net.ranides.assira.trace.LoggerUtils;
-import net.ranides.assira.trace.ThreadUtils;
-import org.slf4j.Logger;
-
-/**
- * EventRouter kolejkujący zdarzenia. Wywołuje procedury obsługi w bliżej
- * nieokreślonym czasie po zasygnalizowaniu zdarzenia.
- * <p>
- * Procedury obsługi zdarzenia zarejestrowanych obserwatorów są wywoływane
- * w oddzielnym wątku, zarządzanym przez {@code EventRouter}. Należy mieć to na
- * uwadze, ponieważ jest to zupełnie inny wątek, niż ten, który zarejestrował obserwatora,
- * albo który zasygnalizował event.
- * </p>
- *
- * <div class="message-note">thread-safe method</div>
- * @author ranides
- */
-public class EventReactor extends EventDispatcher {
-    
-    private static final Logger LOGGER = LoggerUtils.getLogger();
-
-    private final BlockingQueue<Event> events;
-    private final long maxtime;
-    private final String name;
-    private final Thread distributor;
-    private final Events.Stop exit;
-    
-    /**
-     * Tworzy nowy obiekt {@code EventRouter}. Utworzony obiekt należy zniszczyć po użyciu za pomocą
-     * {@link #dispose() }. Przechowuje nieograniczoną ilość komunikatów oraz nie
-     * narzuca limitów czasowych obsługi zdarzanie.
-     * @param name nazwa wątku, który będzie przetwarzał zdarzenia
-     * @return
-     */
-    public static EventReactor newInstance(String name) {
-        return newInstance(name, null);
-    }
-    
-    /**
-     * Tworzy nowy obiekt {@code EventRouter}. Utworzony obiekt należy zniszczyć po użyciu za pomocą
-     * {@link #dispose() }. Przechowuje nieograniczoną ilość komunikatów oraz nie
-     * narzuca limitów czasowych obsługi zdarzanie.
-     * <p>
-     * Utworzony obiekt {@code EventRouter} obsługuje mechanizm złączania zdarzeń
-     * w oparciu o strategię realizowaną przez {@link EventJoiner} przekazany jako
-     * argument.
-     * </p>
-     * @param name nazwa wątku, który będzie przetwarzał zdarzenia
-     * @param joiner procedura implementująca mechanizm łączenia wielu zdarzeń w jedno
-     * @return
-     */
-    public static EventReactor newInstance(String name, EventJoiner joiner) {
-        return newInstance(name, 0, 0, joiner);
-    }
-    
-    /**
-     * Tworzy nowy obiekt {@code EventRouter}. Utworzony obiekt należy zniszczyć po użyciu za pomocą
-     * {@link #dispose() }.
-     * @param name nazwa wątku, w którym obsługiwane są zdarzenia
-     * @param size maksymalny rozmiar wewnętrznej kolejki komunikatów
-     * @param maxtime maksymalny czas oczekiwania na miejsce w kolejce komunikatów
-     * @return
-     */
-    public static EventReactor newInstance(String name, int size, long maxtime) {
-        return newInstance(name, size, maxtime, null);
-    }
-
-    /**
-     * Tworzy nowy obiekt {@code EventRouter}. Utworzony obiekt należy zniszczyć po użyciu za pomocą
-     * {@link #dispose() }.
-     * <p>
-     * Utworzony obiekt {@code EventRouter} obsługuje mechanizm złączania zdarzeń
-     * w oparciu o strategię realizowaną przez {@link EventJoiner} przekazany jako
-     * argument.
-     * </p>
-     * @param name nazwa wątku, który będzie przetwarzał zdarzenia
-     * @param size maksymalny rozmiar wewnętrznej kolejki komunikatów
-     * @param maxtime maksymalny czas oczekiwania na miejsce w kolejce komunikatów
-     * @param joiner procedura implementująca mechanizm łączenia wielu zdarzeń w jedno
-     * @return
-     */
-    public static EventReactor newInstance(String name, int size, long maxtime, EventJoiner joiner) {
-        return new EventReactor(name, size, maxtime, joiner).start();
-    }
-
-    /**
-     * Konstruktor chroniony, żeby niechcący nie utworzyć routera, który nic nie robi.
-     * {@code EventRouter} zaczyna działać dopiero po wywołaniu metody {@link #start()}
-     * @param name nazwa wątku
-     * @param maxtime
-     * @param size
-     * @param joiner
-     */
-    protected EventReactor(final String name, int size, long maxtime, final EventJoiner joiner) {
-        this.maxtime = maxtime;
-        this.events = createQueue(size);
-        this.name = name;
-        this.exit = Events.stop(this);
-        this.distributor = new Thread(name) {
-
-            @Override
-            public void run() {
-                boolean interrupted = true;
-                try {
-                    while (true) {
-                        Event event = events.take();
-                        while(null != joiner && joiner.isJoinable(event, events.peek()) ) {
-                            event = joiner.join(event, events.poll());
-                        }
-                        dispatchEvent(event);
-                        if( dispatchExit(event) ) { interrupted=false; break; }
-                    }
-                } catch (InterruptedException _e) {
-                    dispatchEvent(Events.interrupt(EventReactor.this));
-                    interrupted = false;
-                }
-                if(!interrupted) {
-                    dispatchEvent(Events.shutdown(EventReactor.this));
-                    // dispatchEvent(CoreEvent.dispose(EventReactor.this));
-                }
-            }
-        };
-    }
-    
-    private static BlockingQueue<Event> createQueue(int size) {
-        if( 0==size || Integer.MAX_VALUE==size) {
-            return new LinkedBlockingQueue<>();
-        } else {
-            return new ArrayBlockingQueue<>(size, true);
-        }
-    }
-
-    private EventReactor start() {
-        distributor.start();
-        return this;
-    }
-
-    /**
-     * Wyłącza {@code EventRouter}. Metoda blokująca - czeka, aż EventReactor
-     * poprawnie zwolni wszystkie zasoby.
-     * <div class="message-note">thread-safe method</div>
-     * @return
-     */
-    public EventReactor stop() {
-        signalEvent(exit);
-        join();
-        super.dispose();
-        return this;
-    }
-
-    /**
-     * Metoda blokująca - czeka, aż event router zakończy swoje działanie.
-     * <p>
-     * Uwaga! Żeby metoda kiedykolwiek się zakończyła, w aplikacji musi istnieć
-     * co najmniej jeden dodatkowy wątek, który wyśle do routera {@link ExitEvent}
-     * lub wywoła metodę {@link #stop}.
-     * </p>
-     * <div class="message-note">thread-safe method</div>
-     * @return
-     */
-    public EventReactor join() {
-        try { distributor.join(); }
-        catch (InterruptedException _e) { /* do nothing */ }
-        return this;
-    }
-
-    public String name() {
-        return name;
-    }
-
-    @SuppressWarnings("PMD.CompareObjectsWithEquals")
-    private boolean dispatchExit(Event event) {
-        if(exit == event) { return true; }
-        return (event instanceof Events.Dispose) && this == ((Events.Dispose)event).router();
-    }
-
-    /**
-     * {@inheritDoc}.
-     * <p>
-     * Wstawia jedynie zdarzenie do kolejki komunikatów.
-     * Jeśli kolejka jest w całości wypełniona, tzn osiągnęłą swoją maksymalną pojemność,
-     * to czeka na zwolnienie w niej miejsca przez maksymalny dopuszczalny czas.
-     * Obie wartości (rozmiar kolejki, długość czekania) są definiowane podczas
-     * konstrukcji routera. Jeśli w kolejce nadal nie ma miejsca - to kończy działanie
-     * zwracając {@code false}.
-     * </p><p>
-     * Metoda nie czeka na obsłużenie komunikatu przez zarejestrowanych obserwatorów.
-     * Obsługa komunikatu jest uruchamiana w oddzielnym wątku zarządzanym przez router.
-     * </p>
-     * <div class="message-note">thread-safe method</div>
-     * @param event
-     * @return true - jeśli zdarzenie zostało zlecone do przekazania.
-     */
-    @Override
-    public boolean signalEvent(Event event) {
-		ThreadUtils.dump(LOGGER.isTraceEnabled(), LOGGER::trace);
-        if(0 == maxtime) {
-            return events.offer(event);
-        }
-        try {
-            return events.offer(event, maxtime, TimeUnit.MILLISECONDS);
-        } catch (InterruptedException _e) {
-            return false;
-        }
-    }
-
-    /**
-     * Zleca wyłączenie i zwalnienie zasobów routerowi. Nie czeka na zakończenie
-     * jego pracy.
-     * <div class="message-note">thread-safe method</div>
-     */
-    @Override
-    public void dispose() {
-        signalEvent(exit);
-        new Thread(){
-            @Override
-            public void run() {
-                EventReactor.this.join();
-                EventReactor.super.dispose();
-            }
-        }.start();
-    }
-
-    @Override
-    public String toString() {
-        return "EventReactor<" + name + ">";
-    }
-
-}
+/*
+ * @author Ranides Atterwim <ranides@gmail.com>
+ * @copyright Ranides Atterwim
+ * @license WTFPL
+ * @url http://ranides.net/projects/assira
+ */
+package net.ranides.assira.events;
+
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+import net.ranides.assira.trace.LoggerUtils;
+import net.ranides.assira.trace.ThreadUtils;
+import org.slf4j.Logger;
+
+/**
+ * EventRouter kolejkujący zdarzenia. Wywołuje procedury obsługi w bliżej
+ * nieokreślonym czasie po zasygnalizowaniu zdarzenia.
+ * <p>
+ * Procedury obsługi zdarzenia zarejestrowanych obserwatorów są wywoływane
+ * w oddzielnym wątku, zarządzanym przez {@code EventRouter}. Należy mieć to na
+ * uwadze, ponieważ jest to zupełnie inny wątek, niż ten, który zarejestrował obserwatora,
+ * albo który zasygnalizował event.
+ * </p>
+ *
+ * <div class="message-note">thread-safe method</div>
+ * @author ranides
+ */
+public class EventReactor extends EventDispatcher {
+    
+    private static final Logger LOGGER = LoggerUtils.getLogger();
+
+    private final BlockingQueue<Event> events;
+    private final long maxtime;
+    private final Thread distributor;
+    private final Events.Stop exit;
+    
+    /**
+     * Tworzy nowy obiekt {@code EventRouter}. Utworzony obiekt należy zniszczyć po użyciu za pomocą
+     * {@link #dispose() }. Przechowuje nieograniczoną ilość komunikatów oraz nie
+     * narzuca limitów czasowych obsługi zdarzanie.
+     * @param name nazwa wątku, który będzie przetwarzał zdarzenia
+     * @return
+     */
+    public static EventReactor newInstance(String name) {
+        return newInstance(name, null);
+    }
+    
+    /**
+     * Tworzy nowy obiekt {@code EventRouter}. Utworzony obiekt należy zniszczyć po użyciu za pomocą
+     * {@link #dispose() }. Przechowuje nieograniczoną ilość komunikatów oraz nie
+     * narzuca limitów czasowych obsługi zdarzanie.
+     * <p>
+     * Utworzony obiekt {@code EventRouter} obsługuje mechanizm złączania zdarzeń
+     * w oparciu o strategię realizowaną przez {@link EventJoiner} przekazany jako
+     * argument.
+     * </p>
+     * @param name nazwa wątku, który będzie przetwarzał zdarzenia
+     * @param joiner procedura implementująca mechanizm łączenia wielu zdarzeń w jedno
+     * @return
+     */
+    public static EventReactor newInstance(String name, EventJoiner joiner) {
+        return newInstance(name, 0, 0, joiner);
+    }
+    
+    /**
+     * Tworzy nowy obiekt {@code EventRouter}. Utworzony obiekt należy zniszczyć po użyciu za pomocą
+     * {@link #dispose() }.
+     * @param name nazwa wątku, w którym obsługiwane są zdarzenia
+     * @param size maksymalny rozmiar wewnętrznej kolejki komunikatów
+     * @param maxtime maksymalny czas oczekiwania na miejsce w kolejce komunikatów
+     * @return
+     */
+    public static EventReactor newInstance(String name, int size, long maxtime) {
+        return newInstance(name, size, maxtime, null);
+    }
+
+    /**
+     * Tworzy nowy obiekt {@code EventRouter}. Utworzony obiekt należy zniszczyć po użyciu za pomocą
+     * {@link #dispose() }.
+     * <p>
+     * Utworzony obiekt {@code EventRouter} obsługuje mechanizm złączania zdarzeń
+     * w oparciu o strategię realizowaną przez {@link EventJoiner} przekazany jako
+     * argument.
+     * </p>
+     * @param name nazwa wątku, który będzie przetwarzał zdarzenia
+     * @param size maksymalny rozmiar wewnętrznej kolejki komunikatów
+     * @param maxtime maksymalny czas oczekiwania na miejsce w kolejce komunikatów
+     * @param joiner procedura implementująca mechanizm łączenia wielu zdarzeń w jedno
+     * @return
+     */
+    public static EventReactor newInstance(String name, int size, long maxtime, EventJoiner joiner) {
+        return new EventReactor(name, size, maxtime, joiner).start();
+    }
+
+    /**
+     * Konstruktor chroniony, żeby niechcący nie utworzyć routera, który nic nie robi.
+     * {@code EventRouter} zaczyna działać dopiero po wywołaniu metody {@link #start()}
+     * @param name nazwa wątku
+     * @param maxtime
+     * @param size
+     * @param joiner
+     */
+    protected EventReactor(final String name, int size, long maxtime, final EventJoiner joiner) {
+        super(name);
+        this.maxtime = maxtime;
+        this.events = createQueue(size);
+        this.exit = Events.stop(this);
+        this.distributor = new Thread(name) {
+
+            @Override
+            public void run() {
+                boolean interrupted = true;
+                try {
+                    while (true) {
+                        Event event = events.take();
+                        while(null != joiner && joiner.isJoinable(event, events.peek()) ) {
+                            event = joiner.join(event, events.poll());
+                        }
+                        dispatchEvent(event);
+                        if( dispatchExit(event) ) { interrupted=false; break; }
+                    }
+                } catch (InterruptedException _e) {
+                    dispatchEvent(Events.interrupt(EventReactor.this));
+                    interrupted = false;
+                }
+                if(!interrupted) {
+                    dispatchEvent(Events.shutdown(EventReactor.this));
+                    // dispatchEvent(CoreEvent.dispose(EventReactor.this));
+                }
+            }
+        };
+    }
+    
+    private static BlockingQueue<Event> createQueue(int size) {
+        if( 0==size || Integer.MAX_VALUE==size) {
+            return new LinkedBlockingQueue<>();
+        } else {
+            return new ArrayBlockingQueue<>(size, true);
+        }
+    }
+
+    private EventReactor start() {
+        distributor.start();
+        return this;
+    }
+
+    /**
+     * Wyłącza {@code EventRouter}. Metoda blokująca - czeka, aż EventReactor
+     * poprawnie zwolni wszystkie zasoby.
+     * <div class="message-note">thread-safe method</div>
+     * @return
+     */
+    public EventReactor stop() {
+        signalEvent(exit);
+        join();
+        super.dispose();
+        return this;
+    }
+
+    /**
+     * Metoda blokująca - czeka, aż event router zakończy swoje działanie.
+     * <p>
+     * Uwaga! Żeby metoda kiedykolwiek się zakończyła, w aplikacji musi istnieć
+     * co najmniej jeden dodatkowy wątek, który wyśle do routera {@link ExitEvent}
+     * lub wywoła metodę {@link #stop}.
+     * </p>
+     * <div class="message-note">thread-safe method</div>
+     * @return
+     */
+    public EventReactor join() {
+        try { distributor.join(); }
+        catch (InterruptedException _e) { /* do nothing */ }
+        return this;
+    }
+
+    @SuppressWarnings("PMD.CompareObjectsWithEquals")
+    private boolean dispatchExit(Event event) {
+        if(exit == event) { return true; }
+        return (event instanceof Events.Dispose) && this == ((Events.Dispose)event).router();
+    }
+
+    /**
+     * {@inheritDoc}.
+     * <p>
+     * Wstawia jedynie zdarzenie do kolejki komunikatów.
+     * Jeśli kolejka jest w całości wypełniona, tzn osiągnęłą swoją maksymalną pojemność,
+     * to czeka na zwolnienie w niej miejsca przez maksymalny dopuszczalny czas.
+     * Obie wartości (rozmiar kolejki, długość czekania) są definiowane podczas
+     * konstrukcji routera. Jeśli w kolejce nadal nie ma miejsca - to kończy działanie
+     * zwracając {@code false}.
+     * </p><p>
+     * Metoda nie czeka na obsłużenie komunikatu przez zarejestrowanych obserwatorów.
+     * Obsługa komunikatu jest uruchamiana w oddzielnym wątku zarządzanym przez router.
+     * </p>
+     * <div class="message-note">thread-safe method</div>
+     * @param event
+     * @return true - jeśli zdarzenie zostało zlecone do przekazania.
+     */
+    @Override
+    public boolean signalEvent(Event event) {
+		ThreadUtils.dump(LOGGER.isTraceEnabled(), LOGGER::trace);
+        if(0 == maxtime) {
+            return events.offer(event);
+        }
+        try {
+            return events.offer(event, maxtime, TimeUnit.MILLISECONDS);
+        } catch (InterruptedException _e) {
+            return false;
+        }
+    }
+
+    /**
+     * Zleca wyłączenie i zwalnienie zasobów routerowi. Nie czeka na zakończenie
+     * jego pracy.
+     * <div class="message-note">thread-safe method</div>
+     */
+    @Override
+    public void dispose() {
+        signalEvent(exit);
+        new Thread(){
+            @Override
+            public void run() {
+                EventReactor.this.join();
+                EventReactor.super.dispose();
+            }
+        }.start();
+    }
+
+    @Override
+    public String toString() {
+        return "EventReactor<" + name() + ">";
+    }
+
+}

+ 73 - 71
assira/src/main/java/net/ranides/assira/events/EventRouter.java

@@ -1,71 +1,73 @@
-/*
- * @author Ranides Atterwim <ranides@gmail.com>
- * @copyright Ranides Atterwim
- * @license WTFPL
- * @url http://ranides.net/projects/assira
- */
-package net.ranides.assira.events;
-
-import java.util.Collection;
-
-/**
- * Klasa EventRouter propaguje zasygnalizowane zdarzenia do odbiorców.
- * Każdy router potrafi nie tylko rozsyłać zdarzenia, ale również odbierać -
- * implementuje interfejs {@code EventListener<Event>} i może być rejestrowany
- * jako obserwator. Każde zdarzenie można skierować do routera na dwa sposoby:
- * <ul>
- *  <li>metodą {@link #signalEvent} - wtedy mamy możliwość sprawdzenia,
- * czy komunikat udało się dostarczyć</li>
- *  <li>metodą {@link EventListener#handleEvent} - wtedy takiej możliwości nie
- * posiadamy</li>
- * </ul>
- * @author ranides
- */
-public interface EventRouter extends EventListener<Event> {
-
-    /**
-     * Rejestruje obserwatora, który otrzyma wszystkie zdarzenia o podanym typie.
-     * <div class="message-note">thread-safe method</div>
-     * @param <T>
-     * @param event
-     * @param listener
-     */
-    <T extends Event> void addEventListener(Class<T> event, EventListener<? super T> listener);
-
-    /**
-     * Usuwa obserwatora, którego wcześniej zarejestrowano dla zdarzeń o
-     * podanym typie. Jeśli zajestrowano go również dla zdarzeń o innych typach
-     * - zostają one nadal. Jeśli zarejestrowano go wielokrotnie - musi zostać
-     * usunięty również wielokrotnie.
-     * <div class="message-note">thread-safe method</div>
-     * @param <T>
-     * @param event
-     * @param listener
-     */
-    <T extends Event> void removeEventListener(Class<T> event, EventListener<? super T> listener);
-
-    /**
-     * Usuwa wszystkie zarejestrowane obserwatory.
-     * <div class="message-note">thread-safe method</div>
-     */
-    void removeAllEventListeners();
-
-    Collection<EventBinding<?>> getEventListeners();
-    
-    int getEventListenersCount();
-
-    /**
-     * Sygnalizuje podane zdarzenie rozsyłając komunikat do wszystkich obserwatorów.
-     * Jeśli rozesłanie komunikatu nie jest możliwe, zwraca {@code false}.
-     * @param event
-     * @return true - jeśli zdarzenie może być przekazane odbiorcom.
-     */
-    boolean signalEvent(Event event);
-
-    /**
-     * Kończy działanie routera i zwalnia wszystkie zasoby.
-     * <div class="message-note">thread-safe method</div>
-     */
-    void dispose();
-
-}
+/*
+ * @author Ranides Atterwim <ranides@gmail.com>
+ * @copyright Ranides Atterwim
+ * @license WTFPL
+ * @url http://ranides.net/projects/assira
+ */
+package net.ranides.assira.events;
+
+import java.util.Collection;
+
+/**
+ * Klasa EventRouter propaguje zasygnalizowane zdarzenia do odbiorców.
+ * Każdy router potrafi nie tylko rozsyłać zdarzenia, ale również odbierać -
+ * implementuje interfejs {@code EventListener<Event>} i może być rejestrowany
+ * jako obserwator. Każde zdarzenie można skierować do routera na dwa sposoby:
+ * <ul>
+ *  <li>metodą {@link #signalEvent} - wtedy mamy możliwość sprawdzenia,
+ * czy komunikat udało się dostarczyć</li>
+ *  <li>metodą {@link EventListener#handleEvent} - wtedy takiej możliwości nie
+ * posiadamy</li>
+ * </ul>
+ * @author ranides
+ */
+public interface EventRouter extends EventListener<Event> {
+
+    String name();
+    
+    /**
+     * Rejestruje obserwatora, który otrzyma wszystkie zdarzenia o podanym typie.
+     * <div class="message-note">thread-safe method</div>
+     * @param <T>
+     * @param event
+     * @param listener
+     */
+    <T extends Event> void addEventListener(Class<T> event, EventListener<? super T> listener);
+
+    /**
+     * Usuwa obserwatora, którego wcześniej zarejestrowano dla zdarzeń o
+     * podanym typie. Jeśli zajestrowano go również dla zdarzeń o innych typach
+     * - zostają one nadal. Jeśli zarejestrowano go wielokrotnie - musi zostać
+     * usunięty również wielokrotnie.
+     * <div class="message-note">thread-safe method</div>
+     * @param <T>
+     * @param event
+     * @param listener
+     */
+    <T extends Event> void removeEventListener(Class<T> event, EventListener<? super T> listener);
+
+    /**
+     * Usuwa wszystkie zarejestrowane obserwatory.
+     * <div class="message-note">thread-safe method</div>
+     */
+    void removeAllEventListeners();
+
+    Collection<EventBinding<?>> getEventListeners();
+    
+    int getEventListenersCount();
+
+    /**
+     * Sygnalizuje podane zdarzenie rozsyłając komunikat do wszystkich obserwatorów.
+     * Jeśli rozesłanie komunikatu nie jest możliwe, zwraca {@code false}.
+     * @param event
+     * @return true - jeśli zdarzenie może być przekazane odbiorcom.
+     */
+    boolean signalEvent(Event event);
+
+    /**
+     * Kończy działanie routera i zwalnia wszystkie zasoby.
+     * <div class="message-note">thread-safe method</div>
+     */
+    void dispose();
+
+}