diff --git a/README.md b/README.md index 1bd8862..62ed2c8 100644 --- a/README.md +++ b/README.md @@ -55,21 +55,23 @@ Note that creating static event handlers like this ```java @Event -private static void onSimpleEvent(SimpleEvent event) ... +private static void onSimpleEvent(SimpleEvent event) { ... } ``` is technically possible, however you would still have to create an instance of the event listener to register it at an event bus. -## Event handlers for subtypes +## Polymorphic Event Handlers On certain occasions it's practical for an event handler to accept both events of the specified type, as well as subclasses of that event. -To include subtypes for an event handler, use the `includeSubtypes` parameter as follows: +To include subtypes for an event handler, use the `@Polymorphic` annotation in addition to `@Event`: ```java -@Event(includeSubtypes = true) +@Event +@Polymorphic +private void onSimpleEvent(SimpleEvent event) { ... } ``` -## Event handler execution order +## Event Handler Execution Order Sometimes when using multiple handlers for one event, it might be useful to know in which order they will be executed. Event Bus provides a mechanism to ensure the correct propagation of events: the `priority`. @@ -86,7 +88,7 @@ Events are dispatched top-down, meaning the event handler with the highest prior If no priority is set or multiple handlers have the same priority, the order of execution is undefined. -## Parameter-less event handlers +## Parameter-Less Event Handlers In some cases an event handler is not interested in the dispatched event instance. To avoid declaring a useless parameter just to specify the event type of the handler, there is an alternative: @@ -100,7 +102,7 @@ private void onSimpleEvent() { Make sure that you **do not** declare both a parameter and the `eventType` value of the annotation, as this would be ambiguous. -## Event consumption +## Event Consumption In some cases it might be useful to stop the propagation of an event. Event Bus makes this possible with event consumption: @@ -152,7 +154,7 @@ Then, require the Event Bus Core module in your `module-info.java`: requires dev.kske.eventbus.core; ``` -# Compile-Time Error Checking with Event Bus AP +## Compile-Time Error Checking with Event Bus AP 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. diff --git a/event-bus-ap/src/main/java/dev/kske/eventbus/ap/EventProcessor.java b/event-bus-ap/src/main/java/dev/kske/eventbus/ap/EventProcessor.java index 53ad735..9e661a6 100644 --- a/event-bus-ap/src/main/java/dev/kske/eventbus/ap/EventProcessor.java +++ b/event-bus-ap/src/main/java/dev/kske/eventbus/ap/EventProcessor.java @@ -73,8 +73,9 @@ public class EventProcessor extends AbstractProcessor { getTypeMirror(IEvent.class))) error(paramElement, "Parameter must implement IEvent"); - // Check for handlers for abstract types that don't include subtypes - if (!eventAnnotation.includeSubtypes() && paramType.getKind() == TypeKind.DECLARED) { + // Check for handlers for abstract types that aren't polymorphic + if (eventHandler.getAnnotation(Polymorphic.class) == null + && paramType.getKind() == TypeKind.DECLARED) { var declaredElement = ((DeclaredType) paramType).asElement(); if (declaredElement.getKind() == ElementKind.INTERFACE || declaredElement.getModifiers().contains(Modifier.ABSTRACT)) diff --git a/event-bus-core/src/main/java/dev/kske/eventbus/core/Event.java b/event-bus-core/src/main/java/dev/kske/eventbus/core/Event.java index 25ee7a3..1011d89 100644 --- a/event-bus-core/src/main/java/dev/kske/eventbus/core/Event.java +++ b/event-bus-core/src/main/java/dev/kske/eventbus/core/Event.java @@ -21,6 +21,7 @@ import java.lang.annotation.*; * * @author Kai S. K. Engelbart * @since 0.0.1 + * @see Polymorphic */ @Documented @Retention(RUNTIME) @@ -38,14 +39,6 @@ public @interface Event { */ int priority() default 100; - /** - * Defines whether instances of subtypes of the event type are dispatched to the event handler. - * - * @return whether the event handler includes subtypes - * @since 0.0.4 - */ - boolean includeSubtypes() default false; - /** * Defines the event type the handler listens to. If this value is set, the handler is not * allowed to declare parameters. diff --git a/event-bus-core/src/main/java/dev/kske/eventbus/core/EventBus.java b/event-bus-core/src/main/java/dev/kske/eventbus/core/EventBus.java index 2248668..40ed4fa 100644 --- a/event-bus-core/src/main/java/dev/kske/eventbus/core/EventBus.java +++ b/event-bus-core/src/main/java/dev/kske/eventbus/core/EventBus.java @@ -104,7 +104,7 @@ public final class EventBus { for (var binding : bindings.entrySet()) if (binding.getKey().isAssignableFrom(eventClass)) for (var handler : binding.getValue()) - if (handler.includeSubtypes()) + if (handler.isPolymorphic()) handlers.add(handler); return new ArrayList<>(handlers); diff --git a/event-bus-core/src/main/java/dev/kske/eventbus/core/EventHandler.java b/event-bus-core/src/main/java/dev/kske/eventbus/core/EventHandler.java index aae6387..436bf70 100644 --- a/event-bus-core/src/main/java/dev/kske/eventbus/core/EventHandler.java +++ b/event-bus-core/src/main/java/dev/kske/eventbus/core/EventHandler.java @@ -13,10 +13,11 @@ import dev.kske.eventbus.core.Event.USE_PARAMETER; */ final class EventHandler implements Comparable { - private final EventListener listener; - private final Method method; - private final Event annotation; - private final Class eventType; + private final EventListener listener; + private final Method method; + private final Event annotation; + private final Class eventType; + private final boolean polymorphic; /** * Constructs an event handler. @@ -30,9 +31,9 @@ final class EventHandler implements Comparable { */ @SuppressWarnings("unchecked") EventHandler(EventListener listener, Method method, Event annotation) throws EventBusException { - this.listener = listener; - this.method = method; - this.annotation = annotation; + this.listener = listener; + this.method = method; + this.annotation = annotation; // Check for correct method signature and return type if (method.getParameterCount() == 0 && annotation.eventType().equals(USE_PARAMETER.class)) @@ -55,7 +56,8 @@ final class EventHandler implements Comparable { throw new EventBusException(param + " is not of type IEvent!"); eventType = (Class) param; } - this.eventType = eventType; + this.eventType = eventType; + polymorphic = method.isAnnotationPresent(Polymorphic.class); // Allow access if the method is non-public method.setAccessible(true); @@ -65,7 +67,7 @@ final class EventHandler implements Comparable { * Compares this to another event handler based on {@link Event#priority()}. In case of equal * priority a non-zero value based on hash codes is returned. *

