Remove EventListener and IEvent marker interfaces

This allows Event Bus to interface with existing classes without
modification.
This commit is contained in:
Kai S. K. Engelbart 2021-02-15 14:43:34 +01:00
parent cd2e7ad023
commit 002180ed3b
Signed by: kske
GPG Key ID: 8BEB13EC5DF7EF13
10 changed files with 33 additions and 82 deletions

View File

@ -3,7 +3,7 @@
## Introduction ## Introduction
This library allows passing events between different objects without them having a direct reference to each other. This library allows passing events between different objects without them having a direct reference to each other.
Any class can be made an event by implementing the `IEvent` interface. Any object can serve as an event.
Using an instance of the `EventBus` class, an instant of the event class can be dispatched. Using an instance of the `EventBus` class, an instant of the event class can be dispatched.
This means that it will be forwarded to all listeners registered for it at the event bus. This means that it will be forwarded to all listeners registered for it at the event bus.
@ -13,16 +13,13 @@ In addition, a singleton instance of the event bus is provided by the `EventBus#
To listen to events, register event handling methods using the `Event` annotation. To listen to events, register event handling methods using the `Event` annotation.
For this to work, the method must have a return type of `void` and declare a single parameter of the desired event type. For this to work, the method must have a return type of `void` and declare a single parameter of the desired event type.
Alternatively, a parameter-less event handler can be declared as shown [below](#parameter-less-event-handlers). Alternatively, a parameter-less event handler can be declared as shown [below](#parameter-less-event-handlers).
Additionally, the class containing the method must implement the `EventListener` interface.
## A Simple Example ## A Simple Example
Lets look at a simple example: we declare the empty class `SimpleEvent` that implements `IEvent` and can thus be used as an event. Lets look at a simple example: we declare the empty class `SimpleEvent` whose objects can be used as events.
```java ```java
import dev.kske.eventbus.core.IEvent; public class SimpleEvent {}
public class SimpleEvent implements IEvent {}
``` ```
Next, an event listener for the `SimpleEvent` is declared: Next, an event listener for the `SimpleEvent` is declared:
@ -30,7 +27,7 @@ Next, an event listener for the `SimpleEvent` is declared:
```java ```java
import dev.kske.eventbus.core.*; import dev.kske.eventbus.core.*;
public class SimpleEventListener implements EventListener { public class SimpleEventListener {
public SimpleEventListener() { public SimpleEventListener() {
@ -165,7 +162,7 @@ opens my.module to dev.kske.eventbus.core;
To assist you with writing event listeners, the Event Bus AP (Annotation Processor) module enforces correct usage of the `@Event` annotation during compile time. To assist you with writing event listeners, the Event Bus AP (Annotation Processor) module enforces correct usage of the `@Event` annotation during compile time.
This reduces difficult-to-debug bugs that occur during runtime to compile-time errors which can be easily fixed. This reduces difficult-to-debug bugs that occur during runtime to compile-time errors which can be easily fixed.
The event annotation processor detects invalid event handlers, missing `EventListener` implementations, event type issues with more to come in future versions. The event annotation processor detects invalid event handlers and event type issues with more to come in future versions.
When using Maven, it can be registered using the Maven Compiler Plugin: When using Maven, it can be registered using the Maven Compiler Plugin:

View File

@ -34,8 +34,7 @@ public class EventProcessor extends AbstractProcessor {
private void processRound(Set<ExecutableElement> eventHandlers) { private void processRound(Set<ExecutableElement> eventHandlers) {
for (ExecutableElement eventHandler : eventHandlers) { for (ExecutableElement eventHandler : eventHandlers) {
TypeElement eventListener = (TypeElement) eventHandler.getEnclosingElement(); Event eventAnnotation = eventHandler.getAnnotation(Event.class);
Event eventAnnotation = eventHandler.getAnnotation(Event.class);
// Determine how the event type is defined // Determine how the event type is defined
boolean useParameter; boolean useParameter;
@ -68,11 +67,6 @@ public class EventProcessor extends AbstractProcessor {
var paramElement = eventHandler.getParameters().get(0); var paramElement = eventHandler.getParameters().get(0);
var paramType = paramElement.asType(); var paramType = paramElement.asType();
// Check for valid event type
if (useParameter && !processingEnv.getTypeUtils().isAssignable(paramType,
getTypeMirror(IEvent.class)))
error(paramElement, "Parameter must implement IEvent");
// Check for handlers for abstract types that aren't polymorphic // Check for handlers for abstract types that aren't polymorphic
if (eventHandler.getAnnotation(Polymorphic.class) == null if (eventHandler.getAnnotation(Polymorphic.class) == null
&& paramType.getKind() == TypeKind.DECLARED) { && paramType.getKind() == TypeKind.DECLARED) {
@ -82,11 +76,6 @@ public class EventProcessor extends AbstractProcessor {
warning(paramElement, warning(paramElement,
"Parameter should be instantiable or handler should use @Polymorphic"); "Parameter should be instantiable or handler should use @Polymorphic");
} }
// Check listener for interface implementation
if (!eventListener.getInterfaces().contains(getTypeMirror(EventListener.class)))
warning(eventHandler.getEnclosingElement(),
"Class should implement EventListener interface");
} }
} }

View File

@ -9,10 +9,9 @@ import java.lang.annotation.*;
* Indicates that a method is an event handler. To be successfully used as such, the method has to * Indicates that a method is an event handler. To be successfully used as such, the method has to
* comply with the following specifications: * comply with the following specifications:
* <ul> * <ul>
* <li>Declared inside a class that implements {@link EventListener}</li>
* <li>Specifying an event type by either * <li>Specifying an event type by either
* <ul> * <ul>
* <li>Declaring one parameter of a type that implements {@link IEvent}</li> * <li>Declaring one object parameter</li>
* <li>Defining the class of the event using the annotation value</li> * <li>Defining the class of the event using the annotation value</li>
* </ul> * </ul>
* </li> * </li>
@ -38,7 +37,7 @@ public @interface Event {
* @return the event type accepted by the handler * @return the event type accepted by the handler
* @since 1.0.0 * @since 1.0.0
*/ */
Class<? extends IEvent> value() default USE_PARAMETER.class; Class<?> value() default USE_PARAMETER.class;
/** /**
* Signifies that the event type the handler listens to is determined by the type of its only * Signifies that the event type the handler listens to is determined by the type of its only
@ -46,5 +45,5 @@ public @interface Event {
* *
* @since 0.0.3 * @since 0.0.3
*/ */
static final class USE_PARAMETER implements IEvent {} static final class USE_PARAMETER {}
} }

View File

@ -51,11 +51,11 @@ public final class EventBus {
return instance; return instance;
} }
private final Map<Class<? extends IEvent>, TreeSet<EventHandler>> bindings = private final Map<Class<?>, TreeSet<EventHandler>> bindings =
new ConcurrentHashMap<>(); new ConcurrentHashMap<>();
private final Set<EventListener> registeredListeners = private final Set<Object> registeredListeners =
ConcurrentHashMap.newKeySet(); ConcurrentHashMap.newKeySet();
private final ThreadLocal<DispatchState> dispatchState = private final ThreadLocal<DispatchState> dispatchState =
ThreadLocal.withInitial(DispatchState::new); ThreadLocal.withInitial(DispatchState::new);
/** /**
@ -65,7 +65,7 @@ public final class EventBus {
* @param event the event to dispatch * @param event the event to dispatch
* @since 0.0.1 * @since 0.0.1
*/ */
public void dispatch(IEvent event) { public void dispatch(Object event) {
Objects.requireNonNull(event); Objects.requireNonNull(event);
logger.log(Level.INFO, "Dispatching event {0}", event); logger.log(Level.INFO, "Dispatching event {0}", event);
@ -95,7 +95,7 @@ public final class EventBus {
* @return all event handlers registered for the event class * @return all event handlers registered for the event class
* @since 0.0.1 * @since 0.0.1
*/ */
private List<EventHandler> getHandlersFor(Class<? extends IEvent> eventClass) { private List<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<>()); Set<EventHandler> handlers = bindings.getOrDefault(eventClass, new TreeSet<>());
@ -133,7 +133,7 @@ public final class EventBus {
* @since 0.0.1 * @since 0.0.1
* @see Event * @see Event
*/ */
public void registerListener(EventListener listener) throws EventBusException { public void registerListener(Object listener) throws EventBusException {
Objects.requireNonNull(listener); Objects.requireNonNull(listener);
if (registeredListeners.contains(listener)) if (registeredListeners.contains(listener))
throw new EventBusException(listener + " already registered!"); throw new EventBusException(listener + " already registered!");
@ -170,7 +170,7 @@ public final class EventBus {
* @param listener the listener to remove * @param listener the listener to remove
* @since 0.0.1 * @since 0.0.1
*/ */
public void removeListener(EventListener listener) { public void removeListener(Object listener) {
Objects.requireNonNull(listener); Objects.requireNonNull(listener);
logger.log(Level.INFO, "Removing event listener {0}", listener.getClass().getName()); logger.log(Level.INFO, "Removing event listener {0}", listener.getClass().getName());
@ -204,7 +204,7 @@ public final class EventBus {
* @return all registered event listeners * @return all registered event listeners
* @since 0.0.1 * @since 0.0.1
*/ */
public Set<EventListener> getRegisteredListeners() { public Set<Object> getRegisteredListeners() {
return Collections.unmodifiableSet(registeredListeners); return Collections.unmodifiableSet(registeredListeners);
} }
} }

View File

@ -21,12 +21,12 @@ final class EventHandler implements Comparable<EventHandler> {
*/ */
public static final int DEFAULT_PRIORITY = 100; public static final int DEFAULT_PRIORITY = 100;
private final EventListener listener; private final Object listener;
private final Method method; private final Method method;
private final Class<? extends IEvent> eventType; private final Class<?> eventType;
private final boolean useParameter; private final boolean useParameter;
private final boolean polymorphic; private final boolean polymorphic;
private final int priority; private final int priority;
/** /**
* Constructs an event handler. * Constructs an event handler.
@ -38,8 +38,7 @@ final class EventHandler implements Comparable<EventHandler> {
* specification * specification
* @since 0.0.1 * @since 0.0.1
*/ */
@SuppressWarnings("unchecked") EventHandler(Object listener, Method method, Event annotation) throws EventBusException {
EventHandler(EventListener listener, Method method, Event annotation) 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;
@ -57,17 +56,8 @@ final class EventHandler implements Comparable<EventHandler> {
if (!method.getReturnType().equals(void.class)) if (!method.getReturnType().equals(void.class))
throw new EventBusException(method + " does not have a return type of void!"); throw new EventBusException(method + " does not have a return type of void!");
// Determine the event type // Determine handler properties
if (useParameter) { eventType = useParameter ? method.getParameterTypes()[0] : annotation.value();
var param = method.getParameterTypes()[0];
if (!IEvent.class.isAssignableFrom(param))
throw new EventBusException(param + " is not of type IEvent!");
eventType = (Class<? extends IEvent>) param;
} else {
eventType = annotation.value();
}
// Determine additional handler properties
polymorphic = method.isAnnotationPresent(Polymorphic.class); polymorphic = method.isAnnotationPresent(Polymorphic.class);
priority = method.isAnnotationPresent(Priority.class) priority = method.isAnnotationPresent(Priority.class)
? method.getAnnotation(Priority.class).value() ? method.getAnnotation(Priority.class).value()
@ -107,7 +97,7 @@ final class EventHandler implements Comparable<EventHandler> {
* @throws EventBusException if the handler throws an exception * @throws EventBusException if the handler throws an exception
* @since 0.0.1 * @since 0.0.1
*/ */
void execute(IEvent event) throws EventBusException { void execute(Object event) throws EventBusException {
try { try {
if (useParameter) if (useParameter)
method.invoke(listener, event); method.invoke(listener, event);
@ -122,13 +112,13 @@ final class EventHandler implements Comparable<EventHandler> {
* @return the listener containing this handler * @return the listener containing this handler
* @since 0.0.1 * @since 0.0.1
*/ */
EventListener getListener() { return listener; } Object getListener() { return listener; }
/** /**
* @return the event type this handler listens for * @return the event type this handler listens for
* @since 0.0.3 * @since 0.0.3
*/ */
Class<? extends IEvent> getEventType() { return eventType; } Class<?> getEventType() { return eventType; }
/** /**
* @return the priority of this handler * @return the priority of this handler

View File

@ -1,12 +0,0 @@
package dev.kske.eventbus.core;
/**
* Marker interface for event listeners. Event listeners can contain event handling methods to which
* events can be dispatched.
*
* @author Kai S. K. Engelbart
* @since 0.0.1
* @see Event
* @see EventBus
*/
public interface EventListener {}

View File

@ -1,12 +0,0 @@
package dev.kske.eventbus.core;
/**
* Marker interface for event objects. Event objects can be used as event handler parameters and
* thus can be dispatched to the event bus.
*
* @author Kai S. K. Engelbart
* @since 0.0.1
* @see Event
* @see EventBus
*/
public interface IEvent {}

View File

@ -11,7 +11,7 @@ import org.junit.jupiter.api.*;
* @author Leon Hofmeister * @author Leon Hofmeister
* @since 0.1.0 * @since 0.1.0
*/ */
class CancelTest implements EventListener { class CancelTest {
EventBus bus; EventBus bus;
int hits; int hits;

View File

@ -10,7 +10,7 @@ import org.junit.jupiter.api.*;
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @since 0.0.1 * @since 0.0.1
*/ */
class DispatchTest implements EventListener { class DispatchTest {
EventBus bus; EventBus bus;
static int hits; static int hits;
@ -27,7 +27,7 @@ class DispatchTest implements EventListener {
} }
/** /**
* Tests {@link EventBus#dispatch(IEvent)} with multiple handler priorities, a subtype handler * Tests {@link EventBus#dispatch(Object)} with multiple handler priorities, a subtype handler
* and a static handler. * and a static handler.
* *
* @since 0.0.1 * @since 0.0.1

View File

@ -6,4 +6,4 @@ package dev.kske.eventbus.core;
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @since 0.0.1 * @since 0.0.1
*/ */
public class SimpleEvent implements IEvent {} public class SimpleEvent {}