eventflow-tmp/eventflow/ObservableEventDispatcher.java

281 lines
9.9 KiB
Java
Executable File

package de.midlane_illaoi.eventflow;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.TreeSet;
import de.midlane_illaoi.eventflow.event.ObservableEvent;
import de.midlane_illaoi.eventflow.event.ObserverSortingEvent;
public class ObservableEventDispatcher
{
private Map<Class<? extends ObservableEvent<?>>, Collection<?>> observersMap;
private List<QueuedModification<?>> queuedModifications;
class QueuedModification<T>{
private Class<? extends ObservableEvent<T>> eventType;
private T observer;
private boolean isSubscription;
public QueuedModification(Class<? extends ObservableEvent<T>> eventType, T observer, boolean isSubscription) {
super();
this.eventType = eventType;
this.observer = observer;
this.isSubscription = isSubscription;
}
public Class<? extends ObservableEvent<T>> getEventType() {
return eventType;
}
public T getObserver() {
return observer;
}
public boolean isSubscription() {
return isSubscription;
}
}
/**
* This variable serves as a recursion counter for nested event dispatching.
* */
private int isDispatching;
public ObservableEventDispatcher() {
isDispatching = 0;
observersMap = new HashMap<>();
queuedModifications = new LinkedList<QueuedModification<?>>();
}
/**
* Currently this dispacther favors performance over functionality.
* It is currently not possible to change active listeners with a listener for the currently dispatched event.
* Anyways it is possible to change subscribed listener with a listener.
* The changes will become active after the dispatching of the currently dispatched event and dispatching of all events that caused the currently dispatched event stops.
* */
public <T> void subscribe(Class<? extends ObservableEvent<T>> eventType, T observer) {
Collection<T> computeIfAbsent;
if( isDispatching > 0 ) {
queuedModifications.add( new QueuedModification<T>(eventType, observer, true) );
}else {
computeIfAbsent = getOrCreateListenerSet(eventType);
computeIfAbsent.add(observer);
}
}
/**
* Currently this dispacther favors performance over functionality.
* It is currently not possible to change active listeners with a listener for the currently dispatched event.
* Anyways it is possible to change subscribed listener with a listener.
* The changes will become active after the dispatching of the currently dispatched event stops.
*
* @return true if the listener was currently subscribed and removed. false other wise.
* false is also returned in case the dispatcher is currently dispatching and queues the removal.
* */
public <T> boolean unsubscribe(Class<? extends ObservableEvent<T>> eventType, T observer) {
Collection<T> computeIfAbsent;
if( isDispatching > 0 ) {
queuedModifications.add( new QueuedModification<T>(eventType, observer, false) );
return false;
}else {
computeIfAbsent = getOrCreateListenerSet(eventType);
return computeIfAbsent.remove(observer);
}
}
@SuppressWarnings({ "unchecked"})
private <T> Collection<T> getOrCreateListenerSet(Class<? extends ObservableEvent<T>> eventType)
{
Collection<?> computeIfAbsent = observersMap.computeIfAbsent(eventType, key -> {
if( ObserverSortingEvent.class.isAssignableFrom(eventType) ) {
//ObserverSortingEvent eventInstance = getSortableEventTypeInstance((Class<ObserverSortingEvent>) eventType);
ObserverSortingEvent eventInstance = getSortableEventTypeInstance(castObservableEventClassToObservableEventClass(eventType));
if( eventInstance.isComparatorUsedForAllEventsOfType() ) {
Comparator<T> observerComparator = (Comparator<T>) eventInstance.getObserverComparator();
return new TreeSet<T>( observerComparator );
}
}
return new ArrayList<T>();
});
return (Collection<T>) computeIfAbsent;
}
@SuppressWarnings("unchecked")
private <T extends ObserverSortingEvent> Class<T> castObservableEventClassToObservableEventClass(Class<? extends ObservableEvent<?>> eventType){
return (Class<T>) eventType;
}
private <T extends ObserverSortingEvent> T getSortableEventTypeInstance( Class<T> eventType )
{
Constructor<T> constructor = null;
Boolean accessibility = null;
try {
// Get the (private) default constructor
constructor = eventType.getDeclaredConstructor();
accessibility = constructor.isAccessible();
// Set the accessibility to true
constructor.setAccessible(true);
// Create an instance using the private constructor
return (T) constructor.newInstance();
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException
| InvocationTargetException | NoSuchMethodException | SecurityException e) {
String errorMessage = String.format("Cannot subscribe to events of type: %s. Events that implement the %s interface must implement a (private) default constructor.", eventType.getName(), ObserverSortingEvent.class.getName());
throw new RuntimeException( errorMessage, e);
}finally {
if( accessibility != null && constructor != null) {
constructor.setAccessible(accessibility);
}
}
}
public <T> void dispatchEvent(ObservableEvent<T> event)
{
isDispatching++;
try {
List<Class<?>> eventTypes = getAllEventSuperclasses(event.getClass());
for (Class<?> eventType : eventTypes) {
if(!observersMap.containsKey(eventType)) {
continue;
}
//List<T> observers = (List<T>) observersMap.get(eventType);
/*
* Make a copy of the observers list.
*
* Observers can now unsubscribe while being notified.
*
* Alternative solution: Queuing Unsubscribe Calls:
*
*
* Pros:
* Lower cpu usage
* Lower Memory Usage: There's no need to create copies of observer lists, so memory usage remains low.
* Deferred Unsubscription: Unsubscribe calls are deferred until after event dispatch, allowing observers to continue receiving events during the current dispatch cycle.
*
* Cons:
* Complexity: Managing a queue of unsubscribe commands adds complexity to the implementation.
* Potential Delay: Unsubscribed observers may receive events during the current dispatch cycle.
*
*/
/*
*
* @SuppressWarnings("unchecked")
* List<T> observers = new ArrayList<>( (Set<T>) observersMap.get(eventType) );
*/
@SuppressWarnings("unchecked")
Collection<T> observers = (Collection<T>) observersMap.get(eventType);
/*
* currently for a ObserverSortingEvent all listeners
* get sorted for every dispatched event.
*
* This may slow down execution.
* An alternative is to keep the observers for such event types
* in a sorted list
* */
if (event instanceof ObserverSortingEvent) {
ObserverSortingEvent sortingEvent = (ObserverSortingEvent) event;
if( !sortingEvent.isComparatorUsedForAllEventsOfType() ) {
observers = new ArrayList<>( observers );
@SuppressWarnings("unchecked")
Comparator<? super T> observerComparator = (Comparator<? super T>) sortingEvent.getObserverComparator();
((ArrayList)observers).sort( observerComparator );
}
}
observers.forEach(observer -> {
event.accept(observer);
});
}
/*
Class<? extends ObservableEvent<T>> eventType = getEventType(event);
@SuppressWarnings("unchecked")
List<T> observers = (List<T>) observersMap.computeIfAbsent(eventType, key -> new ArrayList<T>());
observers.forEach(observer -> {
event.accept(observer);
});
*/
}catch (Exception e){
throw e;
}
finally {
isDispatching--;
}
/*
* code changed at 15.07.2024
* old code did not check for
* if( isDispatching == 0 ) {
* */
if( isDispatching == 0 ) {
for( QueuedModification<?> queuedModification : queuedModifications ) {
if( queuedModification.isSubscription() ) {
subscribeHelper(queuedModification);
}else {
unsubscribeHelper(queuedModification);
}
}
queuedModifications.clear();
}
}
private <T> void subscribeHelper(QueuedModification<T> queuedModification) {
subscribe(queuedModification.getEventType(), queuedModification.getObserver());
}
private <T> void unsubscribeHelper(QueuedModification<T> queuedModification) {
unsubscribe(queuedModification.getEventType(), queuedModification.getObserver());
}
private List<Class<?>> getAllEventSuperclasses(Class<?> eventType)
{
List<Class<?>> superclasses = new ArrayList<>();
while (eventType != null && ObservableEvent.class.isAssignableFrom(eventType)) {
superclasses.add(eventType);
eventType = eventType.getSuperclass();
}
return superclasses;
}
@SuppressWarnings({ "unchecked", "unused" })
private <T> Class<? extends ObservableEvent<T>> getEventType(ObservableEvent<T> event) {
@SuppressWarnings("rawtypes")
Class<? extends ObservableEvent> eventClass = event.getClass();
return (Class<? extends ObservableEvent<T>>) eventClass;
}
}