Shorten module names
This commit is contained in:
37
core/src/main/java/dev/kske/eventbus/core/DeadEvent.java
Normal file
37
core/src/main/java/dev/kske/eventbus/core/DeadEvent.java
Normal file
@ -0,0 +1,37 @@
|
||||
package dev.kske.eventbus.core;
|
||||
|
||||
/**
|
||||
* Wraps an event that was dispatched but for which no handler has been bound.
|
||||
* <p>
|
||||
* Handling dead events is useful as it can identify a poorly configured event distribution.
|
||||
*
|
||||
* @author Kai S. K. Engelbart
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public final class DeadEvent {
|
||||
|
||||
private final EventBus eventBus;
|
||||
private final Object event;
|
||||
|
||||
DeadEvent(EventBus eventBus, Object event) {
|
||||
this.eventBus = eventBus;
|
||||
this.event = event;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("DeadEvent[eventBus=%s, event=%s]", eventBus, event);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the event bus that dispatched this event
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public EventBus getEventBus() { return eventBus; }
|
||||
|
||||
/**
|
||||
* @return the event that could not be delivered
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public Object getEvent() { return event; }
|
||||
}
|
42
core/src/main/java/dev/kske/eventbus/core/Event.java
Normal file
42
core/src/main/java/dev/kske/eventbus/core/Event.java
Normal file
@ -0,0 +1,42 @@
|
||||
package dev.kske.eventbus.core;
|
||||
|
||||
import static java.lang.annotation.ElementType.METHOD;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* Indicates that a method is an event handler.
|
||||
* <p>
|
||||
* To be successfully used as such, the method has to specify the event type by either declaring one
|
||||
* parameter of that type or setting the annotation value to the corresponding class.
|
||||
*
|
||||
* @author Kai S. K. Engelbart
|
||||
* @since 0.0.1
|
||||
* @see Polymorphic
|
||||
* @see Priority
|
||||
*/
|
||||
@Documented
|
||||
@Retention(RUNTIME)
|
||||
@Target(METHOD)
|
||||
public @interface Event {
|
||||
|
||||
/**
|
||||
* Defines the event type the handler listens for. If this value is set, the handler is not
|
||||
* allowed to declare parameters.
|
||||
* <p>
|
||||
* This is useful when the event handler does not utilize the event instance.
|
||||
*
|
||||
* @return the event type accepted by the handler
|
||||
* @since 1.0.0
|
||||
*/
|
||||
Class<?> value() default USE_PARAMETER.class;
|
||||
|
||||
/**
|
||||
* Signifies that the event type the handler listens to is determined by the type of its only
|
||||
* parameter.
|
||||
*
|
||||
* @since 0.0.3
|
||||
*/
|
||||
static final class USE_PARAMETER {}
|
||||
}
|
414
core/src/main/java/dev/kske/eventbus/core/EventBus.java
Normal file
414
core/src/main/java/dev/kske/eventbus/core/EventBus.java
Normal file
@ -0,0 +1,414 @@
|
||||
package dev.kske.eventbus.core;
|
||||
|
||||
import java.lang.System.Logger;
|
||||
import java.lang.System.Logger.Level;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import dev.kske.eventbus.core.handler.*;
|
||||
|
||||
/**
|
||||
* Event listeners can be registered at an event bus to be notified when an event is dispatched.
|
||||
* <p>
|
||||
* A singleton instance of this class can be lazily created and acquired using the
|
||||
* {@link EventBus#getInstance()} method.
|
||||
* <p>
|
||||
* This is a thread-safe implementation.
|
||||
*
|
||||
* @author Kai S. K. Engelbart
|
||||
* @since 0.0.1
|
||||
* @see Event
|
||||
*/
|
||||
public final class EventBus {
|
||||
|
||||
/**
|
||||
* Holds the state of the dispatching process on one thread.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*/
|
||||
private static final class DispatchState {
|
||||
|
||||
/**
|
||||
* Indicates that the last event handler invoked has called {@link EventBus#cancel}. In that
|
||||
* case, the event is not dispatched further.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*/
|
||||
boolean isCancelled;
|
||||
|
||||
/**
|
||||
* Is incremented when {@link EventBus#dispatch(Object)} is invoked and decremented when it
|
||||
* finishes. This allows keeping track of nested dispatches.
|
||||
*
|
||||
* @since 1.2.0
|
||||
*/
|
||||
int nestingCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* The priority assigned to every event handler without an explicitly defined priority.
|
||||
*
|
||||
* @since 1.1.0
|
||||
* @see Priority
|
||||
*/
|
||||
public static final int DEFAULT_PRIORITY = 100;
|
||||
|
||||
private static final EventBus singletonInstance = new EventBus();
|
||||
|
||||
private static final Logger logger = System.getLogger(EventBus.class.getName());
|
||||
|
||||
/**
|
||||
* Compares event handlers based on priority, but uses hash codes for equal priorities.
|
||||
*
|
||||
* @implNote As the priority comparator by itself is not consistent with equals (two handlers
|
||||
* with the same priority are not necessarily equal, but would have a comparison
|
||||
* result of 0), the hash code is used for the fallback comparison. This way,
|
||||
* consistency with equals is restored.
|
||||
* @since 1.2.0
|
||||
*/
|
||||
private static final Comparator<EventHandler> byPriority =
|
||||
Comparator.comparingInt(EventHandler::getPriority).reversed()
|
||||
.thenComparingInt(EventHandler::hashCode);
|
||||
|
||||
/**
|
||||
* Returns the default event bus, which is a statically initialized singleton instance.
|
||||
*
|
||||
* @return the default event bus
|
||||
* @since 0.0.2
|
||||
*/
|
||||
public static EventBus getInstance() {
|
||||
return singletonInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Event handler bindings (target class to handlers registered for that class), does not contain
|
||||
* other (polymorphic) handlers.
|
||||
*
|
||||
* @since 0.0.1
|
||||
*/
|
||||
private final Map<Class<?>, TreeSet<EventHandler>> bindings = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* Stores all registered event listeners (which declare event handlers) and prevents them from
|
||||
* being garbage collected.
|
||||
*
|
||||
* @since 0.0.1
|
||||
*/
|
||||
private final Set<Object> registeredListeners = ConcurrentHashMap.newKeySet();
|
||||
|
||||
/**
|
||||
* The current event dispatching state, local to each thread.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*/
|
||||
private final ThreadLocal<DispatchState> dispatchState =
|
||||
ThreadLocal.withInitial(DispatchState::new);
|
||||
|
||||
/**
|
||||
* Dispatches an event to all event handlers registered for it in descending order of their
|
||||
* priority.
|
||||
*
|
||||
* @param event the event to dispatch
|
||||
* @throws EventBusException if an event handler isn't accessible or has an invalid signature
|
||||
* @throws NullPointerException if the specified event is {@code null}
|
||||
* @since 0.0.1
|
||||
*/
|
||||
public void dispatch(Object event) throws EventBusException {
|
||||
Objects.requireNonNull(event);
|
||||
logger.log(Level.INFO, "Dispatching event {0}", event);
|
||||
|
||||
// Look up dispatch state
|
||||
var state = dispatchState.get();
|
||||
|
||||
// Increment nesting count (becomes > 1 during nested dispatches)
|
||||
++state.nestingCount;
|
||||
|
||||
Iterator<EventHandler> handlers = getHandlersFor(event.getClass()).iterator();
|
||||
if (handlers.hasNext()) {
|
||||
while (handlers.hasNext())
|
||||
if (state.isCancelled) {
|
||||
logger.log(Level.INFO, "Cancelled dispatching event {0}", event);
|
||||
state.isCancelled = false;
|
||||
break;
|
||||
} else {
|
||||
try {
|
||||
handlers.next().execute(event);
|
||||
} catch (InvocationTargetException e) {
|
||||
if (e.getCause() instanceof Error)
|
||||
|
||||
// Transparently pass error to the caller
|
||||
throw (Error) e.getCause();
|
||||
else if (event instanceof DeadEvent || event instanceof ExceptionEvent)
|
||||
|
||||
// Warn about system event not being handled
|
||||
logger.log(Level.WARNING, event + " not handled due to exception", e);
|
||||
else
|
||||
|
||||
// Dispatch exception event
|
||||
dispatch(new ExceptionEvent(this, event, e.getCause()));
|
||||
}
|
||||
}
|
||||
} else if (event instanceof DeadEvent || event instanceof ExceptionEvent) {
|
||||
|
||||
// Warn about the dead event not being handled
|
||||
logger.log(Level.WARNING, "{0} not handled", event);
|
||||
} else {
|
||||
|
||||
// Dispatch dead event
|
||||
dispatch(new DeadEvent(this, event));
|
||||
}
|
||||
|
||||
// Decrement nesting count (becomes 0 when all dispatches on the thread are finished)
|
||||
--state.nestingCount;
|
||||
|
||||
logger.log(Level.DEBUG, "Finished dispatching event {0}", event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for the event handlers bound to an event class. This includes polymorphic handlers
|
||||
* that are bound to a supertype of the event class.
|
||||
*
|
||||
* @param eventType the event type to use for the search
|
||||
* @return a navigable set containing the applicable handlers in descending order of priority
|
||||
* @since 1.2.0
|
||||
*/
|
||||
private NavigableSet<EventHandler> getHandlersFor(Class<?> eventType) {
|
||||
|
||||
// Get handlers defined for the event class
|
||||
TreeSet<EventHandler> handlers =
|
||||
bindings.getOrDefault(eventType, new TreeSet<>(byPriority));
|
||||
|
||||
// Get polymorphic handlers
|
||||
for (var binding : bindings.entrySet())
|
||||
if (binding.getKey().isAssignableFrom(eventType))
|
||||
for (var handler : binding.getValue())
|
||||
if (handler.isPolymorphic())
|
||||
handlers.add(handler);
|
||||
|
||||
return handlers;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.nestingCount > 0 && !state.isCancelled)
|
||||
state.isCancelled = true;
|
||||
else
|
||||
throw new EventBusException("Calling thread not an active dispatching thread!");
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 does not comply with the specification
|
||||
* @throws NullPointerException if the specified listener is {@code null}
|
||||
* @since 0.0.1
|
||||
* @see Event
|
||||
*/
|
||||
public void registerListener(Object listener) throws EventBusException {
|
||||
Objects.requireNonNull(listener);
|
||||
if (registeredListeners.contains(listener))
|
||||
throw new EventBusException(listener + " already registered!");
|
||||
logger.log(Level.INFO, "Registering event listener {0}", listener.getClass().getName());
|
||||
boolean handlerBound = false;
|
||||
|
||||
// Predefined handler polymorphism
|
||||
boolean polymorphic = false;
|
||||
if (listener.getClass().isAnnotationPresent(Polymorphic.class))
|
||||
polymorphic = listener.getClass().getAnnotation(Polymorphic.class).value();
|
||||
|
||||
// Predefined handler priority
|
||||
int priority = DEFAULT_PRIORITY;
|
||||
if (listener.getClass().isAnnotationPresent(Priority.class))
|
||||
priority = listener.getClass().getAnnotation(Priority.class).value();
|
||||
|
||||
registeredListeners.add(listener);
|
||||
for (var method : listener.getClass().getDeclaredMethods()) {
|
||||
Event annotation = method.getAnnotation(Event.class);
|
||||
|
||||
// Skip methods without annotations
|
||||
if (annotation == null)
|
||||
continue;
|
||||
|
||||
// Initialize and bind the handler
|
||||
bindHandler(
|
||||
new ReflectiveEventHandler(listener, method, annotation, polymorphic, priority));
|
||||
handlerBound = true;
|
||||
}
|
||||
|
||||
if (!handlerBound)
|
||||
logger.log(
|
||||
Level.WARNING,
|
||||
"No event handlers bound for event listener {0}",
|
||||
listener.getClass().getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a callback listener, which is a consumer that is invoked when an event occurs. The
|
||||
* listener is not polymorphic and has the {@link #DEFAULT_PRIORITY}.
|
||||
*
|
||||
* @param <E> the event type the listener listens for
|
||||
* @param eventType the event type the listener listens for
|
||||
* @param eventListener the callback that is invoked when an event occurs
|
||||
* @since 1.2.0
|
||||
* @see #registerListener(Class, Consumer, boolean, int)
|
||||
*/
|
||||
public <E> void registerListener(Class<E> eventType, Consumer<E> eventListener) {
|
||||
registerListener(eventType, eventListener, false, DEFAULT_PRIORITY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a callback listener, which is a consumer that is invoked when an event occurs. The
|
||||
* listener has the {@link #DEFAULT_PRIORITY}.
|
||||
*
|
||||
* @param <E> the event type the listener listens for
|
||||
* @param eventType the event type the listener listens for
|
||||
* @param eventListener the callback that is invoked when an event occurs
|
||||
* @param polymorphic whether the listener is also invoked for subtypes of the event type
|
||||
* @since 1.2.0
|
||||
* @see #registerListener(Class, Consumer, boolean, int)
|
||||
*/
|
||||
public <E> void registerListener(Class<E> eventType, Consumer<E> eventListener,
|
||||
boolean polymorphic) {
|
||||
registerListener(eventType, eventListener, polymorphic, DEFAULT_PRIORITY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a callback listener, which is a consumer that is invoked when an event occurs. The
|
||||
* listener is not polymorphic.
|
||||
*
|
||||
* @param <E> the event type the listener listens for
|
||||
* @param eventType the event type the listener listens for
|
||||
* @param eventListener the callback that is invoked when an event occurs
|
||||
* @param priority the priority to assign to the listener
|
||||
* @since 1.2.0
|
||||
* @see #registerListener(Class, Consumer, boolean, int)
|
||||
*/
|
||||
public <E> void registerListener(Class<E> eventType, Consumer<E> eventListener, int priority) {
|
||||
registerListener(eventType, eventListener, false, priority);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a callback listener, which is a consumer that is invoked when an event occurs.
|
||||
*
|
||||
* @param <E> the event type the listener listens for
|
||||
* @param eventType the event type the listener listens for
|
||||
* @param eventListener the callback that is invoked when an event occurs
|
||||
* @param polymorphic whether the listener is also invoked for subtypes of the event type
|
||||
* @param priority the priority to assign to the listener
|
||||
* @since 1.2.0
|
||||
*/
|
||||
public <E> void registerListener(Class<E> eventType, Consumer<E> eventListener,
|
||||
boolean polymorphic,
|
||||
int priority) {
|
||||
Objects.requireNonNull(eventListener);
|
||||
if (registeredListeners.contains(eventListener))
|
||||
throw new EventBusException(eventListener + " already registered!");
|
||||
logger.log(Level.INFO, "Registering callback event listener {0}",
|
||||
eventListener.getClass().getName());
|
||||
|
||||
registeredListeners.add(eventListener);
|
||||
bindHandler(new CallbackEventHandler(eventType, eventListener, polymorphic, priority));
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a new handler into the {@link #bindings} map.
|
||||
*
|
||||
* @param handler the handler to bind
|
||||
* @since 1.2.0
|
||||
*/
|
||||
private void bindHandler(EventHandler handler) {
|
||||
bindings.putIfAbsent(handler.getEventType(), new TreeSet<>(byPriority));
|
||||
logger.log(Level.DEBUG, "Binding event handler {0}", handler);
|
||||
bindings.get(handler.getEventType()).add(handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a specific listener from this event bus.
|
||||
*
|
||||
* @param listener the listener to remove
|
||||
* @since 0.0.1
|
||||
*/
|
||||
public void removeListener(Object listener) {
|
||||
Objects.requireNonNull(listener);
|
||||
logger.log(Level.INFO, "Removing event listener {0}", listener.getClass().getName());
|
||||
|
||||
// Remove bindings from binding map
|
||||
for (var binding : bindings.values()) {
|
||||
var it = binding.iterator();
|
||||
while (it.hasNext()) {
|
||||
var handler = it.next();
|
||||
if (handler.getListener() == listener) {
|
||||
logger.log(Level.DEBUG, "Unbinding event handler {0}", handler);
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the listener itself
|
||||
registeredListeners.remove(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all event listeners from this event bus.
|
||||
*
|
||||
* @since 0.0.1
|
||||
*/
|
||||
public void clearListeners() {
|
||||
logger.log(Level.INFO, "Clearing event listeners");
|
||||
bindings.clear();
|
||||
registeredListeners.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a string describing the event handlers that would be executed for a specific event
|
||||
* type, in order and without actually executing them.
|
||||
*
|
||||
* @apiNote Using this method is only recommended for debugging purposes, as the output depends
|
||||
* on implementation internals which may be subject to change.
|
||||
* @implNote Nested dispatches are not accounted for, as this would require actually executing
|
||||
* the handlers.
|
||||
* @param eventType the event type to generate the execution order for
|
||||
* @return a human-readable event handler list suitable for debugging purposes
|
||||
* @since 1.2.0
|
||||
*/
|
||||
public String debugExecutionOrder(Class<?> eventType) {
|
||||
var handlers = getHandlersFor(eventType);
|
||||
var sj = new StringJoiner("\n");
|
||||
|
||||
// Output header line
|
||||
sj.add(String.format("Event handler execution order for %s (%d handler(s)):", eventType,
|
||||
handlers.size()));
|
||||
sj.add(
|
||||
"==========================================================================================");
|
||||
|
||||
// Individual handlers
|
||||
for (var handler : handlers)
|
||||
sj.add(handler.toString());
|
||||
|
||||
// Bottom line
|
||||
sj.add(
|
||||
"==========================================================================================");
|
||||
|
||||
return sj.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides an unmodifiable view of the event listeners registered at this event bus.
|
||||
*
|
||||
* @return all registered event listeners
|
||||
* @since 0.0.1
|
||||
*/
|
||||
public Set<Object> getRegisteredListeners() {
|
||||
return Collections.unmodifiableSet(registeredListeners);
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
package dev.kske.eventbus.core;
|
||||
|
||||
/**
|
||||
* This unchecked exception is specific to the event bus and can be thrown under the following
|
||||
* circumstances:
|
||||
* <ul>
|
||||
* <li>An event handler throws an exception (which is stored as the cause)</li>
|
||||
* <li>An event listener with an invalid event handler is registered</li>
|
||||
* <li>{@link EventBus#cancel()} is invoked from outside an active dispatch thread</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author Kai S. K. Engelbart
|
||||
* @since 0.0.1
|
||||
*/
|
||||
public final class EventBusException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Creates a new event bus exception.
|
||||
*
|
||||
* @param message the message to display
|
||||
* @param cause the cause of this exception
|
||||
*/
|
||||
public EventBusException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new event bus exception.
|
||||
*
|
||||
* @param message the message to display
|
||||
*/
|
||||
public EventBusException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
package dev.kske.eventbus.core;
|
||||
|
||||
/**
|
||||
* Wraps an event that was dispatched but caused an exception in one of its handlers.
|
||||
* <p>
|
||||
* Handling exception events is useful as it allows the creation of a centralized exception handling
|
||||
* mechanism for unexpected exceptions.
|
||||
*
|
||||
* @author Kai S. K. Engelbart
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public final class ExceptionEvent {
|
||||
|
||||
private final EventBus eventBus;
|
||||
private final Object event;
|
||||
private final Throwable cause;
|
||||
|
||||
ExceptionEvent(EventBus eventBus, Object event, Throwable cause) {
|
||||
this.eventBus = eventBus;
|
||||
this.event = event;
|
||||
this.cause = cause;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("ExceptionEvent[eventBus=%s, event=%s, cause=%s]", eventBus, event,
|
||||
cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the event bus that dispatched this event
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public EventBus getEventBus() { return eventBus; }
|
||||
|
||||
/**
|
||||
* @return the event that could not be handled because of an exception
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public Object getEvent() { return event; }
|
||||
|
||||
/**
|
||||
* @return the exception that was thrown while handling the event
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public Throwable getCause() { return cause; }
|
||||
}
|
30
core/src/main/java/dev/kske/eventbus/core/Polymorphic.java
Normal file
30
core/src/main/java/dev/kske/eventbus/core/Polymorphic.java
Normal file
@ -0,0 +1,30 @@
|
||||
package dev.kske.eventbus.core;
|
||||
|
||||
import static java.lang.annotation.ElementType.*;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* Allows an event handler to receive events that are subtypes of the declared event type.
|
||||
* <p>
|
||||
* When used on a type, the value applies to all event handlers declared within that type that don't
|
||||
* define a value on their own.
|
||||
* <p>
|
||||
* This is useful when defining an event handler for an interface or an abstract class.
|
||||
*
|
||||
* @author Kai S. K. Engelbart
|
||||
* @since 1.0.0
|
||||
* @see Event
|
||||
*/
|
||||
@Documented
|
||||
@Retention(RUNTIME)
|
||||
@Target({ METHOD, TYPE })
|
||||
public @interface Polymorphic {
|
||||
|
||||
/**
|
||||
* @return whether the event handler is polymorphic
|
||||
* @since 1.1.0
|
||||
*/
|
||||
boolean value() default true;
|
||||
}
|
33
core/src/main/java/dev/kske/eventbus/core/Priority.java
Normal file
33
core/src/main/java/dev/kske/eventbus/core/Priority.java
Normal file
@ -0,0 +1,33 @@
|
||||
package dev.kske.eventbus.core;
|
||||
|
||||
import static java.lang.annotation.ElementType.*;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* Defines the priority of an event handler. Handlers are executed in descending order of their
|
||||
* priority.
|
||||
* <p>
|
||||
* When used on a type, the value applies to all event handlers declared within that type that don't
|
||||
* define a value on their own.
|
||||
* <p>
|
||||
* Handlers without this annotation have the default priority of 100.
|
||||
* <p>
|
||||
* The execution order of handlers with the same priority is undefined.
|
||||
*
|
||||
* @author Kai S. K. Engelbart
|
||||
* @since 1.0.0
|
||||
* @see Event
|
||||
*/
|
||||
@Documented
|
||||
@Retention(RUNTIME)
|
||||
@Target({ METHOD, TYPE })
|
||||
public @interface Priority {
|
||||
|
||||
/**
|
||||
* @return the priority of the event handler
|
||||
* @since 1.0.0
|
||||
*/
|
||||
int value();
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
package dev.kske.eventbus.core.handler;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* An event handler wrapping a callback method.
|
||||
*
|
||||
* @author Kai S. K. Engelbart
|
||||
* @since 1.2.0
|
||||
*/
|
||||
public final class CallbackEventHandler implements EventHandler {
|
||||
|
||||
private final Class<?> eventType;
|
||||
private final Consumer<Object> callback;
|
||||
private final boolean polymorphic;
|
||||
private final int priority;
|
||||
|
||||
/**
|
||||
* Constructs a callback event handler.
|
||||
*
|
||||
* @param <E> the event type of the handler
|
||||
* @param eventType the event type of the handler
|
||||
* @param callback the callback method to execute when the handler is invoked
|
||||
* @param polymorphic whether the handler is polymorphic
|
||||
* @param priority the priority of the handler
|
||||
* @since 1.2.0
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <E> CallbackEventHandler(Class<E> eventType, Consumer<E> callback, boolean polymorphic,
|
||||
int priority) {
|
||||
this.eventType = eventType;
|
||||
this.callback = (Consumer<Object>) callback;
|
||||
this.polymorphic = polymorphic;
|
||||
this.priority = priority;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(Object event) throws InvocationTargetException {
|
||||
try {
|
||||
callback.accept(event);
|
||||
} catch (RuntimeException e) {
|
||||
throw new InvocationTargetException(e, "Callback event handler failed!");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format(
|
||||
"CallbackEventHandler[eventType=%s, polymorphic=%b, priority=%d]",
|
||||
eventType, polymorphic, priority);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Consumer<?> getListener() {
|
||||
return callback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getEventType() {
|
||||
return eventType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPriority() {
|
||||
return priority;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPolymorphic() {
|
||||
return polymorphic;
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
package dev.kske.eventbus.core.handler;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
|
||||
import dev.kske.eventbus.core.*;
|
||||
|
||||
/**
|
||||
* Internal representation of an event handling method.
|
||||
*
|
||||
* @author Kai S. K. Engelbart
|
||||
* @since 1.2.0
|
||||
* @see EventBus
|
||||
*/
|
||||
public interface EventHandler {
|
||||
|
||||
/**
|
||||
* Executes the event handler.
|
||||
*
|
||||
* @param event the event used as the method parameter
|
||||
* @throws EventBusException if the event handler isn't accessible or has an invalid
|
||||
* signature
|
||||
* @throws InvocationTargetException if the handler throws an exception
|
||||
* @throws EventBusException if the handler has the wrong signature or is inaccessible
|
||||
* @since 1.2.0
|
||||
*/
|
||||
void execute(Object event) throws EventBusException, InvocationTargetException;
|
||||
|
||||
/**
|
||||
* @return the listener containing this handler
|
||||
* @since 1.2.0
|
||||
*/
|
||||
Object getListener();
|
||||
|
||||
/**
|
||||
* @return the event type this handler listens for
|
||||
* @since 1.2.0
|
||||
*/
|
||||
Class<?> getEventType();
|
||||
|
||||
/**
|
||||
* @return the priority of this handler
|
||||
* @since 1.2.0
|
||||
* @see Priority
|
||||
*/
|
||||
int getPriority();
|
||||
|
||||
/**
|
||||
* @return whether this handler also accepts subtypes of the event type
|
||||
* @since 1.2.0
|
||||
* @see Polymorphic
|
||||
*/
|
||||
boolean isPolymorphic();
|
||||
}
|
@ -0,0 +1,105 @@
|
||||
package dev.kske.eventbus.core.handler;
|
||||
|
||||
import java.lang.reflect.*;
|
||||
|
||||
import dev.kske.eventbus.core.*;
|
||||
import dev.kske.eventbus.core.Event.USE_PARAMETER;
|
||||
|
||||
/**
|
||||
* An event handler wrapping a method annotated with {@link Event} and executing it using
|
||||
* reflection.
|
||||
*
|
||||
* @author Kai S. K. Engelbart
|
||||
* @since 1.2.0
|
||||
*/
|
||||
public final class ReflectiveEventHandler implements EventHandler {
|
||||
|
||||
private final Object listener;
|
||||
private final Method method;
|
||||
private final Class<?> eventType;
|
||||
private final boolean useParameter;
|
||||
private final boolean polymorphic;
|
||||
private final int priority;
|
||||
|
||||
/**
|
||||
* Constructs a reflective event handler.
|
||||
*
|
||||
* @param listener the listener containing the handler
|
||||
* @param method the handler method
|
||||
* @param annotation the event annotation
|
||||
* @param defPolymorphism the predefined polymorphism (default or listener-level)
|
||||
* @param defPriority the predefined priority (default or listener-level)
|
||||
* @throws EventBusException if the method or the annotation do not comply with the
|
||||
* specification
|
||||
* @since 1.2.0
|
||||
*/
|
||||
public ReflectiveEventHandler(Object listener, Method method, Event annotation,
|
||||
boolean defPolymorphism, int defPriority) throws EventBusException {
|
||||
this.listener = listener;
|
||||
this.method = method;
|
||||
useParameter = annotation.value() == USE_PARAMETER.class;
|
||||
|
||||
// Check handler signature
|
||||
if (method.getParameterCount() == 0 && useParameter)
|
||||
throw new EventBusException(method + " does not define an event type!");
|
||||
|
||||
if (method.getParameterCount() == 1 && !useParameter)
|
||||
throw new EventBusException(method + " defines an ambiguous event type!");
|
||||
|
||||
if (method.getParameterCount() > 1)
|
||||
throw new EventBusException(method + " defines more than one parameter!");
|
||||
|
||||
// Determine handler properties
|
||||
eventType = useParameter ? method.getParameterTypes()[0] : annotation.value();
|
||||
polymorphic = method.isAnnotationPresent(Polymorphic.class)
|
||||
? method.getAnnotation(Polymorphic.class).value()
|
||||
: defPolymorphism;
|
||||
priority = method.isAnnotationPresent(Priority.class)
|
||||
? method.getAnnotation(Priority.class).value()
|
||||
: defPriority;
|
||||
|
||||
// Allow access if the method is non-public
|
||||
method.setAccessible(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(Object event) throws EventBusException, InvocationTargetException {
|
||||
try {
|
||||
if (useParameter)
|
||||
method.invoke(getListener(), event);
|
||||
else
|
||||
method.invoke(getListener());
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new EventBusException("Event handler rejected target / argument!", e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new EventBusException("Event handler is not accessible!", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format(
|
||||
"ReflectiveEventHandler[eventType=%s, polymorphic=%b, priority=%d, method=%s, useParameter=%b]",
|
||||
eventType, polymorphic, priority, method, useParameter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getListener() {
|
||||
return listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getEventType() {
|
||||
return eventType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPriority() {
|
||||
return priority;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPolymorphic() {
|
||||
return polymorphic;
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Contains the internal representation of event handling methods.
|
||||
*
|
||||
* @author Kai S. K. Engelbart
|
||||
* @since 1.2.0
|
||||
* @see dev.kske.eventbus.core.handler.EventHandler
|
||||
*/
|
||||
package dev.kske.eventbus.core.handler;
|
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Contains the public API and implementation of the Event Bus library.
|
||||
*
|
||||
* @author Kai S. K. Engelbart
|
||||
* @since 0.0.1
|
||||
* @see dev.kske.eventbus.core.Event
|
||||
* @see dev.kske.eventbus.core.EventBus
|
||||
*/
|
||||
package dev.kske.eventbus.core;
|
Reference in New Issue
Block a user