Compare commits
24 Commits
Author | SHA1 | Date | |
---|---|---|---|
cc5c07079a | |||
8fae4f6d76 | |||
2d276a1d74 | |||
8609c6a90c | |||
ee9d08b2b8 | |||
5468bddb35 | |||
a8d858e8c7 | |||
6ee4e11161 | |||
8615a0e021 | |||
3aef7d5bcb | |||
36ed55fd71 | |||
999a172e72 | |||
722bf2b999 | |||
7fb633d69f | |||
c5607d12ae | |||
a8810c497f | |||
ebb2191f4a | |||
09d251a02a | |||
27d14a844d | |||
adbcc64e94 | |||
84ae42b44f | |||
e53f356c54 | |||
d649f377b7 | |||
897f1a20f3 |
41
Jenkinsfile
vendored
Normal file
41
Jenkinsfile
vendored
Normal file
@ -0,0 +1,41 @@
|
||||
pipeline {
|
||||
agent any
|
||||
|
||||
options {
|
||||
ansiColor('xterm')
|
||||
}
|
||||
|
||||
stages {
|
||||
stage('Build') {
|
||||
steps {
|
||||
sh 'mvn -DskipTests clean package'
|
||||
}
|
||||
}
|
||||
stage('Test') {
|
||||
steps {
|
||||
sh 'mvn test'
|
||||
}
|
||||
post {
|
||||
always {
|
||||
junit '*/target/surefire-reports/*.xml'
|
||||
publishCoverage adapters: [jacocoAdapter(mergeToOneReport: true, path: '*/target/site/jacoco/jacoco.xml')]
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('SonarQube Analysis') {
|
||||
when {
|
||||
branch 'develop'
|
||||
}
|
||||
steps {
|
||||
withSonarQubeEnv('KSKE SonarQube') {
|
||||
sh 'mvn sonar:sonar'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
post {
|
||||
success {
|
||||
archiveArtifacts artifacts: '*/target/event-bus-*.jar'
|
||||
}
|
||||
}
|
||||
}
|
36
README.md
36
README.md
@ -183,14 +183,37 @@ 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:
|
||||
An 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.
|
||||
|
||||
#### Yeeting Exceptions Out of an Event Handler
|
||||
|
||||
In some cases, a warning about an `Exception` that was thrown in an event handler is not enough, stays unnoticed, or an exception should be catched explicitly.
|
||||
Event Bus explicitly dispatches no `ExceptionEvent` when an `ExceptionWrapper` exception is thrown and instead simply rethrows it.
|
||||
`ExceptionWrapper` is an unchecked exception that (as the name says) simply wraps an exception that caused it.
|
||||
This means the following is possible and results in a normal program exit:
|
||||
```java
|
||||
@Event(String.class)
|
||||
void onString() {
|
||||
throw new ExceptionWrapper(new RuntimeException("I failed!"));
|
||||
}
|
||||
|
||||
void helloStackTrace() {
|
||||
EventBus.getInstance().registerListener(this);
|
||||
try {
|
||||
EventBus.getInstance().dispatch("A string!");
|
||||
System.exit(-1);
|
||||
} catch(ExceptionWrapper e) {
|
||||
e.getCause().printStackTrace();
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 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.
|
||||
@ -198,6 +221,15 @@ 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.
|
||||
|
||||
## Inheritance
|
||||
|
||||
When a superclass or an interface of an event listener defines event handlers, they will be detected and registered by Event Bus, even if they are `private`.
|
||||
If an event handler is overridden by the listener, the `@Event` annotation of the overridden method is automatically considered present on the overriding method.
|
||||
If the overridden method already contains an implementation in the superclass, the superclass implementation is ignored as expected.
|
||||
|
||||
The `@Priority` and `@Polymorphic` annotations are inherited both on a class and on a method level.
|
||||
If the priority or polymorphism has to be redefined on an inherited handler, the `@Event` annotation has to be added explicitly.
|
||||
|
||||
## Debugging
|
||||
|
||||
In more complex setups, taking a look at the event handler execution order can be helpful for debugging.
|
||||
|
66
core/pom.xml
Normal file
66
core/pom.xml
Normal file
@ -0,0 +1,66 @@
|
||||
<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 />
|
||||
|
||||
<plugins>
|
||||
|
||||
<!-- Run unit tests -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>3.0.0-M5</version>
|
||||
<configuration>
|
||||
<argLine>
|
||||
${argLine}
|
||||
--add-opens dev.kske.eventbus.core/dev.kske.eventbus.core=ALL-UNNAMED
|
||||
--add-opens dev.kske.eventbus.core/dev.kske.eventbus.core.handler=ALL-UNNAMED
|
||||
</argLine>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<!-- Generate coverage report -->
|
||||
<plugin>
|
||||
<groupId>org.jacoco</groupId>
|
||||
<artifactId>jacoco-maven-plugin</artifactId>
|
||||
<version>0.8.7</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>prepare-agent</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>report</id>
|
||||
<phase>test</phase>
|
||||
<goals>
|
||||
<goal>report</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
@ -30,13 +30,5 @@ public @interface Event {
|
||||
* @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 {}
|
||||
Class<?> value() default void.class;
|
||||
}
|
@ -2,7 +2,8 @@ package dev.kske.eventbus.core;
|
||||
|
||||
import java.lang.System.Logger;
|
||||
import java.lang.System.Logger.Level;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.*;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Consumer;
|
||||
@ -14,9 +15,8 @@ import dev.kske.eventbus.core.handler.*;
|
||||
* <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.
|
||||
*
|
||||
* @implNote This is a thread-safe implementation.
|
||||
* @author Kai S. K. Engelbart
|
||||
* @since 0.0.1
|
||||
* @see Event
|
||||
@ -90,6 +90,16 @@ public final class EventBus {
|
||||
*/
|
||||
private final Map<Class<?>, TreeSet<EventHandler>> bindings = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* A cache mapping an event class to all handlers the event should be dispatched to. This
|
||||
* includes polymorphic handlers that don't reference the event class explicitly. If an event
|
||||
* class is not contained inside this cache, the {@link #bindings} have to be traversed manually
|
||||
* in search of applicable handlers.
|
||||
*
|
||||
* @since 1.3.0
|
||||
*/
|
||||
private final Map<Class<?>, TreeSet<EventHandler>> bindingCache = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* Stores all registered event listeners (which declare event handlers) and prevents them from
|
||||
* being garbage collected.
|
||||
@ -112,10 +122,11 @@ public final class EventBus {
|
||||
*
|
||||
* @param event the event to dispatch
|
||||
* @throws EventBusException if an event handler isn't accessible or has an invalid signature
|
||||
* @throws ExceptionWrapper if it is thrown by an event handler
|
||||
* @throws NullPointerException if the specified event is {@code null}
|
||||
* @since 0.0.1
|
||||
*/
|
||||
public void dispatch(Object event) throws EventBusException {
|
||||
public void dispatch(Object event) {
|
||||
Objects.requireNonNull(event);
|
||||
logger.log(Level.INFO, "Dispatching event {0}", event);
|
||||
|
||||
@ -140,6 +151,10 @@ public final class EventBus {
|
||||
|
||||
// Transparently pass error to the caller
|
||||
throw (Error) e.getCause();
|
||||
else if (e.getCause() instanceof ExceptionWrapper)
|
||||
|
||||
// Transparently pass exception wrapper to the caller
|
||||
throw (ExceptionWrapper) e.getCause();
|
||||
else if (event instanceof DeadEvent || event instanceof ExceptionEvent)
|
||||
|
||||
// Warn about system event not being handled
|
||||
@ -170,11 +185,15 @@ public final class EventBus {
|
||||
* Searches for the event handlers bound to an event class. This includes polymorphic handlers
|
||||
* that are bound to a supertype of the event class.
|
||||
*
|
||||
* @implNote If the given event type was requested in the past, the handlers are retrieved from
|
||||
* the {@link #bindingCache}. If not, the entire {@link #bindings} are traversed in
|
||||
* search of polymorphic handlers compatible with the event type.
|
||||
* @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) {
|
||||
return bindingCache.computeIfAbsent(eventType, k -> {
|
||||
|
||||
// Get handlers defined for the event class
|
||||
TreeSet<EventHandler> handlers =
|
||||
@ -188,6 +207,7 @@ public final class EventBus {
|
||||
handlers.add(handler);
|
||||
|
||||
return handlers;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -214,7 +234,7 @@ public final class EventBus {
|
||||
* @since 0.0.1
|
||||
* @see Event
|
||||
*/
|
||||
public void registerListener(Object listener) throws EventBusException {
|
||||
public void registerListener(Object listener) {
|
||||
Objects.requireNonNull(listener);
|
||||
if (registeredListeners.contains(listener))
|
||||
throw new EventBusException(listener + " already registered!");
|
||||
@ -232,7 +252,7 @@ public final class EventBus {
|
||||
priority = listener.getClass().getAnnotation(Priority.class).value();
|
||||
|
||||
registeredListeners.add(listener);
|
||||
for (var method : listener.getClass().getDeclaredMethods()) {
|
||||
for (var method : getHandlerMethods(listener.getClass())) {
|
||||
Event annotation = method.getAnnotation(Event.class);
|
||||
|
||||
// Skip methods without annotations
|
||||
@ -252,6 +272,49 @@ public final class EventBus {
|
||||
listener.getClass().getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for event handling methods declared inside the inheritance hierarchy of an event
|
||||
* listener.
|
||||
*
|
||||
* @param listenerClass the class to inspect
|
||||
* @return all event handling methods defined for the given listener
|
||||
* @since 1.3.0
|
||||
*/
|
||||
private Set<Method> getHandlerMethods(Class<?> listenerClass) {
|
||||
|
||||
// Get methods declared by the listener
|
||||
Set<Method> methods = getMethodsAnnotatedWith(listenerClass, Event.class);
|
||||
|
||||
// Recursively add superclass handlers
|
||||
Class<?> superClass = listenerClass.getSuperclass();
|
||||
if (superClass != null && superClass != Object.class)
|
||||
methods.addAll(getHandlerMethods(superClass));
|
||||
|
||||
// Recursively add interface handlers
|
||||
for (Class<?> iClass : listenerClass.getInterfaces())
|
||||
methods.addAll(getHandlerMethods(iClass));
|
||||
|
||||
return methods;
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for declared methods with a specific annotation inside a class.
|
||||
*
|
||||
* @param enclosingClass the class to inspect
|
||||
* @param annotationClass the annotation to look for
|
||||
* @return all methods matching the search criteria
|
||||
* @since 1.3.0
|
||||
*/
|
||||
private Set<Method> getMethodsAnnotatedWith(Class<?> enclosingClass,
|
||||
Class<? extends Annotation> annotationClass) {
|
||||
var methods = new HashSet<Method>();
|
||||
for (var method : enclosingClass.getDeclaredMethods())
|
||||
if (method.isAnnotationPresent(annotationClass))
|
||||
methods.add(method);
|
||||
|
||||
return methods;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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}.
|
||||
@ -321,15 +384,28 @@ public final class EventBus {
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a new handler into the {@link #bindings} map.
|
||||
* Inserts a new handler into the {@link #bindings} map. Additionally, the handler is placed
|
||||
* inside the {@link #bindingCache} where applicable.
|
||||
*
|
||||
* @param handler the handler to bind
|
||||
* @since 1.2.0
|
||||
*/
|
||||
private void bindHandler(EventHandler handler) {
|
||||
bindings.putIfAbsent(handler.getEventType(), new TreeSet<>(byPriority));
|
||||
|
||||
// Bind handler
|
||||
logger.log(Level.DEBUG, "Binding event handler {0}", handler);
|
||||
bindings.get(handler.getEventType()).add(handler);
|
||||
bindings.computeIfAbsent(handler.getEventType(), k -> new TreeSet<>(byPriority))
|
||||
.add(handler);
|
||||
|
||||
// Insert handler into cache
|
||||
bindingCache.computeIfAbsent(handler.getEventType(), k -> new TreeSet<>(byPriority))
|
||||
.add(handler);
|
||||
|
||||
// Handler is polymorphic => insert where applicable
|
||||
if (handler.isPolymorphic())
|
||||
for (var binding : bindingCache.entrySet())
|
||||
if (binding.getKey().isAssignableFrom(handler.getEventType()))
|
||||
binding.getValue().add(handler);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -347,13 +423,25 @@ public final class EventBus {
|
||||
var it = binding.iterator();
|
||||
while (it.hasNext()) {
|
||||
var handler = it.next();
|
||||
if (handler.getListener() == listener) {
|
||||
if (handler.getListener().equals(listener)) {
|
||||
logger.log(Level.DEBUG, "Unbinding event handler {0}", handler);
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove bindings from cache
|
||||
for (var binding : bindingCache.values()) {
|
||||
var it = binding.iterator();
|
||||
while (it.hasNext()) {
|
||||
var handler = it.next();
|
||||
if (handler.getListener().equals(listener)) {
|
||||
logger.log(Level.TRACE, "Removing event handler {0} from cache", handler);
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the listener itself
|
||||
registeredListeners.remove(listener);
|
||||
}
|
||||
@ -366,6 +454,7 @@ public final class EventBus {
|
||||
public void clearListeners() {
|
||||
logger.log(Level.INFO, "Clearing event listeners");
|
||||
bindings.clear();
|
||||
bindingCache.clear();
|
||||
registeredListeners.clear();
|
||||
}
|
||||
|
@ -14,13 +14,14 @@ package dev.kske.eventbus.core;
|
||||
*/
|
||||
public final class EventBusException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final long serialVersionUID = 7254445250300604449L;
|
||||
|
||||
/**
|
||||
* Creates a new event bus exception.
|
||||
*
|
||||
* @param message the message to display
|
||||
* @param cause the cause of this exception
|
||||
* @since 0.0.1
|
||||
*/
|
||||
public EventBusException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
@ -30,6 +31,7 @@ public final class EventBusException extends RuntimeException {
|
||||
* Creates a new event bus exception.
|
||||
*
|
||||
* @param message the message to display
|
||||
* @since 0.0.1
|
||||
*/
|
||||
public EventBusException(String message) {
|
||||
super(message);
|
@ -0,0 +1,24 @@
|
||||
package dev.kske.eventbus.core;
|
||||
|
||||
/**
|
||||
* This unchecked exception acts as a wrapper for an arbitrary exception to prevent an
|
||||
* {@link ExceptionEvent} from being dispatched. Instead, the wrapped exception is rethrown by
|
||||
* {@link EventBus#dispatch(Object)}.
|
||||
*
|
||||
* @author Kai S. K. Engelbart
|
||||
* @since 1.2.1
|
||||
*/
|
||||
public final class ExceptionWrapper extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = -2016681140617308788L;
|
||||
|
||||
/**
|
||||
* Creates a new exception wrapper.
|
||||
*
|
||||
* @param cause the exception to wrap
|
||||
* @since 1.2.1
|
||||
*/
|
||||
public ExceptionWrapper(Exception cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
@ -18,6 +18,7 @@ import java.lang.annotation.*;
|
||||
* @see Event
|
||||
*/
|
||||
@Documented
|
||||
@Inherited
|
||||
@Retention(RUNTIME)
|
||||
@Target({ METHOD, TYPE })
|
||||
public @interface Polymorphic {
|
@ -21,6 +21,7 @@ import java.lang.annotation.*;
|
||||
* @see Event
|
||||
*/
|
||||
@Documented
|
||||
@Inherited
|
||||
@Retention(RUNTIME)
|
||||
@Target({ METHOD, TYPE })
|
||||
public @interface Priority {
|
@ -3,7 +3,6 @@ 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
|
||||
@ -37,7 +36,7 @@ public final class ReflectiveEventHandler implements EventHandler {
|
||||
boolean defPolymorphism, int defPriority) throws EventBusException {
|
||||
this.listener = listener;
|
||||
this.method = method;
|
||||
useParameter = annotation.value() == USE_PARAMETER.class;
|
||||
useParameter = annotation.value() == void.class;
|
||||
|
||||
// Check handler signature
|
||||
if (method.getParameterCount() == 0 && useParameter)
|
||||
@ -58,7 +57,8 @@ public final class ReflectiveEventHandler implements EventHandler {
|
||||
? method.getAnnotation(Priority.class).value()
|
||||
: defPriority;
|
||||
|
||||
// Allow access if the method is non-public
|
||||
// Try to allow access if the method is not accessible
|
||||
if (!method.canAccess(Modifier.isStatic(method.getModifiers()) ? null : listener))
|
||||
method.setAccessible(true);
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ import org.junit.jupiter.api.*;
|
||||
* @author Leon Hofmeister
|
||||
* @since 0.1.0
|
||||
*/
|
||||
public class CancelTest {
|
||||
class CancelTest {
|
||||
|
||||
EventBus bus;
|
||||
int hits;
|
||||
@ -22,7 +22,7 @@ public class CancelTest {
|
||||
* @since 0.1.0
|
||||
*/
|
||||
@BeforeEach
|
||||
public void registerListener() {
|
||||
void registerListener() {
|
||||
bus = new EventBus();
|
||||
bus.registerListener(this);
|
||||
}
|
||||
@ -34,7 +34,7 @@ public class CancelTest {
|
||||
* @since 0.1.0
|
||||
*/
|
||||
@Test
|
||||
public void testCancellation() {
|
||||
void testCancellation() {
|
||||
bus.dispatch(new SimpleEvent());
|
||||
assertEquals(1, hits);
|
||||
}
|
@ -10,7 +10,7 @@ import org.junit.jupiter.api.Test;
|
||||
* @author Kai S. K. Engelbart
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public class DeadTest {
|
||||
class DeadTest {
|
||||
|
||||
EventBus bus = new EventBus();
|
||||
String event = "This event has no handler";
|
||||
@ -22,7 +22,7 @@ public class DeadTest {
|
||||
* @since 1.1.0
|
||||
*/
|
||||
@Test
|
||||
public void testDeadEvent() {
|
||||
void testDeadEvent() {
|
||||
bus.registerListener(this);
|
||||
bus.dispatch(event);
|
||||
assertTrue(deadEventHandled);
|
||||
@ -36,7 +36,7 @@ public class DeadTest {
|
||||
* @since 1.1.0
|
||||
*/
|
||||
@Test
|
||||
public void testUnhandledDeadEvent() {
|
||||
void testUnhandledDeadEvent() {
|
||||
bus.dispatch(event);
|
||||
}
|
||||
|
@ -12,10 +12,9 @@ import org.junit.jupiter.api.*;
|
||||
*/
|
||||
@Polymorphic
|
||||
@Priority(150)
|
||||
public class DispatchTest {
|
||||
class DispatchTest {
|
||||
|
||||
EventBus bus;
|
||||
static int hits;
|
||||
|
||||
/**
|
||||
* Constructs an event bus and registers this test instance as an event listener.
|
||||
@ -23,12 +22,12 @@ public class DispatchTest {
|
||||
* @since 0.0.1
|
||||
*/
|
||||
@BeforeEach
|
||||
public void registerListener() {
|
||||
void registerListener() {
|
||||
bus = new EventBus();
|
||||
bus.registerListener(this);
|
||||
bus.registerListener(SimpleEvent.class, e -> {
|
||||
++hits;
|
||||
assertEquals(4, hits);
|
||||
e.increment();
|
||||
assertEquals(3, e.getCounter());
|
||||
});
|
||||
}
|
||||
|
||||
@ -39,7 +38,7 @@ public class DispatchTest {
|
||||
* @since 0.0.1
|
||||
*/
|
||||
@Test
|
||||
public void testDispatch() {
|
||||
void testDispatch() {
|
||||
bus.dispatch(new SimpleEventSub());
|
||||
bus.dispatch(new SimpleEvent());
|
||||
}
|
||||
@ -50,30 +49,40 @@ public class DispatchTest {
|
||||
* @since 1.2.0
|
||||
*/
|
||||
@Test
|
||||
public void testDebugExecutionOrder() {
|
||||
String executionOrder = bus.debugExecutionOrder(SimpleEvent.class);
|
||||
System.out.println(executionOrder);
|
||||
void testDebugExecutionOrder() {
|
||||
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"
|
||||
+ "ReflectiveEventHandler[eventType=class dev.kske.eventbus.core.SimpleEvent, polymorphic=true, priority=200, method=void dev.kske.eventbus.core.DispatchTest.onSimpleEventFirst(dev.kske.eventbus.core.SimpleEvent), useParameter=true]\n"
|
||||
+ "ReflectiveEventHandler[eventType=class dev.kske.eventbus.core.SimpleEvent, polymorphic=false, priority=150, method=static void dev.kske.eventbus.core.DispatchTest.onSimpleEventSecond(dev.kske.eventbus.core.SimpleEvent), useParameter=true]\n"
|
||||
+ "CallbackEventHandler[eventType=class dev.kske.eventbus.core.SimpleEvent, polymorphic=false, priority=100]\n"
|
||||
+ "==========================================================================================",
|
||||
executionOrder);
|
||||
bus.debugExecutionOrder(SimpleEvent.class));
|
||||
}
|
||||
|
||||
@Event(SimpleEvent.class)
|
||||
/**
|
||||
* Tests whether the handlers bound to an event type are correct when retrieved from the binding
|
||||
* cache. On the second call of {@link EventBus#debugExecutionOrder(Class)} the cache is used.
|
||||
*
|
||||
* @since 1.3.0
|
||||
*/
|
||||
@Test
|
||||
void testBindingCache() {
|
||||
assertEquals(bus.debugExecutionOrder(SimpleEventSub.class),
|
||||
bus.debugExecutionOrder(SimpleEventSub.class));
|
||||
}
|
||||
|
||||
@Event
|
||||
@Priority(200)
|
||||
void onSimpleEventFirst() {
|
||||
++hits;
|
||||
assertTrue(hits == 1 || hits == 2);
|
||||
void onSimpleEventFirst(SimpleEvent event) {
|
||||
event.increment();
|
||||
assertTrue(event.getCounter() == 1 || event.getCounter() == 2);
|
||||
}
|
||||
|
||||
@Event(SimpleEvent.class)
|
||||
@Event
|
||||
@Polymorphic(false)
|
||||
static void onSimpleEventSecond() {
|
||||
++hits;
|
||||
assertEquals(3, hits);
|
||||
static void onSimpleEventSecond(SimpleEvent event) {
|
||||
event.increment();
|
||||
assertEquals(2, event.getCounter());
|
||||
}
|
||||
}
|
@ -10,7 +10,7 @@ import org.junit.jupiter.api.Test;
|
||||
* @author Kai S. K. Engelbart
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public class ExceptionTest {
|
||||
class ExceptionTest {
|
||||
|
||||
EventBus bus = new EventBus();
|
||||
String event = "This event will cause an exception";
|
||||
@ -23,7 +23,7 @@ public class ExceptionTest {
|
||||
* @since 1.1.0
|
||||
*/
|
||||
@Test
|
||||
public void testExceptionEvent() {
|
||||
void testExceptionEvent() {
|
||||
bus.registerListener(this);
|
||||
bus.registerListener(new ExceptionListener());
|
||||
bus.dispatch(event);
|
||||
@ -38,7 +38,7 @@ public class ExceptionTest {
|
||||
* @since 1.1.0
|
||||
*/
|
||||
@Test
|
||||
public void testUnhandledExceptionEvent() {
|
||||
void testUnhandledExceptionEvent() {
|
||||
bus.registerListener(this);
|
||||
bus.dispatch(event);
|
||||
bus.removeListener(this);
|
@ -0,0 +1,33 @@
|
||||
package dev.kske.eventbus.core;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Tests the behavior of the event bus when an {@link ExceptionWrapper} is thrown.
|
||||
*
|
||||
* @author Kai S. K. Engelbart
|
||||
* @since 1.2.1
|
||||
*/
|
||||
class ExceptionWrapperTest {
|
||||
|
||||
EventBus bus = new EventBus();
|
||||
String event = "This event will cause an exception";
|
||||
|
||||
/**
|
||||
* Tests transparent rethrowing of an exception wrapper by {@link EventBus#dispatch(Object)}.
|
||||
*
|
||||
* @since 1.2.1
|
||||
*/
|
||||
@Test
|
||||
void testExceptionWrapper() {
|
||||
bus.registerListener(this);
|
||||
assertThrows(ExceptionWrapper.class, () -> bus.dispatch(event));
|
||||
}
|
||||
|
||||
@Event(String.class)
|
||||
void onString() {
|
||||
throw new ExceptionWrapper(new RuntimeException("I failed!"));
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
package dev.kske.eventbus.core;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Tests whether event handlers correctly work in the context of an inheritance hierarchy. The
|
||||
* effect of handler priorities is also accounted for.
|
||||
*
|
||||
* @author Kai S. K. Engelbart
|
||||
* @since 1.3.0
|
||||
*/
|
||||
class InheritanceTest extends SimpleEventListenerBase implements SimpleEventListenerInterface {
|
||||
|
||||
EventBus bus = new EventBus();
|
||||
|
||||
@Test
|
||||
void test() {
|
||||
bus.registerListener(this);
|
||||
var event = new SimpleEvent();
|
||||
|
||||
bus.dispatch(event);
|
||||
assertSame(3, event.getCounter());
|
||||
}
|
||||
|
||||
@Override
|
||||
void onSimpleEventAbstractHandler(SimpleEvent event) {
|
||||
assertSame(1, event.getCounter());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSimpleEventInterfaceHandler(SimpleEvent event) {
|
||||
event.increment();
|
||||
}
|
||||
|
||||
@Event
|
||||
@Priority(250)
|
||||
private void onSimpleEventPrivate(SimpleEvent event) {
|
||||
assertSame(0, event.getCounter());
|
||||
event.increment();
|
||||
}
|
||||
}
|
@ -10,7 +10,7 @@ import org.junit.jupiter.api.*;
|
||||
* @author Kai S. K. Engelbart
|
||||
* @since 1.2.0
|
||||
*/
|
||||
public class NestedTest {
|
||||
class NestedTest {
|
||||
|
||||
EventBus bus;
|
||||
boolean nestedHit;
|
||||
@ -21,7 +21,7 @@ public class NestedTest {
|
||||
* @since 1.2.0
|
||||
*/
|
||||
@BeforeEach
|
||||
public void registerListener() {
|
||||
void registerListener() {
|
||||
bus = new EventBus();
|
||||
bus.registerListener(this);
|
||||
}
|
||||
@ -34,7 +34,7 @@ public class NestedTest {
|
||||
* @since 1.2.0
|
||||
*/
|
||||
@Test
|
||||
public void testNestedDispatch() {
|
||||
void testNestedDispatch() {
|
||||
bus.dispatch(new SimpleEvent());
|
||||
assertTrue(nestedHit);
|
||||
}
|
31
core/src/test/java/dev/kske/eventbus/core/SimpleEvent.java
Normal file
31
core/src/test/java/dev/kske/eventbus/core/SimpleEvent.java
Normal file
@ -0,0 +1,31 @@
|
||||
package dev.kske.eventbus.core;
|
||||
|
||||
/**
|
||||
* A simple event for testing purposes. The event contains a counter that is supposed to be
|
||||
* incremented when the event is processed by a handler. That way it is possible to test whether all
|
||||
* handlers that were supposed to be invoked were in fact invoked.
|
||||
*
|
||||
* @author Kai S. K. Engelbart
|
||||
* @since 0.0.1
|
||||
*/
|
||||
class SimpleEvent {
|
||||
|
||||
private int counter;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("%s[%d]", getClass().getSimpleName(), counter);
|
||||
}
|
||||
|
||||
void increment() {
|
||||
++counter;
|
||||
}
|
||||
|
||||
int getCounter() {
|
||||
return counter;
|
||||
}
|
||||
|
||||
void reset() {
|
||||
counter = 0;
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package dev.kske.eventbus.core;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* An abstract class defining a package-private and a private handler for {@link SimpleEvent}.
|
||||
*
|
||||
* @author Kai S. K. Engelbart
|
||||
* @since 1.3.0
|
||||
*/
|
||||
@Priority(200)
|
||||
abstract class SimpleEventListenerBase {
|
||||
|
||||
@Event
|
||||
void onSimpleEventAbstractHandler(SimpleEvent event) {
|
||||
fail("This handler should not be invoked");
|
||||
}
|
||||
|
||||
@Event
|
||||
@Priority(150)
|
||||
private void onSimpleEventPrivate(SimpleEvent event) {
|
||||
assertSame(1, event.getCounter());
|
||||
event.increment();
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
package dev.kske.eventbus.core;
|
||||
|
||||
/**
|
||||
* An interface defining a single handler for {@link SimpleEvent}.
|
||||
*
|
||||
* @author Kai S. K. Engelbart
|
||||
* @since 1.3.0
|
||||
*/
|
||||
interface SimpleEventListenerInterface {
|
||||
|
||||
@Priority(120)
|
||||
@Event
|
||||
void onSimpleEventInterfaceHandler(SimpleEvent event);
|
||||
}
|
@ -6,4 +6,4 @@ package dev.kske.eventbus.core;
|
||||
* @author Kai S. K. Engelbart
|
||||
* @since 0.0.4
|
||||
*/
|
||||
public class SimpleEventSub extends SimpleEvent {}
|
||||
class SimpleEventSub extends SimpleEvent {}
|
@ -1,39 +0,0 @@
|
||||
<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>
|
@ -1,9 +0,0 @@
|
||||
package dev.kske.eventbus.core;
|
||||
|
||||
/**
|
||||
* A simple event for testing purposes.
|
||||
*
|
||||
* @author Kai S. K. Engelbart
|
||||
* @since 0.0.1
|
||||
*/
|
||||
public class SimpleEvent {}
|
4
pom.xml
4
pom.xml
@ -13,8 +13,8 @@
|
||||
<url>https://git.kske.dev/kske/event-bus</url>
|
||||
|
||||
<modules>
|
||||
<module>event-bus-core</module>
|
||||
<module>event-bus-proc</module>
|
||||
<module>core</module>
|
||||
<module>proc</module>
|
||||
</modules>
|
||||
|
||||
<licenses>
|
||||
|
@ -16,17 +16,17 @@
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="src" output="target/test-classes" path="home/kske/git/event-bus/event-bus-ap">
|
||||
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
|
||||
<attributes>
|
||||
<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 excluding="**" kind="src" output="target/classes" path="src/main/resources">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="output" path="target/classes"/>
|
||||
</classpath>
|
@ -24,9 +24,6 @@
|
||||
|
||||
<build>
|
||||
|
||||
<!-- Disable test folder -->
|
||||
<testSourceDirectory />
|
||||
|
||||
<plugins>
|
||||
|
||||
<!-- Prevent annotation processing error during compilation -->
|
@ -47,13 +47,12 @@ public class EventProcessor extends AbstractProcessor {
|
||||
|
||||
// Task failed successfully
|
||||
eventType = e.getTypeMirror();
|
||||
useParameter = processingEnv.getTypeUtils().isSameType(eventType,
|
||||
getTypeMirror(Event.USE_PARAMETER.class));
|
||||
useParameter = eventType.getKind() == TypeKind.VOID;
|
||||
}
|
||||
|
||||
// Check handler signature
|
||||
boolean pass = false;
|
||||
if (useParameter && eventHandler.getParameters().size() == 0)
|
||||
if (useParameter && eventHandler.getParameters().isEmpty())
|
||||
error(eventHandler, "The method or the annotation must define the event type");
|
||||
else if (!useParameter && eventHandler.getParameters().size() == 1)
|
||||
error(eventHandler,
|
||||
@ -107,7 +106,8 @@ public class EventProcessor extends AbstractProcessor {
|
||||
polymorphic = eventHandler.getAnnotation(Polymorphic.class).value();
|
||||
|
||||
// Effective priority
|
||||
int priority = hasListenerPriority ? listenerPriority.value() : defPriority;
|
||||
int priority =
|
||||
hasListenerPriority ? listenerPriority.value() : defPriority;
|
||||
boolean hasHandlerPriority = eventHandler.getAnnotation(Priority.class) != null;
|
||||
if (hasHandlerPriority)
|
||||
priority = eventHandler.getAnnotation(Priority.class).value();
|
||||
@ -137,14 +137,6 @@ public class EventProcessor extends AbstractProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
private TypeMirror getTypeMirror(Class<?> clazz) {
|
||||
return getTypeElement(clazz).asType();
|
||||
}
|
||||
|
||||
private TypeElement getTypeElement(Class<?> clazz) {
|
||||
return processingEnv.getElementUtils().getTypeElement(clazz.getCanonicalName());
|
||||
}
|
||||
|
||||
private void warning(Element e, String msg, Object... args) {
|
||||
processingEnv.getMessager().printMessage(Kind.WARNING, String.format(msg, args), e);
|
||||
}
|
@ -5,7 +5,7 @@
|
||||
* @author Kai S. K. Engelbart
|
||||
* @since 1.0.0
|
||||
*/
|
||||
module dev.kske.eventbus.ap {
|
||||
module dev.kske.eventbus.proc {
|
||||
|
||||
requires java.compiler;
|
||||
requires dev.kske.eventbus.core;
|
Loading…
Reference in New Issue
Block a user