- * This is used to retrieve event handlers in the correct order from a tree set. + * This is used to retrieve event handlers in order of descending priority from a tree set. * * @since 0.0.1 */ @@ -91,15 +93,11 @@ final class EventHandler implements Comparable { */ void execute(IEvent event) throws EventBusException { try { - if (annotation.eventType().equals(USE_PARAMETER.class)) + if (annotation.eventType() == USE_PARAMETER.class) method.invoke(listener, event); else method.invoke(listener); - } catch ( - IllegalAccessException - | IllegalArgumentException - | InvocationTargetException e - ) { + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { throw new EventBusException("Failed to invoke event handler!", e); } } @@ -123,10 +121,11 @@ final class EventHandler implements Comparable { int getPriority() { return annotation.priority(); } /** - * @return whether this handler includes subtypes - * @since 0.0.4 + * @return whether this handler is polymorphic + * @since 1.0.0 + * @see Polymorphic */ - boolean includeSubtypes() { return annotation.includeSubtypes(); } + boolean isPolymorphic() { return polymorphic; } /** * @return the event type this handler listens to diff --git a/event-bus-core/src/main/java/dev/kske/eventbus/core/Polymorphic.java b/event-bus-core/src/main/java/dev/kske/eventbus/core/Polymorphic.java new file mode 100644 index 0000000..bbed5d8 --- /dev/null +++ b/event-bus-core/src/main/java/dev/kske/eventbus/core/Polymorphic.java @@ -0,0 +1,20 @@ +package dev.kske.eventbus.core; + +import static java.lang.annotation.ElementType.METHOD; +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. + *

+ * 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) +public @interface Polymorphic {} diff --git a/event-bus-core/src/test/java/dev/kske/eventbus/core/DispatchTest.java b/event-bus-core/src/test/java/dev/kske/eventbus/core/DispatchTest.java index a683bc7..0239a8c 100644 --- a/event-bus-core/src/test/java/dev/kske/eventbus/core/DispatchTest.java +++ b/event-bus-core/src/test/java/dev/kske/eventbus/core/DispatchTest.java @@ -38,7 +38,8 @@ class DispatchTest implements EventListener { bus.dispatch(new SimpleEvent()); } - @Event(eventType = SimpleEvent.class, includeSubtypes = true, priority = 200) + @Event(eventType = SimpleEvent.class, priority = 200) + @Polymorphic void onSimpleEventFirst() { ++hits; assertTrue(hits == 1 || hits == 2);