Shorten module names

This commit is contained in:
2021-12-24 10:57:24 +02:00
parent 856a2e8cbf
commit 897f1a20f3
31 changed files with 2 additions and 2 deletions

27
core/.classpath Normal file
View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
</classpath>

23
core/.project Normal file
View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>event-bus-core</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
</natures>
</projectDescription>

39
core/pom.xml Normal file
View File

@ -0,0 +1,39 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>event-bus-core</artifactId>
<name>Event Bus Core</name>
<parent>
<groupId>dev.kske</groupId>
<artifactId>event-bus</artifactId>
<version>1.2.0</version>
</parent>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<!-- Disable resource folder -->
<resources />
<!-- Run unit tests -->
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M5</version>
</plugin>
</plugins>
</build>
</project>

View 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; }
}

View 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 {}
}

View 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);
}
}

View File

@ -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);
}
}

View File

@ -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; }
}

View 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;
}

View 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();
}

View File

@ -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;
}
}

View File

@ -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();
}

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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;

View File

@ -0,0 +1,12 @@
/**
* Contains the public API and implementation of the Event Bus library.
*
* @author Kai S. K. Engelbart
* @since 0.0.3
* @see dev.kske.eventbus.core.Event
* @see dev.kske.eventbus.core.EventBus
*/
module dev.kske.eventbus.core {
exports dev.kske.eventbus.core;
}

View File

@ -0,0 +1,54 @@
package dev.kske.eventbus.core;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.*;
/**
* Tests the event cancellation mechanism of the event bus.
*
* @author Kai S. K. Engelbart
* @author Leon Hofmeister
* @since 0.1.0
*/
public class CancelTest {
EventBus bus;
int hits;
/**
* Constructs an event bus and registers this test instance as an event listener.
*
* @since 0.1.0
*/
@BeforeEach
public void registerListener() {
bus = new EventBus();
bus.registerListener(this);
}
/**
* Tests {@link EventBus#cancel()} with two event handlers, of which the first cancels the
* event.
*
* @since 0.1.0
*/
@Test
public void testCancellation() {
bus.dispatch(new SimpleEvent());
assertEquals(1, hits);
}
@Event(SimpleEvent.class)
@Priority(100)
void onSimpleFirst() {
++hits;
bus.cancel();
}
@Event(SimpleEvent.class)
@Priority(50)
void onSimpleSecond() {
++hits;
}
}

View File

@ -0,0 +1,49 @@
package dev.kske.eventbus.core;
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;
/**
* Tests the dispatching of a dead event if an event could not be delivered.
*
* @author Kai S. K. Engelbart
* @since 1.1.0
*/
public class DeadTest {
EventBus bus = new EventBus();
String event = "This event has no handler";
boolean deadEventHandled;
/**
* Tests dead event delivery.
*
* @since 1.1.0
*/
@Test
public void testDeadEvent() {
bus.registerListener(this);
bus.dispatch(event);
assertTrue(deadEventHandled);
bus.removeListener(this);
}
/**
* Tests how the event bus reacts to an unhandled dead event. This should not lead to an
* exception or an endless recursion and should be logged instead.
*
* @since 1.1.0
*/
@Test
public void testUnhandledDeadEvent() {
bus.dispatch(event);
}
@Event
void onDeadEvent(DeadEvent deadEvent) {
assertEquals(bus, deadEvent.getEventBus());
assertEquals(event, deadEvent.getEvent());
deadEventHandled = true;
}
}

View File

