diff --git a/README.md b/README.md index 01fd4c6..876dc83 100644 --- a/README.md +++ b/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. +## 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 In some cases it might be useful to stop the propagation of an event. 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 95a65a5..45c9ef8 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 @@ -30,6 +30,14 @@ public final class EventBus { 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 final Logger logger = System.getLogger(EventBus.class.getName()); @@ -169,6 +177,16 @@ public final class EventBus { logger.log(Level.INFO, "Registering event listener {0}", listener.getClass().getName()); boolean handlerBound = false; + // Predefined handler polymorphism + boolean polymorphic = false; + if (listener.getClass().isAnnotationPresent(Polymorphic.class)) + polymorphic = listener.getClass().getAnnotation(Polymorphic.class).value(); + + // Predefined handler priority + int priority = DEFAULT_PRIORITY; + if (listener.getClass().isAnnotationPresent(Priority.class)) + priority = listener.getClass().getAnnotation(Priority.class).value(); + registeredListeners.add(listener); for (var method : listener.getClass().getDeclaredMethods()) { Event annotation = method.getAnnotation(Event.class); @@ -178,7 +196,7 @@ public final class EventBus { continue; // 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<>()); logger.log(Level.DEBUG, "Binding event handler {0}", handler); bindings.get(handler.getEventType()) 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 60e7e85..f22ddf0 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,14 +13,6 @@ import dev.kske.eventbus.core.Event.USE_PARAMETER; */ final class EventHandler implements Comparable { - /** - * 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 Method method; private final Class eventType; @@ -31,14 +23,17 @@ final class EventHandler implements Comparable { /** * Constructs an event handler. * - * @param listener the listener containing the handler - * @param method the handler method - * @param annotation the event annotation + * @param listener the listener containing the handler + * @param method the handler method + * @param annotation the event annotation + * @param defPolymorphism the predefined polymorphism (default or listener-level) + * @param defPriority the predefined priority (default or listener-level) * @throws EventBusException if the method or the annotation do not comply with the * specification * @since 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.method = method; useParameter = annotation.value() == USE_PARAMETER.class; @@ -55,10 +50,12 @@ final class EventHandler implements Comparable { // Determine handler properties 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) ? method.getAnnotation(Priority.class).value() - : DEFAULT_PRIORITY; + : defPriority; // Allow access if the method is non-public method.setAccessible(true); @@ -94,6 +91,7 @@ final class EventHandler implements Comparable { * @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 */ void execute(Object event) throws EventBusException, InvocationTargetException { 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 index bbed5d8..1f77726 100644 --- 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 @@ -1,6 +1,6 @@ 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 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. *

+ * 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. + *

* This is useful when defining an event handler for an interface or an abstract class. * * @author Kai S. K. Engelbart @@ -16,5 +19,12 @@ import java.lang.annotation.*; */ @Documented @Retention(RUNTIME) -@Target(METHOD) -public @interface Polymorphic {} +@Target({ METHOD, TYPE }) +public @interface Polymorphic { + + /** + * @return whether the event handler is polymorphic + * @since 1.1.0 + */ + boolean value() default true; +} diff --git a/event-bus-core/src/main/java/dev/kske/eventbus/core/Priority.java b/event-bus-core/src/main/java/dev/kske/eventbus/core/Priority.java index 1218df3..ca82fa3 100644 --- a/event-bus-core/src/main/java/dev/kske/eventbus/core/Priority.java +++ b/event-bus-core/src/main/java/dev/kske/eventbus/core/Priority.java @@ -1,6 +1,6 @@ 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 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 * priority. *

+ * 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. + *

* Handlers without this annotation have the default priority of 100. *

* The execution order of handlers with the same priority is undefined. @@ -19,7 +22,7 @@ import java.lang.annotation.*; */ @Documented @Retention(RUNTIME) -@Target(METHOD) +@Target({ METHOD, TYPE }) public @interface Priority { /** 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 f8a5f90..5149ae1 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 @@ -10,6 +10,8 @@ import org.junit.jupiter.api.*; * @author Kai S. K. Engelbart * @since 0.0.1 */ +@Polymorphic +@Priority(150) class DispatchTest { EventBus bus; @@ -40,20 +42,21 @@ class DispatchTest { @Event(SimpleEvent.class) @Priority(200) - @Polymorphic void onSimpleEventFirst() { ++hits; assertTrue(hits == 1 || hits == 2); } @Event(SimpleEvent.class) - @Priority(150) + @Polymorphic(false) static void onSimpleEventSecond() { ++hits; assertEquals(3, hits); } @Event + @Polymorphic(false) + @Priority(100) void onSimpleEventThird(SimpleEvent event) { ++hits; assertEquals(4, hits); diff --git a/event-bus-proc/src/main/java/dev/kske/eventbus/proc/EventProcessor.java b/event-bus-proc/src/main/java/dev/kske/eventbus/proc/EventProcessor.java index fed6f1c..fd0567b 100644 --- a/event-bus-proc/src/main/java/dev/kske/eventbus/proc/EventProcessor.java +++ b/event-bus-proc/src/main/java/dev/kske/eventbus/proc/EventProcessor.java @@ -65,7 +65,7 @@ public class EventProcessor extends AbstractProcessor { // Warn the user about unused return values if (useParameter && eventHandler.getReturnType().getKind() != TypeKind.VOID) - warning(eventHandler, "Unused return value"); + warning(eventHandler, "Unused return value"); // Abort checking if the handler signature is incorrect if (!pass) @@ -84,9 +84,45 @@ public class EventProcessor extends AbstractProcessor { } } + // 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 - boolean polymorphic = eventHandler.getAnnotation(Polymorphic.class) != null; - Element eventElement = ((DeclaredType) eventType).asElement(); + Element eventElement = ((DeclaredType) eventType).asElement(); // Check for handlers for abstract types that aren't polymorphic if (!polymorphic && (eventElement.getKind() == ElementKind.INTERFACE