From 7a3debe444ed54704aff402ad8decfbec8cc0d02 Mon Sep 17 00:00:00 2001 From: kske Date: Tue, 8 Sep 2020 19:47:21 +0200 Subject: [PATCH] Support parameter-less event handlers - Add eventType value to Event - Move semantic event handler checks to EventHandler - Use Objects#requireNonNull(T) on public API method parameters - Update README with a parameter-less event handlers section --- README.md | 20 ++++++++- src/main/java/dev/kske/eventbus/Event.java | 24 +++++++++- src/main/java/dev/kske/eventbus/EventBus.java | 28 +++++------- .../java/dev/kske/eventbus/EventHandler.java | 44 ++++++++++++++++++- .../java/dev/kske/eventbus/EventBusTest.java | 4 +- 5 files changed, 95 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 78b8268..3345abb 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ 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. 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). Additionally, the class containing the method must implement the `EventListener` interface. ## A Simple Example @@ -47,7 +48,22 @@ public class SimpleEventListener implements EventListener { } ``` -In this case, an event bus is created and used locally. In a more sophisticated example the class would acquire an external event bus that is used by multiple classes. +In this case, an event bus is created and used locally. +In a more sophisticated example the class would acquire an external event bus that is used by multiple classes. + +## 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: + +```java +@Event(eventType = SimpleEvent.class) +private void onSimpleEvent() { + System.out.println("SimpleEvent received!"); +} +``` + +Make sure that you **do not** declare both a parameter and the `eventType` value of the annotation, as this would be ambiguous. ## Installation @@ -66,7 +82,7 @@ To include it inside your project, just add the Maven repository and the depende dev.kske event-bus - 0.0.2 + 0.0.3 ``` diff --git a/src/main/java/dev/kske/eventbus/Event.java b/src/main/java/dev/kske/eventbus/Event.java index b67fdc0..dc208af 100644 --- a/src/main/java/dev/kske/eventbus/Event.java +++ b/src/main/java/dev/kske/eventbus/Event.java @@ -10,7 +10,11 @@ import java.lang.annotation.*; * comply with the following specifications: * * @@ -31,4 +35,22 @@ public @interface Event { * @since 0.0.1 */ int priority() default 100; + + /** + * Defines the event type the handler listens to. If this value is set, the handler is not + * allowed to declare parameters. + *

