|
|
@@ -6,12 +6,6 @@
|
|
|
*/
|
|
|
package net.ranides.assira.events;
|
|
|
|
|
|
-import java.lang.ref.WeakReference;
|
|
|
-import java.util.*;
|
|
|
-import java.util.function.Supplier;
|
|
|
-
|
|
|
-import net.ranides.assira.collection.maps.OpenMap;
|
|
|
-import net.ranides.assira.text.StringTraits;
|
|
|
import net.ranides.assira.trace.LoggerUtils;
|
|
|
import net.ranides.assira.trace.ThreadDump;
|
|
|
import org.slf4j.Logger;
|
|
|
@@ -31,44 +25,16 @@ import org.slf4j.Logger;
|
|
|
*
|
|
|
* @author ranides
|
|
|
*/
|
|
|
-public class EventDispatcher implements EventRouter {
|
|
|
-
|
|
|
- /**
|
|
|
- * Dispatcher which ignores all events and all listeners.
|
|
|
- *
|
|
|
- * Please note, that adding listener won't cause any errors, but won't be reflects by "getEventListeners"
|
|
|
- */
|
|
|
- public static final EventDispatcher NULL = new EmptyDispatcher();
|
|
|
+public class EventDispatcher extends EventRouter {
|
|
|
|
|
|
private static final Logger LOGGER = LoggerUtils.getLogger();
|
|
|
-
|
|
|
- private static final Map<String, WeakReference<EventRouter>> DISPATCHERS = new OpenMap<>();
|
|
|
-
|
|
|
- private Object[] listeners = null;
|
|
|
-
|
|
|
- private final String name;
|
|
|
|
|
|
- /**
|
|
|
- * Creates anonymous instant dispatcher.
|
|
|
- * Anonymous dispatchers can't be find by using {@link EventDispatcher#find(String)}.
|
|
|
- *
|
|
|
- * Subclasses should use this constructor only inside protected or private constructors.
|
|
|
- * Although anonymous dispatcher could be created directly, it is preferred not to do so.
|
|
|
- */
|
|
|
- protected EventDispatcher() {
|
|
|
- this.name = "";
|
|
|
+ private EventDispatcher() {
|
|
|
+ super();
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * Creates named instant dispatcher.
|
|
|
- * Subclasses should use this constructor only inside protected or private constructors.
|
|
|
- *
|
|
|
- * Correctly created dispatcher is always created by static method {@link #newInstance(String, Supplier)}.
|
|
|
- *
|
|
|
- * @param name name
|
|
|
- */
|
|
|
- protected EventDispatcher(String name) {
|
|
|
- this.name = name;
|
|
|
+ private EventDispatcher(String name) {
|
|
|
+ super(name);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -91,368 +57,24 @@ public class EventDispatcher implements EventRouter {
|
|
|
* @return dispatcher
|
|
|
*/
|
|
|
public static EventDispatcher newInstance(String name) {
|
|
|
- return newInstance(name, () -> new EventDispatcher(name));
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Creates new dispatcher with provided name, using factory method.
|
|
|
- *
|
|
|
- * Registers provided dispatcher if it is not anonymous.
|
|
|
- * Registered named dispatchers can be find by using {@link EventDispatcher#find(String)}.
|
|
|
- *
|
|
|
- * If there was already dispatcher with specified name,
|
|
|
- * it won't create new one but it will throw IllegalArgumentException.
|
|
|
- *
|
|
|
- * WARNING:
|
|
|
- * all dispatchers MUST BE constructed using this method.
|
|
|
- * Especially subclasses MUST NOT construct objects without using it.
|
|
|
- * This is the only method in whole codebase, which is allowed to create dispatchers.
|
|
|
- *
|
|
|
- * @param name name
|
|
|
- * @param factory factory
|
|
|
- * @param <R> dispatcher type
|
|
|
- * @return dispatcher
|
|
|
- */
|
|
|
- public static <R extends EventRouter> R newInstance(String name, Supplier<R> factory) {
|
|
|
- if(StringTraits.isEmpty(name)) {
|
|
|
- return factory.get();
|
|
|
- }
|
|
|
- synchronized(DISPATCHERS) {
|
|
|
- WeakReference<EventRouter> ref = DISPATCHERS.get(name);
|
|
|
- if (ref != null && ref.get() != null) {
|
|
|
- throw new IllegalArgumentException("EventDispatcher with name " + name + " already exists");
|
|
|
- }
|
|
|
- R that = factory.get();
|
|
|
- DISPATCHERS.put(name, new WeakReference<>(that));
|
|
|
- return that;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Finds named EventDispatcher.
|
|
|
- * If there is no dispatcher with specified name, it returns none.
|
|
|
- *
|
|
|
- * @param name name
|
|
|
- * @return optional
|
|
|
- */
|
|
|
- public static Optional<EventRouter> find(String name) {
|
|
|
- synchronized (DISPATCHERS) {
|
|
|
- WeakReference<EventRouter> ref = DISPATCHERS.get(name);
|
|
|
- if(ref != null) {
|
|
|
- return Optional.ofNullable(ref.get());
|
|
|
- }
|
|
|
- return Optional.empty();
|
|
|
- }
|
|
|
+ return EventRouter.newInstance(name, () -> new EventDispatcher(name));
|
|
|
}
|
|
|
|
|
|
- @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 <T extends Event> void removeEventListener(Class<T> event) {
|
|
|
- // atomic operation, signal update
|
|
|
- synchronized(this) {
|
|
|
- if(null == listeners) {
|
|
|
- return;
|
|
|
- }
|
|
|
- int found = 0;
|
|
|
- for (int i = 0; i <= listeners.length; i += 2) {
|
|
|
- if (listeners[i]==event) {
|
|
|
- found++;
|
|
|
- }
|
|
|
- }
|
|
|
- if(found == listeners.length/2) {
|
|
|
- listeners = null;
|
|
|
- } else {
|
|
|
- Object[] realloc = new Object[listeners.length - 2*found];
|
|
|
-
|
|
|
- int index = 0;
|
|
|
- for (int i = 0; i <= listeners.length; i += 2) {
|
|
|
- if (listeners[i]!=event) {
|
|
|
- realloc[index] = listeners[i];
|
|
|
- realloc[index+1] = listeners[i+1];
|
|
|
- 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 event
|
|
|
- *
|
|
|
- * @todo #85 and then document
|
|
|
- */
|
|
|
- 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 {
|
|
|
- EventListener listener = (EventListener) array[i + 1];
|
|
|
- if(direct) {
|
|
|
- listener.handleEvent(event);
|
|
|
- } else {
|
|
|
- dispatchEvent(listener, 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 EventReactor}-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 EventReactor#handleEvent}</li>
|
|
|
- * <li>metoda wydeleguje obsługę do {@link EventReactor#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>
|
|
|
- *
|
|
|
- * <p>Podobny przypadek to {@code EventProactor}. Kolejność wywołań jest następująca:</p>
|
|
|
- * <ul>
|
|
|
- * <li>ruszy metoda {@link EventProactor#handleEvent}</li>
|
|
|
- * <li>metoda wydeleguje obsługę do {@link EventProactor#signalEvent}</li>
|
|
|
- * <li>metoda wydeleguje obsługę do {@link EventReactor#dispatchEvent}</li>
|
|
|
- * <li>{@code dispatchEvent} przekaże komunikat do ThreadPoolExecutor, który wywoła obsługę
|
|
|
- * w sobie tylko znany sposób.</li>
|
|
|
- * <li>Nie tylko czas obsługi jest całkowicie niezdefiniowany, ale dodatkowo odbiorcy mogą być uruchomieni
|
|
|
- * jednocześnie w kilku wątkach.</li>
|
|
|
- * </ul>
|
|
|
- *
|
|
|
- * <div class="message-note">thread-safe method</div>
|
|
|
- * @param event
|
|
|
- *
|
|
|
- * @todo #85 and then document
|
|
|
- */
|
|
|
@Override
|
|
|
public boolean signalEvent(Event event) {
|
|
|
ThreadDump.printIf(LOGGER.isTraceEnabled(), LOGGER::trace);
|
|
|
- dispatchEvent(event);
|
|
|
+ executeListenersNow(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 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;
|
|
|
- synchronized (DISPATCHERS) {
|
|
|
- DISPATCHERS.remove(name());
|
|
|
- }
|
|
|
- }
|
|
|
+ public void dispose() {
|
|
|
+ executeDisposeNow();
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
- public void dispose() {
|
|
|
- dispose(true);
|
|
|
+ public String toString() {
|
|
|
+ return "EventDispatcher<" + name() + ">";
|
|
|
}
|
|
|
|
|
|
- private static class EmptyDispatcher extends EventDispatcher {
|
|
|
- @Override
|
|
|
- public String name() {
|
|
|
- return "$null";
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public <T extends Event> void addEventListener(Class<T> event, EventListener<? super T> listener) {
|
|
|
- // do nothing
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public <T extends Event> void removeEventListener(Class<T> event, EventListener<? super T> listener) {
|
|
|
- // do nothing
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public <T extends Event> void removeEventListener(Class<T> event) {
|
|
|
- // do nothing
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public void removeAllEventListeners() {
|
|
|
- // do nothing
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public Collection<EventBinding<?>> getEventListeners() {
|
|
|
- return Collections.emptyList();
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public int getEventListenersCount() {
|
|
|
- return 0;
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public boolean signalEvent(Event event) {
|
|
|
- return false;
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public void dispose() {
|
|
|
- // do nothing
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public void handleEvent(Event event) {
|
|
|
- // do nothing
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- protected void dispatchEvent(Event event) {
|
|
|
- // do nothing
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- protected void dispatchEvent(Event event, boolean direct) {
|
|
|
- // do nothing
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- protected <T extends Event> void dispatchEvent(EventListener<? super T> listener, T event) {
|
|
|
- // do nothing
|
|
|
- }
|
|
|
-
|
|
|
- }
|
|
|
}
|