2020-09-02 16:15:31 +02:00
|
|
|
package dev.kske.eventbus;
|
|
|
|
|
2020-09-20 15:27:51 +02:00
|
|
|
import java.lang.System.Logger;
|
|
|
|
import java.lang.System.Logger.Level;
|
2020-09-02 16:15:31 +02:00
|
|
|
import java.util.*;
|
|
|
|
import java.util.concurrent.ConcurrentHashMap;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Event listeners can be registered at an event bus to be notified when an event is dispatched.
|
|
|
|
* <p>
|
2020-09-06 15:32:54 +02:00
|
|
|
* A singleton instance of this class can be lazily created and acquired using the
|
|
|
|
* {@link EventBus#getInstance()} method.
|
|
|
|
* <p>
|
2020-09-02 16:15:31 +02:00
|
|
|
* This is a thread-safe implementation.
|
|
|
|
*
|
|
|
|
* @author Kai S. K. Engelbart
|
|
|
|
* @since 0.0.1
|
|
|
|
* @see Event
|
|
|
|
*/
|
|
|
|
public final class EventBus {
|
|
|
|
|
2020-10-02 17:50:11 +02:00
|
|
|
/**
|
|
|
|
* Holds the state of the dispatching process on one thread.
|
|
|
|
*
|
|
|
|
* @since 0.1.0
|
|
|
|
*/
|
|
|
|
private static final class DispatchState {
|
|
|
|
|
|
|
|
boolean isDispatching, isCancelled;
|
|
|
|
}
|
|
|
|
|
2020-09-24 17:41:08 +02:00
|
|
|
private static volatile EventBus singletonInstance;
|
2020-09-06 15:32:54 +02:00
|
|
|
|
2020-09-20 15:27:51 +02:00
|
|
|
private static final Logger logger = System.getLogger(EventBus.class.getName());
|
|
|
|
|
2020-09-06 15:32:54 +02:00
|
|
|
/**
|
|
|
|
* Produces a singleton instance of the event bus. It is lazily initialized on the first call.
|
|
|
|
*
|
|
|
|
* @return a singleton instance of the event bus.
|
|
|
|
* @since 0.0.2
|
|
|
|
*/
|
|
|
|
public static EventBus getInstance() {
|
2020-09-24 17:41:08 +02:00
|
|
|
EventBus instance = singletonInstance;
|
|
|
|
if (instance == null)
|
|
|
|
synchronized (EventBus.class) {
|
|
|
|
if ((instance = singletonInstance) == null) {
|
|
|
|
logger.log(Level.DEBUG, "Initializing singleton event bus instance");
|
|
|
|
instance = singletonInstance = new EventBus();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return instance;
|
2020-09-06 15:32:54 +02:00
|
|
|
}
|
|
|
|
|
2020-09-20 12:20:29 +02:00
|
|
|
private final Map<Class<? extends IEvent>, TreeSet<EventHandler>> bindings
|
2020-09-02 16:15:31 +02:00
|
|
|
= new ConcurrentHashMap<>();
|
|
|
|
private final Set<EventListener> registeredListeners = ConcurrentHashMap.newKeySet();
|
2020-10-02 17:50:11 +02:00
|
|
|
private final ThreadLocal<DispatchState> dispatchState
|
|
|
|
= ThreadLocal.withInitial(DispatchState::new);
|
2020-09-02 16:15:31 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Dispatches an event to all event handlers registered for it in descending order of their
|
|
|
|
* priority.
|
|
|
|
*
|
|
|
|
* @param event the event to dispatch
|
|
|
|
* @since 0.0.1
|
|
|
|
*/
|
|
|
|
public void dispatch(IEvent event) {
|
2020-09-08 19:47:21 +02:00
|
|
|
Objects.requireNonNull(event);
|
2020-09-20 15:27:51 +02:00
|
|
|
logger.log(Level.INFO, "Dispatching event {0}", event);
|
2020-10-02 17:50:11 +02:00
|
|
|
|
|
|
|
// Set dispatch state
|
|
|
|
var state = dispatchState.get();
|
|
|
|
state.isDispatching = true;
|
|
|
|
|
|
|
|
for (var handler : getHandlersFor(event.getClass()))
|
|
|
|
if (state.isCancelled) {
|
|
|
|
logger.log(Level.INFO, "Cancelled dispatching event {0}", event);
|
|
|
|
state.isCancelled = false;
|
|
|
|
break;
|
|
|
|
} else {
|
|
|
|
handler.execute(event);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Reset dispatch state
|
|
|
|
state.isDispatching = false;
|
|
|
|
|
|
|
|
logger.log(Level.DEBUG, "Finished dispatching event {0}", event);
|
2020-09-02 16:15:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Searches for the event handlers bound to an event class.
|
|
|
|
*
|
|
|
|
* @param eventClass the event class to use for the search
|
|
|
|
* @return all event handlers registered for the event class
|
|
|
|
* @since 0.0.1
|
|
|
|
*/
|
|
|
|
private List<EventHandler> getHandlersFor(Class<? extends IEvent> eventClass) {
|
2020-09-20 12:20:29 +02:00
|
|
|
|
|
|
|
// Get handlers defined for the event class
|
|
|
|
Set<EventHandler> handlers
|
|
|
|
= bindings.containsKey(eventClass) ? bindings.get(eventClass)
|
|
|
|
: new TreeSet<>();
|
|
|
|
|
|
|
|
// Get subtype handlers
|
|
|
|
for (var binding : bindings.entrySet())
|
|
|
|
if (binding.getKey().isAssignableFrom(eventClass))
|
|
|
|
for (var handler : binding.getValue())
|
|
|
|
if (handler.includeSubtypes())
|
|
|
|
handlers.add(handler);
|
|
|
|
|
|
|
|
return new ArrayList<>(handlers);
|
2020-09-02 16:15:31 +02:00
|
|
|
}
|
|
|
|
|
2020-10-02 17:50:11 +02:00
|
|
|
/**
|
|
|
|
* Cancels an event that is currently dispatched from inside an event handler.
|
|
|
|
*
|
|
|
|
* @throws EventBusException if the calling thread is not an active dispatching thread
|
|
|
|
* @since 0.1.0
|
|
|
|
*/
|
|
|
|
public void cancel() {
|
|
|
|
var state = dispatchState.get();
|
|
|
|
if (state.isDispatching && !state.isCancelled)
|
|
|
|
state.isCancelled = true;
|
|
|
|
else
|
|
|
|
throw new EventBusException("Calling thread not an active dispatching thread!");
|
|
|
|
}
|
|
|
|
|
2020-09-02 16:15:31 +02:00
|
|
|
/**
|
|
|
|
* Registers an event listener at this event bus.
|
|
|
|
*
|
|
|
|
* @param listener the listener to register
|
|
|
|
* @throws EventBusException if the listener is already registered or a declared event handler
|
2020-09-08 19:47:21 +02:00
|
|
|
* does not comply with the specification
|
2020-09-02 16:15:31 +02:00
|
|
|
* @since 0.0.1
|
|
|
|
* @see Event
|
|
|
|
*/
|
|
|
|
public void registerListener(EventListener listener) throws EventBusException {
|
2020-09-08 19:47:21 +02:00
|
|
|
Objects.requireNonNull(listener);
|
2020-09-02 16:15:31 +02:00
|
|
|
if (registeredListeners.contains(listener))
|
|
|
|
throw new EventBusException(listener + " already registered!");
|
2020-09-20 15:27:51 +02:00
|
|
|
logger.log(Level.INFO, "Registering event listener {0}", listener.getClass().getName());
|
|
|
|
boolean handlerBound = false;
|
2020-09-02 16:15:31 +02:00
|
|
|
|
|
|
|
registeredListeners.add(listener);
|
|
|
|
for (var method : listener.getClass().getDeclaredMethods()) {
|
|
|
|
Event annotation = method.getAnnotation(Event.class);
|
|
|
|
|
|
|
|
// Skip methods without annotations
|
|
|
|
if (annotation == null)
|
|
|
|
continue;
|
|
|
|
|
2020-09-08 19:47:21 +02:00
|
|
|
// Initialize and bind the handler
|
|
|
|
var handler = new EventHandler(listener, method, annotation);
|
|
|
|
if (!bindings.containsKey(handler.getEventType()))
|
|
|
|
bindings.put(handler.getEventType(), new TreeSet<>());
|
2020-09-20 15:27:51 +02:00
|
|
|
logger.log(Level.DEBUG, "Binding event handler {0}", handler);
|
2020-09-08 19:47:21 +02:00
|
|
|
bindings.get(handler.getEventType())
|
|
|
|
.add(handler);
|
2020-09-20 15:27:51 +02:00
|
|
|
handlerBound = true;
|
2020-09-02 16:15:31 +02:00
|
|
|
}
|
2020-09-20 15:27:51 +02:00
|
|
|
|
|
|
|
if(!handlerBound)
|
|
|
|
logger.log(
|
|
|
|
Level.WARNING,
|
|
|
|
"No event handlers bound for event listener {0}",
|
|
|
|
listener.getClass().getName()
|
|
|
|
);
|
2020-09-02 16:15:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Removes a specific listener from this event bus.
|
|
|
|
*
|
|
|
|
* @param listener the listener to remove
|
|
|
|
* @since 0.0.1
|
|
|
|
*/
|
|
|
|
public void removeListener(EventListener listener) {
|
2020-09-08 19:47:21 +02:00
|
|
|
Objects.requireNonNull(listener);
|
2020-09-20 15:27:51 +02:00
|
|
|
logger.log(Level.INFO, "Removing event listener {0}", listener.getClass().getName());
|
|
|
|
|
2020-09-02 16:15:31 +02:00
|
|
|
for (var binding : bindings.values()) {
|
|
|
|
var it = binding.iterator();
|
2020-09-20 15:27:51 +02:00
|
|
|
while (it.hasNext()) {
|
|
|
|
var handler = it.next();
|
|
|
|
if (handler.getListener() == listener) {
|
|
|
|
logger.log(Level.DEBUG, "Unbinding event handler {0}", handler);
|
2020-09-02 16:15:31 +02:00
|
|
|
it.remove();
|
2020-09-20 15:27:51 +02:00
|
|
|
}
|
|
|
|
}
|
2020-09-02 16:15:31 +02:00
|
|
|
}
|
|
|
|
registeredListeners.remove(listener);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Removes all event listeners from this event bus.
|
|
|
|
*
|
|
|
|
* @since 0.0.1
|
|
|
|
*/
|
|
|
|
public void clearListeners() {
|
2020-09-20 15:27:51 +02:00
|
|
|
logger.log(Level.INFO, "Clearing event listeners");
|
2020-09-02 16:15:31 +02:00
|
|
|
bindings.clear();
|
|
|
|
registeredListeners.clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Provides an unmodifiable view of the event listeners registered at this event bus.
|
|
|
|
*
|
|
|
|
* @return all registered event listeners
|
|
|
|
* @since 0.0.1
|
|
|
|
*/
|
|
|
|
public Set<EventListener> getRegisteredListeners() {
|
|
|
|
return Collections.unmodifiableSet(registeredListeners);
|
|
|
|
}
|
|
|
|
}
|