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 6ef1dd9..f194c56 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 @@ -22,7 +22,7 @@ import java.lang.annotation.*; public @interface Event { /** - * Defines the event type the handler listens to. If this value is set, the handler is not + * Defines the event type the handler listens for. 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. 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 b49f353..42060b7 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 @@ -5,6 +5,9 @@ import java.lang.System.Logger.Level; import java.lang.reflect.InvocationTargetException; import java.util.*; import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Consumer; + +import dev.kske.eventbus.core.handler.*; /** * Event listeners can be registered at an event bus to be notified when an event is dispatched. @@ -56,6 +59,19 @@ public final class EventBus { private static final Logger logger = System.getLogger(EventBus.class.getName()); + /** + * Compares event handlers based on priority, but uses hash codes for equal priorities. + * + * @implNote As the priority comparator by itself is not consistent with equals (two handlers + * with the same priority are not necessarily equal, but would have a comparison + * result of 0), the hash code is used for the fallback comparison. This way, + * consistency with equals is restored. + * @since 1.2.0 + */ + private static final Comparator byPriority = + Comparator.comparingInt(EventHandler::getPriority).reversed() + .thenComparingInt(EventHandler::hashCode); + /** * Returns the default event bus, which is a statically initialized singleton instance. * @@ -154,18 +170,19 @@ 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. * - * @param eventClass the event class to use for the search + * @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 getHandlersFor(Class eventClass) { + private NavigableSet getHandlersFor(Class eventType) { // Get handlers defined for the event class - TreeSet handlers = bindings.getOrDefault(eventClass, new TreeSet<>()); + TreeSet handlers = + bindings.getOrDefault(eventType, new TreeSet<>(byPriority)); // Get polymorphic handlers for (var binding : bindings.entrySet()) - if (binding.getKey().isAssignableFrom(eventClass)) + if (binding.getKey().isAssignableFrom(eventType)) for (var handler : binding.getValue()) if (handler.isPolymorphic()) handlers.add(handler); @@ -223,11 +240,8 @@ public final class EventBus { continue; // Initialize and bind the handler - 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()) - .add(handler); + bindHandler( + new ReflectiveEventHandler(listener, method, annotation, polymorphic, priority)); handlerBound = true; } @@ -238,6 +252,86 @@ public final class EventBus { listener.getClass().getName()); } + /** + * 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}. + * + * @param the event type the listener listens for + * @param eventType the event type the listener listens for + * @param eventListener the callback that is invoked when an event occurs + * @since 1.2.0 + * @see #registerListener(Class, Consumer, boolean, int) + */ + public void registerListener(Class eventType, Consumer eventListener) { + registerListener(eventType, eventListener, false, DEFAULT_PRIORITY); + } + + /** + * Registers a callback listener, which is a consumer that is invoked when an event occurs. The + * listener has the {@link #DEFAULT_PRIORITY}. + * + * @param the event type the listener listens for + * @param eventType the event type the listener listens for + * @param eventListener the callback that is invoked when an event occurs + * @param polymorphic whether the listener is also invoked for subtypes of the event type + * @since 1.2.0 + * @see #registerListener(Class, Consumer, boolean, int) + */ + public void registerListener(Class eventType, Consumer eventListener, + boolean polymorphic) { + registerListener(eventType, eventListener, polymorphic, DEFAULT_PRIORITY); + } + + /** + * Registers a callback listener, which is a consumer that is invoked when an event occurs. The + * listener is not polymorphic. + * + * @param the event type the listener listens for + * @param eventType the event type the listener listens for + * @param eventListener the callback that is invoked when an event occurs + * @param priority the priority to assign to the listener + * @since 1.2.0 + * @see #registerListener(Class, Consumer, boolean, int) + */ + public void registerListener(Class eventType, Consumer eventListener, int priority) { + registerListener(eventType, eventListener, false, priority); + } + + /** + * Registers a callback listener, which is a consumer that is invoked when an event occurs. + * + * @param the event type the listener listens for + * @param eventType the event type the listener listens for + * @param eventListener the callback that is invoked when an event occurs + * @param polymorphic whether the listener is also invoked for subtypes of the event type + * @param priority the priority to assign to the listener + * @since 1.2.0 + */ + public void registerListener(Class eventType, Consumer eventListener, + boolean polymorphic, + int priority) { + Objects.requireNonNull(eventListener); + if (registeredListeners.contains(eventListener)) + throw new EventBusException(eventListener + " already registered!"); + logger.log(Level.INFO, "Registering callback event listener {0}", + eventListener.getClass().getName()); + + registeredListeners.add(eventListener); + bindHandler(new CallbackEventHandler(eventType, eventListener, polymorphic, priority)); + } + + /** + * Inserts a new handler into the {@link #bindings} map. + * + * @param handler the handler to bind + * @since 1.2.0 + */ + private void bindHandler(EventHandler handler) { + bindings.putIfAbsent(handler.getEventType(), new TreeSet<>(byPriority)); + logger.log(Level.DEBUG, "Binding event handler {0}", handler); + bindings.get(handler.getEventType()).add(handler); + } + /** * Removes a specific listener from this event bus. * diff --git a/event-bus-core/src/main/java/dev/kske/eventbus/core/handler/CallbackEventHandler.java b/event-bus-core/src/main/java/dev/kske/eventbus/core/handler/CallbackEventHandler.java new file mode 100644 index 0000000..faf5a6c --- /dev/null +++ b/event-bus-core/src/main/java/dev/kske/eventbus/core/handler/CallbackEventHandler.java @@ -0,0 +1,73 @@ +package dev.kske.eventbus.core.handler; + +import java.lang.reflect.InvocationTargetException; +import java.util.function.Consumer; + +/** + * An event handler wrapping a callback method. + * + * @author Kai S. K. Engelbart + * @since 1.2.0 + */ +public final class CallbackEventHandler implements EventHandler { + + private final Class eventType; + private final Consumer callback; + private final boolean polymorphic; + private final int priority; + + /** + * Constructs a callback event handler. + * + * @param the event type of the handler + * @param eventType the event type of the handler + * @param callback the callback method to execute when the handler is invoked + * @param polymorphic whether the handler is polymorphic + * @param priority the priority of the handler + * @since 1.2.0 + */ + @SuppressWarnings("unchecked") + public CallbackEventHandler(Class eventType, Consumer callback, boolean polymorphic, + int priority) { + this.eventType = eventType; + this.callback = (Consumer) callback; + this.polymorphic = polymorphic; + this.priority = priority; + } + + @Override + public void execute(Object event) throws InvocationTargetException { + try { + callback.accept(event); + } catch (RuntimeException e) { + throw new InvocationTargetException(e, "Callback event handler failed!"); + } + } + + @Override + public String toString() { + return String.format( + "CallbackEventHandler[eventType=%s, polymorphic=%b, priority=%d]", + eventType, polymorphic, priority); + } + + @Override + public Consumer getListener() { + return callback; + } + + @Override + public Class getEventType() { + return eventType; + } + + @Override + public int getPriority() { + return priority; + } + + @Override + public boolean isPolymorphic() { + return polymorphic; + } +} diff --git a/event-bus-core/src/main/java/dev/kske/eventbus/core/handler/EventHandler.java b/event-bus-core/src/main/java/dev/kske/eventbus/core/handler/EventHandler.java new file mode 100644 index 0000000..30147a6 --- /dev/null +++ b/event-bus-core/src/main/java/dev/kske/eventbus/core/handler/EventHandler.java @@ -0,0 +1,53 @@ +package dev.kske.eventbus.core.handler; + +import java.lang.reflect.InvocationTargetException; + +import dev.kske.eventbus.core.*; + +/** + * Internal representation of an event handling method. + * + * @author Kai S. K. Engelbart + * @since 1.2.0 + * @see EventBus + */ +public interface EventHandler { + + /** + * Executes the event handler. + * + * @param event the event used as the method parameter + * @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 1.2.0 + */ + void execute(Object event) throws EventBusException, InvocationTargetException; + + /** + * @return the listener containing this handler + * @since 1.2.0 + */ + Object getListener(); + + /** + * @return the event type this handler listens for + * @since 1.2.0 + */ + Class getEventType(); + + /** + * @return the priority of this handler + * @since 1.2.0 + * @see Priority + */ + int getPriority(); + + /** + * @return whether this handler also accepts subtypes of the event type + * @since 1.2.0 + * @see Polymorphic + */ + boolean isPolymorphic(); +} 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/handler/ReflectiveEventHandler.java similarity index 50% rename from event-bus-core/src/main/java/dev/kske/eventbus/core/EventHandler.java rename to event-bus-core/src/main/java/dev/kske/eventbus/core/handler/ReflectiveEventHandler.java index f22ddf0..22ecea5 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/handler/ReflectiveEventHandler.java @@ -1,17 +1,18 @@ -package dev.kske.eventbus.core; +package dev.kske.eventbus.core.handler; import java.lang.reflect.*; +import dev.kske.eventbus.core.*; import dev.kske.eventbus.core.Event.USE_PARAMETER; /** - * Internal representation of an event handling method. + * An event handler wrapping a method annotated with {@link Event} and executing it using + * reflection. * * @author Kai S. K. Engelbart - * @since 0.0.1 - * @see EventBus + * @since 1.2.0 */ -final class EventHandler implements Comparable { +public final class ReflectiveEventHandler implements EventHandler { private final Object listener; private final Method method; @@ -21,7 +22,7 @@ final class EventHandler implements Comparable { private final int priority; /** - * Constructs an event handler. + * Constructs a reflective event handler. * * @param listener the listener containing the handler * @param method the handler method @@ -30,10 +31,10 @@ final class EventHandler implements Comparable { * @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 + * @since 1.2.0 */ - EventHandler(Object listener, Method method, Event annotation, boolean defPolymorphism, - int defPriority) throws EventBusException { + public ReflectiveEventHandler(Object listener, Method method, Event annotation, + boolean defPolymorphism, int defPriority) throws EventBusException { this.listener = listener; this.method = method; useParameter = annotation.value() == USE_PARAMETER.class; @@ -61,45 +62,13 @@ final class EventHandler implements Comparable { method.setAccessible(true); } - /** - * Compares this to another event handler based on priority. In case of equal priority a - * non-zero value based on hash codes is returned. - *

- * This is used to retrieve event handlers in descending order of priority from a tree set. - * - * @since 0.0.1 - */ @Override - public int compareTo(EventHandler other) { - int priority = other.priority - this.priority; - if (priority == 0) - priority = listener.hashCode() - other.listener.hashCode(); - return priority == 0 ? hashCode() - other.hashCode() : priority; - } - - @Override - public String toString() { - return String.format( - "EventHandler[method=%s, eventType=%s, useParameter=%b, polymorphic=%b, priority=%d]", - method, eventType, useParameter, polymorphic, priority); - } - - /** - * Executes the event handler. - * - * @param event the event used as the method parameter - * @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 { + public void execute(Object event) throws EventBusException, InvocationTargetException { try { if (useParameter) - method.invoke(listener, event); + method.invoke(getListener(), event); else - method.invoke(listener); + method.invoke(getListener()); } catch (IllegalArgumentException e) { throw new EventBusException("Event handler rejected target / argument!", e); } catch (IllegalAccessException e) { @@ -107,29 +76,30 @@ final class EventHandler implements Comparable { } } - /** - * @return the listener containing this handler - * @since 0.0.1 - */ - Object getListener() { return listener; } + @Override + public String toString() { + return String.format( + "ReflectiveEventHandler[eventType=%s, polymorphic=%b, priority=%d, method=%s, useParameter=%b]", + eventType, polymorphic, priority, method, useParameter); + } - /** - * @return the event type this handler listens for - * @since 0.0.3 - */ - Class getEventType() { return eventType; } + @Override + public Object getListener() { + return listener; + } - /** - * @return the priority of this handler - * @since 0.0.1 - * @see Priority - */ - int getPriority() { return priority; } + @Override + public Class getEventType() { + return eventType; + } - /** - * @return whether this handler is polymorphic - * @since 1.0.0 - * @see Polymorphic - */ - boolean isPolymorphic() { return polymorphic; } + @Override + public int getPriority() { + return priority; + } + + @Override + public boolean isPolymorphic() { + return polymorphic; + } } diff --git a/event-bus-core/src/main/java/dev/kske/eventbus/core/handler/package-info.java b/event-bus-core/src/main/java/dev/kske/eventbus/core/handler/package-info.java new file mode 100644 index 0000000..fa3165a --- /dev/null +++ b/event-bus-core/src/main/java/dev/kske/eventbus/core/handler/package-info.java @@ -0,0 +1,8 @@ +/** + * Contains the internal representation of event handling methods. + * + * @author Kai S. K. Engelbart + * @since 1.2.0 + * @see dev.kske.eventbus.core.handler.EventHandler + */ +package dev.kske.eventbus.core.handler; 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 5ba883e..250fc26 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 @@ -26,6 +26,10 @@ class DispatchTest { void registerListener() { bus = new EventBus(); bus.registerListener(this); + bus.registerListener(SimpleEvent.class, e -> { + ++hits; + assertEquals(4, hits); + }); } /** @@ -52,9 +56,9 @@ class DispatchTest { assertEquals( "Event handler execution order for class dev.kske.eventbus.core.SimpleEvent (3 handler(s)):\n" + "==========================================================================================\n" - + "EventHandler[method=void dev.kske.eventbus.core.DispatchTest.onSimpleEventFirst(), eventType=class dev.kske.eventbus.core.SimpleEvent, useParameter=false, polymorphic=true, priority=200]\n" - + "EventHandler[method=static void dev.kske.eventbus.core.DispatchTest.onSimpleEventSecond(), eventType=class dev.kske.eventbus.core.SimpleEvent, useParameter=false, polymorphic=false, priority=150]\n" - + "EventHandler[method=void dev.kske.eventbus.core.DispatchTest.onSimpleEventThird(dev.kske.eventbus.core.SimpleEvent), eventType=class dev.kske.eventbus.core.SimpleEvent, useParameter=true, polymorphic=false, priority=100]\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" + + "CallbackEventHandler[eventType=class dev.kske.eventbus.core.SimpleEvent, polymorphic=false, priority=100]\n" + "==========================================================================================", executionOrder); } @@ -72,12 +76,4 @@ class DispatchTest { ++hits; assertEquals(3, hits); } - - @Event - @Polymorphic(false) - @Priority(100) - void onSimpleEventThird(SimpleEvent event) { - ++hits; - assertEquals(4, hits); - } }