+ * This is useful when the event handler does not utilize the event instance. + * + * @since 0.0.3 + */ + Class eventType() 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 implements IEvent {} } diff --git a/src/main/java/dev/kske/eventbus/EventBus.java b/src/main/java/dev/kske/eventbus/EventBus.java index f4f54f5..cdb93af 100644 --- a/src/main/java/dev/kske/eventbus/EventBus.java +++ b/src/main/java/dev/kske/eventbus/EventBus.java @@ -43,6 +43,7 @@ public final class EventBus { * @since 0.0.1 */ public void dispatch(IEvent event) { + Objects.requireNonNull(event); getHandlersFor(event.getClass()).forEach(handler -> handler.execute(event)); } @@ -63,11 +64,12 @@ public final class EventBus { * * @param listener the listener to register * @throws EventBusException if the listener is already registered or a declared event handler - * does not comply to the specification + * does not comply with the specification * @since 0.0.1 * @see Event */ public void registerListener(EventListener listener) throws EventBusException { + Objects.requireNonNull(listener); if (registeredListeners.contains(listener)) throw new EventBusException(listener + " already registered!"); @@ -79,23 +81,12 @@ public final class EventBus { if (annotation == null) continue; - // Check for correct method signature and return type - if (method.getParameterCount() != 1) - throw new EventBusException(method + " does not have an argument count of 1!"); - - if (!method.getReturnType().equals(void.class)) - throw new EventBusException(method + " does not have a return type of void!"); - - var param = method.getParameterTypes()[0]; - if (!IEvent.class.isAssignableFrom(param)) - throw new EventBusException(param + " is not of type IEvent!"); - - @SuppressWarnings("unchecked") - var realParam = (Class) param; - if (!bindings.containsKey(realParam)) - bindings.put(realParam, new TreeSet<>()); - - bindings.get(realParam).add(new EventHandler(listener, method, annotation)); + // Initialize and bind the handler + var handler = new EventHandler(listener, method, annotation); + if (!bindings.containsKey(handler.getEventType())) + bindings.put(handler.getEventType(), new TreeSet<>()); + bindings.get(handler.getEventType()) + .add(handler); } } @@ -106,6 +97,7 @@ public final class EventBus { * @since 0.0.1 */ public void removeListener(EventListener listener) { + Objects.requireNonNull(listener); for (var binding : bindings.values()) { var it = binding.iterator(); while (it.hasNext()) diff --git a/src/main/java/dev/kske/eventbus/EventHandler.java b/src/main/java/dev/kske/eventbus/EventHandler.java index f434dfa..f4aa355 100644 --- a/src/main/java/dev/kske/eventbus/EventHandler.java +++ b/src/main/java/dev/kske/eventbus/EventHandler.java @@ -2,6 +2,8 @@ package dev.kske.eventbus; import java.lang.reflect.*; +import dev.kske.eventbus.Event.USE_PARAMETER; + /** * Internal representation of an event handling method. * @@ -14,6 +16,7 @@ final class EventHandler implements Comparable { private final EventListener listener; private final Method method; private final Event annotation; + private final Class eventType; /** * Constructs an event handler. @@ -21,12 +24,40 @@ final class EventHandler implements Comparable { * @param listener the listener containing the handler * @param method the handler method * @param annotation the event annotation + * @throws EventBusException if the method or the annotation do not comply with the + * specification * @since 0.0.1 */ - EventHandler(EventListener listener, Method method, Event annotation) { + @SuppressWarnings("unchecked") + EventHandler(EventListener listener, Method method, Event annotation) throws EventBusException { 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)) + throw new EventBusException(method + " does not define an event type!"); + + if (method.getParameterCount() == 1 && !annotation.eventType().equals(USE_PARAMETER.class)) + throw new EventBusException(method + " defines an ambiguous event type!"); + + if (method.getParameterCount() > 1) + throw new EventBusException(method + " defines more than one parameter!"); + + if (!method.getReturnType().equals(void.class)) + throw new EventBusException(method + " does not have a return type of void!"); + + // Determine the event type + Class eventType = annotation.eventType(); + if (eventType.equals(USE_PARAMETER.class)) { + var param = method.getParameterTypes()[0]; + if (!IEvent.class.isAssignableFrom(param)) + throw new EventBusException(param + " is not of type IEvent!"); + eventType = (Class) param; + } + this.eventType = eventType; + + // Allow access if the method is non-public method.setAccessible(true); } @@ -55,7 +86,10 @@ final class EventHandler implements Comparable { */ void execute(IEvent event) throws EventBusException { try { - method.invoke(listener, event); + if (annotation.eventType().equals(USE_PARAMETER.class)) + method.invoke(listener, event); + else + method.invoke(listener); } catch ( IllegalAccessException | IllegalArgumentException @@ -82,4 +116,10 @@ final class EventHandler implements Comparable { * @since 0.0.1 */ int getPriority() { return annotation.priority(); } + + /** + * @return the event type this handler listens to + * @since 0.0.3 + */ + Class getEventType() { return eventType; } } diff --git a/src/test/java/dev/kske/eventbus/EventBusTest.java b/src/test/java/dev/kske/eventbus/EventBusTest.java index 5e58787..adb2d57 100644 --- a/src/test/java/dev/kske/eventbus/EventBusTest.java +++ b/src/test/java/dev/kske/eventbus/EventBusTest.java @@ -30,8 +30,8 @@ class EventBusTest implements EventListener { assertEquals(2, hits); } - @Event(priority = 150) - private void onSimpleEventFirst(SimpleEvent event) { + @Event(eventType = SimpleEvent.class, priority = 150) + private void onSimpleEventFirst() { ++hits; assertEquals(1, hits); }