|
|
@@ -1,262 +0,0 @@
|
|
|
-/*
|
|
|
- * @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.reflect.Method;
|
|
|
-import java.util.ArrayList;
|
|
|
-import java.util.Collection;
|
|
|
-import java.util.Iterator;
|
|
|
-import java.util.LinkedList;
|
|
|
-import java.util.List;
|
|
|
-import net.ranides.assira.annotations.Meta;
|
|
|
-import net.ranides.assira.collection.map.MultiHashMap;
|
|
|
-import net.ranides.assira.collection.map.MultiMap;
|
|
|
-import net.ranides.assira.events.ReflectEventListener.ReflectAdapter;
|
|
|
-import net.ranides.assira.reflection.ClassInspector;
|
|
|
-
|
|
|
-/**
|
|
|
- * Obserwator pozwalający tworzyć wiązania z obserwatorami nasłuchującymi zdarzeń
|
|
|
- * o określonym typie oraz nazwie. Wszystkie nazwane zdarzenia dziedziczą po
|
|
|
- * {@link NamedEvent}. Ponieważ mechanizm obsługi zdarzeń nazwanych jest rozszerzeniem
|
|
|
- * klasycznej infrastruktury przekazywania zdarzeń, to może być dowolnie łączony
|
|
|
- * ze standardowymi obiektami {@link EventListener} oraz {@link EventRouter}.
|
|
|
- *
|
|
|
- * <p>
|
|
|
- * {@code NamedEventListener} posiada funkcjonalność bardzo podobną do {@link
|
|
|
- * ReflectEventListener}. Klasy pochodne mogą deklarować metody (niekoniecznie
|
|
|
- * publiczne), które zostaną uruchomione, jeśli do obserwatora zostanie wysłane
|
|
|
- * określone zdarzenie. Deklaracja metody obsługi zdarzeń ma następującą postać:
|
|
|
- * <ul>
|
|
|
- * <li>metoda pobiera tylko jeden argument</li>
|
|
|
- * <li>typ argumentu odpowiada typowi obserwowanego zdarzenia</li>
|
|
|
- * <li>metoda jest adnotowana za pomocą {@link net.ranides.assira.annotations.Meta.EventHandler}</li>
|
|
|
- * <li>{@link net.ranides.assira.annotations.Meta.EventHandler} obowiązkowo zawiera nazwę obserwowanego zdarzenia</li>
|
|
|
- * <li>{@link net.ranides.assira.annotations.Meta.EventHandler} opcjonalnie określa sposób synchronizacji metody względem innych wątków</li>
|
|
|
- * </ul>
|
|
|
- * </p>
|
|
|
- *
|
|
|
- * <table style="margin: 10px 0 0 0; background: #c0F0c0; border: solid 1px #006000;" cellpadding="0"><tr><td>thread-safe class</td></tr></table>
|
|
|
- *
|
|
|
- * @author ranides
|
|
|
- */
|
|
|
-public class NamedEventListener implements Observable {
|
|
|
-
|
|
|
- private static final String GLOBAL = "global://*";
|
|
|
-
|
|
|
- private final MultiMap<String, EventListener<? super Event>> listeners = new MultiHashMap<String, EventListener<? super Event>>(){
|
|
|
- @Override
|
|
|
- protected Collection<EventListener<? super Event>> createCollection() {
|
|
|
- return new LinkedList<EventListener<? super Event>>();
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
- /**
|
|
|
- * {@code NamedEventListener} nie może tworzony bez pośrednio - można jedynie
|
|
|
- * tworzyć jego klasy pochodne. Po otrzymaniu zdarzenia uruchamia wszystkich
|
|
|
- * podległych obserwatorów oraz wszystkie metody obsługi zdarzeń. Przeczytaj
|
|
|
- * opis klasy, aby poznać wymagania wobec metod obsługi zdarzeń.
|
|
|
- */
|
|
|
- protected NamedEventListener() {
|
|
|
- this(true);
|
|
|
- }
|
|
|
-
|
|
|
- NamedEventListener(boolean useReflection) {
|
|
|
- if(!useReflection) {
|
|
|
- return;
|
|
|
- }
|
|
|
- for(Method method : ClassInspector.getDeepMethods(getClass())) {
|
|
|
- EventListener<Event> adapter = ReflectEventListener.wrap(this, method);
|
|
|
- if( adapter != null ) {
|
|
|
- Meta.EventHandler info = method.getAnnotation(Meta.EventHandler.class);
|
|
|
- listeners.putItem(info.value(), adapter);
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public final void handleEvent(Event event) {
|
|
|
- signalEvent(event);
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public boolean signalEvent(Event event) {
|
|
|
- return (event instanceof NamedEvent) ? dispatchEvent((NamedEvent)event) : false;
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public synchronized void dispose() {
|
|
|
- listeners.clear();
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 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ń.
|
|
|
- *
|
|
|
- * <table style="margin: 10px 0 0 0; background: #c0F0c0; border: solid 1px #006000;" cellpadding="0"><tr><td>thread-safe method</td></tr></table>
|
|
|
- * @param event
|
|
|
- * @return
|
|
|
- */
|
|
|
- protected final synchronized boolean dispatchEvent(NamedEvent event) {
|
|
|
- if(listeners == null) {
|
|
|
- return false;
|
|
|
- }
|
|
|
- runBindings(listeners.get(GLOBAL), event);
|
|
|
- runBindings(listeners.get( event.name() ), event);
|
|
|
- return true;
|
|
|
- }
|
|
|
-
|
|
|
- private void runBindings(Collection<EventListener<? super Event>> list, NamedEvent event) {
|
|
|
- if(list == null) {
|
|
|
- return;
|
|
|
- }
|
|
|
- for(EventListener<? super Event> listener : list) {
|
|
|
- listener.handleEvent(event);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public final synchronized NamedEventListener bind(String name, EventListener<? super NamedEvent> listener) {
|
|
|
- listeners.putItem(name, new TypedListener(listener) );
|
|
|
- return this;
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public final synchronized <T extends NamedEvent> Observable bind(String name, Class<T> type, EventListener<? super T> listener) {
|
|
|
- listeners.putItem(name, new TypedListener(type, listener) );
|
|
|
- return this;
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- @SuppressWarnings("unchecked")
|
|
|
- public final synchronized NamedEventListener unbind(String name, EventListener<? super NamedEvent> listener) {
|
|
|
- // świadome rzutowanie: zobacz opis TypedListener
|
|
|
- listeners.removeItem(name, (EventListener)listener );
|
|
|
- return this;
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- @SuppressWarnings("unchecked")
|
|
|
- public final synchronized NamedEventListener unbind(EventListener<? super NamedEvent> listener) {
|
|
|
- for(String name : listeners.keySet()) {
|
|
|
- // świadome rzutowanie: zobacz opis TypedListener
|
|
|
- listeners.removeItem(name, (EventListener)listener );
|
|
|
- }
|
|
|
- return this;
|
|
|
- }
|
|
|
-
|
|
|
- @SuppressWarnings("unchecked")
|
|
|
- @Override
|
|
|
- public final <T extends Event> void addEventListener(Class<T> event, EventListener<? super T> listener) {
|
|
|
- if( !NamedEvent.class.isAssignableFrom(event) ) {
|
|
|
- throw new UnsupportedOperationException("Direct adding listener without name is not permitted. Use bind instead.");
|
|
|
- }
|
|
|
- bind(GLOBAL, (Class)event, (EventListener)listener);
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- @SuppressWarnings("unchecked")
|
|
|
- @Override
|
|
|
- public final <T extends Event> void removeEventListener(Class<T> event, EventListener<? super T> listener) {
|
|
|
- if( !NamedEvent.class.isAssignableFrom(event) ) {
|
|
|
- throw new UnsupportedOperationException("Direct removing listener without name is not permitted. Use unbind instead.");
|
|
|
- }
|
|
|
- unbind(GLOBAL, (EventListener)listener);
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public synchronized Collection<EventBinding<?>> getEventListeners() {
|
|
|
- Collection<EventListener<? super Event>> source = listeners.get(GLOBAL);
|
|
|
- List<EventBinding<?>> result = new ArrayList<EventBinding<?>>(source.size());
|
|
|
- for(EventListener<? super Event> listener : source) {
|
|
|
- result.add(EventBinding.make(NamedEvent.class, listener));
|
|
|
- }
|
|
|
- return result;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * {@inheritDoc}
|
|
|
- * <p>Uwaga! Jeśli klasa posiada zadeklarowane metody adnotowane za
|
|
|
- * pomocą {@link net.ranides.assira.annotations.Meta.EventHandler}, to
|
|
|
- * zdarzenia nadal są do nich delegowane. Nie istnieje możliwość usunięcia
|
|
|
- * metod zadeklarowanych w czasie kompilacji.</p>
|
|
|
- */
|
|
|
- @Override
|
|
|
- public final synchronized void removeAllEventListeners() {
|
|
|
- for(Collection<EventListener<? super Event>> list : listeners.values()) {
|
|
|
- Iterator<EventListener<? super Event>> iterator = list.iterator();
|
|
|
- while(iterator.hasNext()) {
|
|
|
- EventListener<? super Event> value = iterator.next();
|
|
|
- if( !(value instanceof ReflectAdapter) ) {
|
|
|
- iterator.remove();
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- Iterator<Collection<EventListener<? super Event>>> iterator = listeners.values().iterator();
|
|
|
- while(iterator.hasNext()) {
|
|
|
- Collection<EventListener<? super Event>> value = iterator.next();
|
|
|
- if(value.isEmpty()) {
|
|
|
- iterator.remove();
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
-/* ************************************************************************** */
|
|
|
-
|
|
|
- /**
|
|
|
- * Klasa ma napisaną metodę {@code equals} w taki sposób, że adapter jest
|
|
|
- * równy samemu sobie, innemu adapterowi wrappującemu obserwatora oraz
|
|
|
- * <b>opakowanemu obserwatorowi</b>.
|
|
|
- * <p>
|
|
|
- * Dzięki temu, jeśli chcemy sprawdzić, czy
|
|
|
- * dany obserwator został dodany do jakiejś kolekcji (albo usunąć go), to
|
|
|
- * nie musimy go wrappować - możemy go przekazać bezpośrednio.
|
|
|
- * </p>
|
|
|
- */
|
|
|
- static final class TypedListener implements EventListener<Event> {
|
|
|
- private final Class<?> type;
|
|
|
- private final EventListener<Event> handler;
|
|
|
-
|
|
|
- @SuppressWarnings("unchecked")
|
|
|
- public <E> TypedListener(Class<E> type, EventListener<? super E> handler) {
|
|
|
- this.type = type;
|
|
|
- this.handler = (EventListener<Event>)handler;
|
|
|
- }
|
|
|
-
|
|
|
- @SuppressWarnings("unchecked")
|
|
|
- public <E> TypedListener(EventListener<? super NamedEvent> handler) {
|
|
|
- this.type = NamedEvent.class;
|
|
|
- this.handler = (EventListener<Event>)handler;
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public void handleEvent(Event event) {
|
|
|
- if(type.isInstance(event)) {
|
|
|
- this.handler.handleEvent(event);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public boolean equals(Object object) {
|
|
|
- if( this == object || handler.equals(object)) {
|
|
|
- return true;
|
|
|
- }
|
|
|
- if( object instanceof TypedListener) {
|
|
|
- return ((TypedListener)object).handler == handler;
|
|
|
- }
|
|
|
- return false;
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public int hashCode() {
|
|
|
- return this.handler.hashCode();
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
-}
|