diff --git a/event-bus-core/src/main/java/dev/kske/eventbus/core/DeadEvent.java b/event-bus-core/src/main/java/dev/kske/eventbus/core/DeadEvent.java new file mode 100644 index 0000000..ad0feb8 --- /dev/null +++ b/event-bus-core/src/main/java/dev/kske/eventbus/core/DeadEvent.java @@ -0,0 +1,37 @@ +package dev.kske.eventbus.core; + +/** + * Wraps an event that was dispatched but for which no handler has been bound. + *

+ * Handling dead events is useful as it can identify a poorly configured event distribution. + * + * @author Kai S. K. Engelbart + * @since 1.1.0 + */ +public final class DeadEvent { + + private final EventBus eventBus; + private final Object event; + + DeadEvent(EventBus eventBus, Object event) { + this.eventBus = eventBus; + this.event = event; + } + + @Override + public String toString() { + return String.format("DeadEvent[eventBus=%s, event=%s]", eventBus, event); + } + + /** + * @return the event bus that dispatched this event + * @since 1.1.0 + */ + public EventBus getEventBus() { return eventBus; } + + /** + * @return the event that could not be delivered + * @since 1.1.0 + */ + public Object getEvent() { return 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 8f30e89..88317c6 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 @@ -73,14 +73,25 @@ public final class EventBus { var state = dispatchState.get(); state.isDispatching = true; - for (var handler : getHandlersFor(event.getClass())) - if (state.isCancelled) { - logger.log(Level.INFO, "Cancelled dispatching event {0}", event); - state.isCancelled = false; - break; - } else { - handler.execute(event); - } + Iterator handlers = getHandlersFor(event.getClass()); + if (handlers.hasNext()) { + while (handlers.hasNext()) + if (state.isCancelled) { + logger.log(Level.INFO, "Cancelled dispatching event {0}", event); + state.isCancelled = false; + break; + } else { + handlers.next().execute(event); + } + } else if (!(event instanceof DeadEvent)) { + + // Dispatch dead event + dispatch(new DeadEvent(this, event)); + } else { + + // Warn about the dead event not being handled + logger.log(Level.WARNING, "{0} not handled", event); + } // Reset dispatch state state.isDispatching = false; @@ -89,25 +100,26 @@ public final class EventBus { } /** - * Searches for the event handlers bound to an event class. + * 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 - * @return all event handlers registered for the event class + * @return an iterator over the applicable handlers in descending order of priority * @since 0.0.1 */ - private List getHandlersFor(Class eventClass) { + private Iterator getHandlersFor(Class eventClass) { // Get handlers defined for the event class - Set handlers = bindings.getOrDefault(eventClass, new TreeSet<>()); + TreeSet handlers = bindings.getOrDefault(eventClass, new TreeSet<>()); - // Get subtype handlers + // Get polymorphic handlers for (var binding : bindings.entrySet()) if (binding.getKey().isAssignableFrom(eventClass)) for (var handler : binding.getValue()) if (handler.isPolymorphic()) handlers.add(handler); - return new ArrayList<>(handlers); + return handlers.iterator(); } /** 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 91c2078..4c89174 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 @@ -68,7 +68,7 @@ final class EventHandler implements Comparable { * 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 order of descending priority from a tree set. + * This is used to retrieve event handlers in descending order of priority from a tree set. * * @since 0.0.1 */ diff --git a/event-bus-core/src/test/java/dev/kske/eventbus/core/DeadTest.java b/event-bus-core/src/test/java/dev/kske/eventbus/core/DeadTest.java new file mode 100644 index 0000000..50e1986 --- /dev/null +++ b/event-bus-core/src/test/java/dev/kske/eventbus/core/DeadTest.java @@ -0,0 +1,49 @@ +package dev.kske.eventbus.core; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +/** + * Tests the dispatching of a dead event if an event could not be delivered. + * + * @author Kai S. K. Engelbart + * @since 1.1.0 + */ +class DeadTest { + + EventBus bus = new EventBus(); + String event = "This event has no handler"; + boolean deadEventHandled; + + /** + * Tests dead event delivery. + * + * @since 1.1.0 + */ + @Test + void testDeadEvent() { + bus.registerListener(this); + bus.dispatch(event); + assertTrue(deadEventHandled); + bus.removeListener(this); + } + + /** + * Tests how the event bus reacts to an unhandled dead event. This should not lead to an + * exception or endless recursion and instead be logged. + * + * @since 1.1.0 + */ + @Test + void testUnhandledDeadEvent() { + bus.dispatch(event); + } + + @Event + void onDeadEvent(DeadEvent deadEvent) { + assertEquals(bus, deadEvent.getEventBus()); + assertEquals(event, deadEvent.getEvent()); + deadEventHandled = true; + } +} 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 95bae3f..f8a5f90 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 @@ -27,8 +27,8 @@ class DispatchTest { } /** - * Tests {@link EventBus#dispatch(Object)} with multiple handler priorities, a subtype handler - * and a static handler. + * Tests {@link EventBus#dispatch(Object)} with multiple handler priorities, a polymorphic + * handler and a static handler. * * @since 0.0.1 */