Compare commits
17 Commits
1.0.0
...
51f10c4144
Author | SHA1 | Date | |
---|---|---|---|
51f10c4144
![]() |
|||
f74b953db8
|
|||
52719d22d4
![]() |
|||
122106bf39
|
|||
7357198d45
|
|||
32dfe64c0f
|
|||
2ec0a82a96
|
|||
6c74af608c
|
|||
d9ddc0e1a9
![]() |
|||
7c3cd017de
|
|||
6a2cad4ae5
|
|||
0f9b64be48
![]() |
|||
b2fe3a9d6c
|
|||
9379e6bb94
![]() |
|||
0036dc4829
|
|||
8a30493c52
|
|||
b56f08e441
|
39
README.md
39
README.md
@ -97,6 +97,13 @@ private void onSimpleEvent() {
|
|||||||
|
|
||||||
Make sure that you **do not** both declare a parameter and specify the event type in the annotation, as this would be ambiguous.
|
Make sure that you **do not** both declare a parameter and specify the event type in the annotation, as this would be ambiguous.
|
||||||
|
|
||||||
|
## Listener-Level Properties
|
||||||
|
|
||||||
|
When defining a dedicated event listener that, for example, performs pre- or post-processing, all event handlers will probably have the same non-standard priority.
|
||||||
|
Instead of defining that priority for each handler, it can be defined at the listener level by annotating the listener itself.
|
||||||
|
|
||||||
|
The same applies to polymorphism.
|
||||||
|
|
||||||
## Event Consumption
|
## Event Consumption
|
||||||
|
|
||||||
In some cases it might be useful to stop the propagation of an event.
|
In some cases it might be useful to stop the propagation of an event.
|
||||||
@ -123,6 +130,38 @@ This applies to all event handlers that would have been executed after the one c
|
|||||||
Avoid cancelling events while using multiple event handlers with the same priority.
|
Avoid cancelling events while using multiple event handlers with the same priority.
|
||||||
As event handlers are ordered by priority, it is not defined which of them will be executed after the event has been consumed.
|
As event handlers are ordered by priority, it is not defined which of them will be executed after the event has been consumed.
|
||||||
|
|
||||||
|
## System Events
|
||||||
|
|
||||||
|
To accommodate for special circumstances in an event distribution, system events have been introduced.
|
||||||
|
At the moment, there are two system events, which are explained in this section.
|
||||||
|
|
||||||
|
### Detecting Unhandled Events
|
||||||
|
|
||||||
|
When an event is dispatched but not delivered to any handler, a dead event is dispatched that wraps the original event.
|
||||||
|
You can declare a dead event handler to respond to this situation:
|
||||||
|
|
||||||
|
```java
|
||||||
|
private void onDeadEvent(DeadEvent deadEvent) { ... }
|
||||||
|
```
|
||||||
|
|
||||||
|
### Detecting Exceptions Thrown by Event Handlers
|
||||||
|
|
||||||
|
When an event handler throws an exception, an exception event is dispatched that wraps the original event.
|
||||||
|
A exception handler is declared as follows:
|
||||||
|
|
||||||
|
```java
|
||||||
|
private void onExceptionEvent(ExceptionEvent ExceptionEvent) { ... }
|
||||||
|
```
|
||||||
|
|
||||||
|
Both system events reference the event bus that caused them and a warning is logged if they are unhandled.
|
||||||
|
|
||||||
|
### What About Endless Recursion Caused By Dead Events and Exception Events?
|
||||||
|
|
||||||
|
As one might imagine, an unhandled dead event would theoretically lead to an endless recursion.
|
||||||
|
The same applies when an exception event handler throws an exception.
|
||||||
|
|
||||||
|
To avoid this, system events never cause system events and instead just issue a warning to the logger.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
Event Bus is available in Maven Central.
|
Event Bus is available in Maven Central.
|
||||||
|
@ -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; }
|
||||||
|
}
|
@ -2,6 +2,7 @@ package dev.kske.eventbus.core;
|
|||||||
|
|
||||||
import java.lang.System.Logger;
|
import java.lang.System.Logger;
|
||||||
import java.lang.System.Logger.Level;
|
import java.lang.System.Logger.Level;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
@ -29,6 +30,14 @@ public final class EventBus {
|
|||||||
boolean isDispatching, isCancelled;
|
boolean isDispatching, isCancelled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 volatile EventBus singletonInstance;
|
private static volatile EventBus singletonInstance;
|
||||||
|
|
||||||
private static final Logger logger = System.getLogger(EventBus.class.getName());
|
private static final Logger logger = System.getLogger(EventBus.class.getName());
|
||||||
@ -63,9 +72,10 @@ public final class EventBus {
|
|||||||
* priority.
|
* priority.
|
||||||
*
|
*
|
||||||
* @param event the event to dispatch
|
* @param event the event to dispatch
|
||||||
|
* @throws EventBusException if an event handler isn't accessible or has an invalid signature
|
||||||
* @since 0.0.1
|
* @since 0.0.1
|
||||||
*/
|
*/
|
||||||
public void dispatch(Object event) {
|
public void dispatch(Object event) throws EventBusException {
|
||||||
Objects.requireNonNull(event);
|
Objects.requireNonNull(event);
|
||||||
logger.log(Level.INFO, "Dispatching event {0}", event);
|
logger.log(Level.INFO, "Dispatching event {0}", event);
|
||||||
|
|
||||||
@ -73,13 +83,39 @@ public final class EventBus {
|
|||||||
var state = dispatchState.get();
|
var state = dispatchState.get();
|
||||||
state.isDispatching = true;
|
state.isDispatching = true;
|
||||||
|
|
||||||
for (var handler : getHandlersFor(event.getClass()))
|
Iterator<EventHandler> handlers = getHandlersFor(event.getClass());
|
||||||
|
if (handlers.hasNext()) {
|
||||||
|
while (handlers.hasNext())
|
||||||
if (state.isCancelled) {
|
if (state.isCancelled) {
|
||||||
logger.log(Level.INFO, "Cancelled dispatching event {0}", event);
|
logger.log(Level.INFO, "Cancelled dispatching event {0}", event);
|
||||||
state.isCancelled = false;
|
state.isCancelled = false;
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
handler.execute(event);
|
try {
|
||||||
|
handlers.next().execute(event);
|
||||||
|
} catch (InvocationTargetException e) {
|
||||||
|
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 if (e.getCause() instanceof Error)
|
||||||
|
|
||||||
|
// Transparently pass error to the caller
|
||||||
|
throw (Error) e.getCause();
|
||||||
|
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));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset dispatch state
|
// Reset dispatch state
|
||||||
@ -89,25 +125,26 @@ public final class EventBus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Searches for the event handlers bound to an event class.
|
* 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 eventClass the event class to use for the search
|
* @param eventClass the event class to use for the search
|
||||||
* @return all event handlers registered for the event class
|
* @return an iterator over the applicable handlers in descending order of priority
|
||||||
* @since 0.0.1
|
* @since 0.0.1
|
||||||
*/
|
*/
|
||||||
private List<EventHandler> getHandlersFor(Class<?> eventClass) {
|
private Iterator<EventHandler> getHandlersFor(Class<?> eventClass) {
|
||||||
|
|
||||||
// Get handlers defined for the event class
|
// Get handlers defined for the event class
|
||||||
Set<EventHandler> handlers = bindings.getOrDefault(eventClass, new TreeSet<>());
|
TreeSet<EventHandler> handlers = bindings.getOrDefault(eventClass, new TreeSet<>());
|
||||||
|
|
||||||
// Get subtype handlers
|
// Get polymorphic handlers
|
||||||
for (var binding : bindings.entrySet())
|
for (var binding : bindings.entrySet())
|
||||||
if (binding.getKey().isAssignableFrom(eventClass))
|
if (binding.getKey().isAssignableFrom(eventClass))
|
||||||
for (var handler : binding.getValue())
|
for (var handler : binding.getValue())
|
||||||
if (handler.isPolymorphic())
|
if (handler.isPolymorphic())
|
||||||
handlers.add(handler);
|
handlers.add(handler);
|
||||||
|
|
||||||
return new ArrayList<>(handlers);
|
return handlers.iterator();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -140,6 +177,16 @@ public final class EventBus {
|
|||||||
logger.log(Level.INFO, "Registering event listener {0}", listener.getClass().getName());
|
logger.log(Level.INFO, "Registering event listener {0}", listener.getClass().getName());
|
||||||
boolean handlerBound = false;
|
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);
|
registeredListeners.add(listener);
|
||||||
for (var method : listener.getClass().getDeclaredMethods()) {
|
for (var method : listener.getClass().getDeclaredMethods()) {
|
||||||
Event annotation = method.getAnnotation(Event.class);
|
Event annotation = method.getAnnotation(Event.class);
|
||||||
@ -149,7 +196,7 @@ public final class EventBus {
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
// Initialize and bind the handler
|
// Initialize and bind the handler
|
||||||
var handler = new EventHandler(listener, method, annotation);
|
var handler = new EventHandler(listener, method, annotation, polymorphic, priority);
|
||||||
bindings.putIfAbsent(handler.getEventType(), new TreeSet<>());
|
bindings.putIfAbsent(handler.getEventType(), new TreeSet<>());
|
||||||
logger.log(Level.DEBUG, "Binding event handler {0}", handler);
|
logger.log(Level.DEBUG, "Binding event handler {0}", handler);
|
||||||
bindings.get(handler.getEventType())
|
bindings.get(handler.getEventType())
|
||||||
|
@ -13,14 +13,6 @@ import dev.kske.eventbus.core.Event.USE_PARAMETER;
|
|||||||
*/
|
*/
|
||||||
final class EventHandler implements Comparable<EventHandler> {
|
final class EventHandler implements Comparable<EventHandler> {
|
||||||
|
|
||||||
/**
|
|
||||||
* The priority assigned to every event handler without an explicitly defined priority.
|
|
||||||
*
|
|
||||||
* @since 1.0.0
|
|
||||||
* @see Priority
|
|
||||||
*/
|
|
||||||
public static final int DEFAULT_PRIORITY = 100;
|
|
||||||
|
|
||||||
private final Object listener;
|
private final Object listener;
|
||||||
private final Method method;
|
private final Method method;
|
||||||
private final Class<?> eventType;
|
private final Class<?> eventType;
|
||||||
@ -34,11 +26,14 @@ final class EventHandler implements Comparable<EventHandler> {
|
|||||||
* @param listener the listener containing the handler
|
* @param listener the listener containing the handler
|
||||||
* @param method the handler method
|
* @param method the handler method
|
||||||
* @param annotation the event annotation
|
* @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
|
* @throws EventBusException if the method or the annotation do not comply with the
|
||||||
* specification
|
* specification
|
||||||
* @since 0.0.1
|
* @since 0.0.1
|
||||||
*/
|
*/
|
||||||
EventHandler(Object listener, Method method, Event annotation) throws EventBusException {
|
EventHandler(Object listener, Method method, Event annotation, boolean defPolymorphism,
|
||||||
|
int defPriority) throws EventBusException {
|
||||||
this.listener = listener;
|
this.listener = listener;
|
||||||
this.method = method;
|
this.method = method;
|
||||||
useParameter = annotation.value() == USE_PARAMETER.class;
|
useParameter = annotation.value() == USE_PARAMETER.class;
|
||||||
@ -55,10 +50,12 @@ final class EventHandler implements Comparable<EventHandler> {
|
|||||||
|
|
||||||
// Determine handler properties
|
// Determine handler properties
|
||||||
eventType = useParameter ? method.getParameterTypes()[0] : annotation.value();
|
eventType = useParameter ? method.getParameterTypes()[0] : annotation.value();
|
||||||
polymorphic = method.isAnnotationPresent(Polymorphic.class);
|
polymorphic = method.isAnnotationPresent(Polymorphic.class)
|
||||||
|
? method.getAnnotation(Polymorphic.class).value()
|
||||||
|
: defPolymorphism;
|
||||||
priority = method.isAnnotationPresent(Priority.class)
|
priority = method.isAnnotationPresent(Priority.class)
|
||||||
? method.getAnnotation(Priority.class).value()
|
? method.getAnnotation(Priority.class).value()
|
||||||
: DEFAULT_PRIORITY;
|
: defPriority;
|
||||||
|
|
||||||
// Allow access if the method is non-public
|
// Allow access if the method is non-public
|
||||||
method.setAccessible(true);
|
method.setAccessible(true);
|
||||||
@ -68,7 +65,7 @@ final class EventHandler implements Comparable<EventHandler> {
|
|||||||
* Compares this to another event handler based on priority. In case of equal priority a
|
* Compares this to another event handler based on priority. In case of equal priority a
|
||||||
* non-zero value based on hash codes is returned.
|
* non-zero value based on hash codes is returned.
|
||||||
* <p>
|
* <p>
|
||||||
* This is used to retrieve event handlers in order of descending priority from a tree set.
|
* This is used to retrieve event handlers in descending order of priority from a tree set.
|
||||||
*
|
*
|
||||||
* @since 0.0.1
|
* @since 0.0.1
|
||||||
*/
|
*/
|
||||||
@ -91,17 +88,22 @@ final class EventHandler implements Comparable<EventHandler> {
|
|||||||
* Executes the event handler.
|
* Executes the event handler.
|
||||||
*
|
*
|
||||||
* @param event the event used as the method parameter
|
* @param event the event used as the method parameter
|
||||||
* @throws EventBusException if the handler throws an exception
|
* @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 0.0.1
|
* @since 0.0.1
|
||||||
*/
|
*/
|
||||||
void execute(Object event) throws EventBusException {
|
void execute(Object event) throws EventBusException, InvocationTargetException {
|
||||||
try {
|
try {
|
||||||
if (useParameter)
|
if (useParameter)
|
||||||
method.invoke(listener, event);
|
method.invoke(listener, event);
|
||||||
else
|
else
|
||||||
method.invoke(listener);
|
method.invoke(listener);
|
||||||
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
throw new EventBusException("Failed to invoke event handler!", e);
|
throw new EventBusException("Event handler rejected target / argument!", e);
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
throw new EventBusException("Event handler is not accessible!", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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; }
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
package dev.kske.eventbus.core;
|
package dev.kske.eventbus.core;
|
||||||
|
|
||||||
import static java.lang.annotation.ElementType.METHOD;
|
import static java.lang.annotation.ElementType.*;
|
||||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||||
|
|
||||||
import java.lang.annotation.*;
|
import java.lang.annotation.*;
|
||||||
@ -8,6 +8,9 @@ import java.lang.annotation.*;
|
|||||||
/**
|
/**
|
||||||
* Allows an event handler to receive events that are subtypes of the declared event type.
|
* Allows an event handler to receive events that are subtypes of the declared event type.
|
||||||
* <p>
|
* <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.
|
* This is useful when defining an event handler for an interface or an abstract class.
|
||||||
*
|
*
|
||||||
* @author Kai S. K. Engelbart
|
* @author Kai S. K. Engelbart
|
||||||
@ -16,5 +19,12 @@ import java.lang.annotation.*;
|
|||||||
*/
|
*/
|
||||||
@Documented
|
@Documented
|
||||||
@Retention(RUNTIME)
|
@Retention(RUNTIME)
|
||||||
@Target(METHOD)
|
@Target({ METHOD, TYPE })
|
||||||
public @interface Polymorphic {}
|
public @interface Polymorphic {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return whether the event handler is polymorphic
|
||||||
|
* @since 1.1.0
|
||||||
|
*/
|
||||||
|
boolean value() default true;
|
||||||
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package dev.kske.eventbus.core;
|
package dev.kske.eventbus.core;
|
||||||
|
|
||||||
import static java.lang.annotation.ElementType.METHOD;
|
import static java.lang.annotation.ElementType.*;
|
||||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||||
|
|
||||||
import java.lang.annotation.*;
|
import java.lang.annotation.*;
|
||||||
@ -9,6 +9,9 @@ import java.lang.annotation.*;
|
|||||||
* Defines the priority of an event handler. Handlers are executed in descending order of their
|
* Defines the priority of an event handler. Handlers are executed in descending order of their
|
||||||
* priority.
|
* priority.
|
||||||
* <p>
|
* <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.
|
* Handlers without this annotation have the default priority of 100.
|
||||||
* <p>
|
* <p>
|
||||||
* The execution order of handlers with the same priority is undefined.
|
* The execution order of handlers with the same priority is undefined.
|
||||||
@ -19,7 +22,7 @@ import java.lang.annotation.*;
|
|||||||
*/
|
*/
|
||||||
@Documented
|
@Documented
|
||||||
@Retention(RUNTIME)
|
@Retention(RUNTIME)
|
||||||
@Target(METHOD)
|
@Target({ METHOD, TYPE })
|
||||||
public @interface Priority {
|
public @interface Priority {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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
|
||||||
|
*/
|
||||||
|
class DeadTest {
|
||||||
|
|
||||||
|
EventBus bus = new EventBus();
|
||||||
|
String event = "This event has no handler";
|
||||||
|
boolean deadEventHandled;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests dead event delivery.
|
||||||
|
*
|
||||||
|
* @since 1.1.0
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
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
|
||||||
|
void testUnhandledDeadEvent() {
|
||||||
|
bus.dispatch(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Event
|
||||||
|
void onDeadEvent(DeadEvent deadEvent) {
|
||||||
|
assertEquals(bus, deadEvent.getEventBus());
|
||||||
|
assertEquals(event, deadEvent.getEvent());
|
||||||
|
deadEventHandled = true;
|
||||||
|
}
|
||||||
|
}
|
@ -10,6 +10,8 @@ import org.junit.jupiter.api.*;
|
|||||||
* @author Kai S. K. Engelbart
|
* @author Kai S. K. Engelbart
|
||||||
* @since 0.0.1
|
* @since 0.0.1
|
||||||
*/
|
*/
|
||||||
|
@Polymorphic
|
||||||
|
@Priority(150)
|
||||||
class DispatchTest {
|
class DispatchTest {
|
||||||
|
|
||||||
EventBus bus;
|
EventBus bus;
|
||||||
@ -27,8 +29,8 @@ class DispatchTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests {@link EventBus#dispatch(Object)} with multiple handler priorities, a subtype handler
|
* Tests {@link EventBus#dispatch(Object)} with multiple handler priorities, a polymorphic
|
||||||
* and a static handler.
|
* handler and a static handler.
|
||||||
*
|
*
|
||||||
* @since 0.0.1
|
* @since 0.0.1
|
||||||
*/
|
*/
|
||||||
@ -40,20 +42,21 @@ class DispatchTest {
|
|||||||
|
|
||||||
@Event(SimpleEvent.class)
|
@Event(SimpleEvent.class)
|
||||||
@Priority(200)
|
@Priority(200)
|
||||||
@Polymorphic
|
|
||||||
void onSimpleEventFirst() {
|
void onSimpleEventFirst() {
|
||||||
++hits;
|
++hits;
|
||||||
assertTrue(hits == 1 || hits == 2);
|
assertTrue(hits == 1 || hits == 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Event(SimpleEvent.class)
|
@Event(SimpleEvent.class)
|
||||||
@Priority(150)
|
@Polymorphic(false)
|
||||||
static void onSimpleEventSecond() {
|
static void onSimpleEventSecond() {
|
||||||
++hits;
|
++hits;
|
||||||
assertEquals(3, hits);
|
assertEquals(3, hits);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Event
|
@Event
|
||||||
|
@Polymorphic(false)
|
||||||
|
@Priority(100)
|
||||||
void onSimpleEventThird(SimpleEvent event) {
|
void onSimpleEventThird(SimpleEvent event) {
|
||||||
++hits;
|
++hits;
|
||||||
assertEquals(4, hits);
|
assertEquals(4, hits);
|
||||||
|
@ -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
|
||||||
|
*/
|
||||||
|
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
|
||||||
|
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
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -63,6 +63,10 @@ public class EventProcessor extends AbstractProcessor {
|
|||||||
else
|
else
|
||||||
pass = true;
|
pass = true;
|
||||||
|
|
||||||
|
// Warn the user about unused return values
|
||||||
|
if (useParameter && eventHandler.getReturnType().getKind() != TypeKind.VOID)
|
||||||
|
warning(eventHandler, "Unused return value");
|
||||||
|
|
||||||
// Abort checking if the handler signature is incorrect
|
// Abort checking if the handler signature is incorrect
|
||||||
if (!pass)
|
if (!pass)
|
||||||
continue;
|
continue;
|
||||||
@ -80,14 +84,56 @@ public class EventProcessor extends AbstractProcessor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for handlers for abstract types that aren't polymorphic
|
// Get the listener containing this handler
|
||||||
|
TypeElement listener = (TypeElement) eventHandler.getEnclosingElement();
|
||||||
|
|
||||||
|
// Default properties
|
||||||
|
boolean defPolymorphic = false;
|
||||||
|
int defPriority = 100;
|
||||||
|
|
||||||
|
// Listener-level polymorphism
|
||||||
|
Polymorphic listenerPolymorphic = listener.getAnnotation(Polymorphic.class);
|
||||||
|
boolean hasListenerPolymorphic = listenerPolymorphic != null;
|
||||||
|
|
||||||
|
// Listener-level priority
|
||||||
|
Priority listenerPriority = listener.getAnnotation(Priority.class);
|
||||||
|
boolean hasListenerPriority = listenerPriority != null;
|
||||||
|
|
||||||
|
// Effective polymorphism
|
||||||
|
boolean polymorphic =
|
||||||
|
hasListenerPolymorphic ? listenerPolymorphic.value() : defPolymorphic;
|
||||||
|
boolean hasHandlerPolymorphic = eventHandler.getAnnotation(Polymorphic.class) != null;
|
||||||
|
if (hasHandlerPolymorphic)
|
||||||
|
polymorphic = eventHandler.getAnnotation(Polymorphic.class).value();
|
||||||
|
|
||||||
|
// Effective priority
|
||||||
|
int priority = hasListenerPriority ? listenerPriority.value() : defPriority;
|
||||||
|
boolean hasHandlerPriority = eventHandler.getAnnotation(Priority.class) != null;
|
||||||
|
if (hasHandlerPriority)
|
||||||
|
priority = eventHandler.getAnnotation(Priority.class).value();
|
||||||
|
|
||||||
|
// Detect useless polymorphism redefinition
|
||||||
|
if (hasListenerPolymorphic && hasHandlerPolymorphic
|
||||||
|
&& listenerPolymorphic.value() == polymorphic)
|
||||||
|
warning(eventHandler, "@Polymorphism is already defined at listener level");
|
||||||
|
|
||||||
|
// Detect useless priority redefinition
|
||||||
|
if (hasListenerPriority && hasHandlerPriority && listenerPriority.value() == priority)
|
||||||
|
warning(eventHandler, "@Priority is already defined at the listener level");
|
||||||
|
|
||||||
|
// Detect missing or useless @Polymorphic
|
||||||
Element eventElement = ((DeclaredType) eventType).asElement();
|
Element eventElement = ((DeclaredType) eventType).asElement();
|
||||||
if (eventHandler.getAnnotation(Polymorphic.class) == null
|
|
||||||
&& (eventElement.getKind() == ElementKind.INTERFACE
|
// Check for handlers for abstract types that aren't polymorphic
|
||||||
|| eventElement.getModifiers().contains(Modifier.ABSTRACT))) {
|
if (!polymorphic && (eventElement.getKind() == ElementKind.INTERFACE
|
||||||
|
|| eventElement.getModifiers().contains(Modifier.ABSTRACT)))
|
||||||
warning(eventHandler,
|
warning(eventHandler,
|
||||||
"Parameter should be instantiable or handler should use @Polymorphic");
|
"Parameter should be instantiable or handler should use @Polymorphic");
|
||||||
}
|
|
||||||
|
// Check for handlers for final types that are polymorphic
|
||||||
|
else if (polymorphic && eventElement.getModifiers().contains(Modifier.FINAL))
|
||||||
|
warning(eventHandler,
|
||||||
|
"@Polymorphic should be removed as parameter cannot be subclassed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user