@ -0,0 +1,79 @@
package dev.kske.eventbus.core;
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.*;
/**
* Tests the dispatching mechanism of the event bus.
*
* @author Kai S. K. Engelbart
* @since 0.0.1
*/
@Polymorphic
@Priority(150)
public class DispatchTest {
EventBus bus;
static int hits;
/**
* Constructs an event bus and registers this test instance as an event listener.
*
* @since 0.0.1
*/
@BeforeEach
public void registerListener() {
bus = new EventBus();
bus.registerListener(this);
bus.registerListener(SimpleEvent.class, e -> {
++hits;
assertEquals(4, hits);
});
}
/**
* Tests {@link EventBus#dispatch(Object)} with multiple handler priorities, a polymorphic
* handler and a static handler.
*
* @since 0.0.1
*/
@Test
public void testDispatch() {
bus.dispatch(new SimpleEventSub());
bus.dispatch(new SimpleEvent());
}
/**
* Tests {@link EventBus#debugExecutionOrder(Class)} based on the currently registered handlers.
*
* @since 1.2.0
*/
@Test
public void testDebugExecutionOrder() {
String executionOrder = bus.debugExecutionOrder(SimpleEvent.class);
System.out.println(executionOrder);
assertEquals(
"Event handler execution order for class dev.kske.eventbus.core.SimpleEvent (3 handler(s)):\n"
+ "==========================================================================================\n"
+ "ReflectiveEventHandler[eventType=class dev.kske.eventbus.core.SimpleEvent, polymorphic=true, priority=200, method=void dev.kske.eventbus.core.DispatchTest.onSimpleEventFirst(), useParameter=false]\n"
+ "ReflectiveEventHandler[eventType=class dev.kske.eventbus.core.SimpleEvent, polymorphic=false, priority=150, method=static void dev.kske.eventbus.core.DispatchTest.onSimpleEventSecond(), useParameter=false]\n"
+ "CallbackEventHandler[eventType=class dev.kske.eventbus.core.SimpleEvent, polymorphic=false, priority=100]\n"
+ "==========================================================================================",
executionOrder);
}
@Event(SimpleEvent.class)
@Priority(200)
void onSimpleEventFirst() {
++hits;
assertTrue(hits == 1 || hits == 2);
}
@Event(SimpleEvent.class)
@Polymorphic(false)
static void onSimpleEventSecond() {
++hits;
assertEquals(3, hits);
}
}

View File

@ -0,0 +1,62 @@
package dev.kske.eventbus.core;
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;
/**
* Tests the dispatching of an exception event if an event handler threw an exception.
*
* @author Kai S. K. Engelbart
* @since 1.1.0
*/
public class ExceptionTest {
EventBus bus = new EventBus();
String event = "This event will cause an exception";
RuntimeException exception = new RuntimeException("I failed");
boolean exceptionEventHandled;
/**
* Tests exception event delivery.
*
* @since 1.1.0
*/
@Test
public void testExceptionEvent() {
bus.registerListener(this);
bus.registerListener(new ExceptionListener());
bus.dispatch(event);
assertTrue(exceptionEventHandled);
bus.clearListeners();
}
/**
* Tests how the event bus reacts to an unhandled exception event. This should not lead to an
* exception or an endless recursion and should be logged instead.
*
* @since 1.1.0
*/
@Test
public void testUnhandledExceptionEvent() {
bus.registerListener(this);
bus.dispatch(event);
bus.removeListener(this);
}
@Event(String.class)
void onString() {
throw exception;
}
class ExceptionListener {
@Event
void onExceptionEvent(ExceptionEvent exceptionEvent) {
assertEquals(bus, exceptionEvent.getEventBus());
assertEquals(event, exceptionEvent.getEvent());
assertEquals(exception, exceptionEvent.getCause());
exceptionEventHandled = true;
}
}
}

View File

@ -0,0 +1,73 @@
package dev.kske.eventbus.core;
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.*;
/**
* Tests nested event dispatches.
*
* @author Kai S. K. Engelbart
* @since 1.2.0
*/
public class NestedTest {
EventBus bus;
boolean nestedHit;
/**
* Constructs an event bus and registers this test instance as an event listener.
*
* @since 1.2.0
*/
@BeforeEach
public void registerListener() {
bus = new EventBus();
bus.registerListener(this);
}
/**
* Dispatches a simple event, which should in turn cause a string to be dispatched as a nested
* event. If the corresponding handler sets {@link #nestedHit} to {@code true}, the test is
* successful.
*
* @since 1.2.0
*/
@Test
public void testNestedDispatch() {
bus.dispatch(new SimpleEvent());
assertTrue(nestedHit);
}
/**
* Dispatches a string as a nested event and cancels the current dispatch afterwards.
*
* @since 1.2.0
*/
@Event(SimpleEvent.class)
void onSimpleEvent() {
bus.dispatch("Nested event");
bus.cancel();
}
/**
* Sets {@link #nestedHit} to {@code true} indicating that nested dispatches work.
*
* @since 1.2.0
*/
@Event(String.class)
void onString() {
nestedHit = true;
}
/**
* Fails the test if an exception is caused during the dispatch.
*
* @param e the event containing the exception
* @since 1.2.0
*/
@Event
void onException(ExceptionEvent e) {
fail("Exception during dispatch", e.getCause());
}
}

View File

@ -0,0 +1,9 @@
package dev.kske.eventbus.core;
/**
* A simple event for testing purposes.
*
* @author Kai S. K. Engelbart
* @since 0.0.1
*/
public class SimpleEvent {}

View File

@ -0,0 +1,9 @@
package dev.kske.eventbus.core;
/**
* Subclass of {@link SimpleEvent} for testing purposes.
*
* @author Kai S. K. Engelbart
* @since 0.0.4
*/
public class SimpleEventSub extends SimpleEvent {}