From 0036dc4829963449f22d966fbf2ad8ff4b4ae7cf Mon Sep 17 00:00:00 2001 From: kske Date: Fri, 19 Feb 2021 16:04:49 +0100 Subject: [PATCH 1/2] Add DeadEvent A dead events wraps an event that was dispatched but not delivered to any handler. The dead event is than dispatched to dedicated handlers. --- .../dev/kske/eventbus/core/DeadEvent.java | 32 ++++++++++++++++ .../java/dev/kske/eventbus/core/EventBus.java | 34 ++++++++++------- .../dev/kske/eventbus/core/EventHandler.java | 2 +- .../java/dev/kske/eventbus/core/DeadTest.java | 37 +++++++++++++++++++ 4 files changed, 90 insertions(+), 15 deletions(-) create mode 100644 event-bus-core/src/main/java/dev/kske/eventbus/core/DeadEvent.java create mode 100644 event-bus-core/src/test/java/dev/kske/eventbus/core/DeadTest.java 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..0c93b9a --- /dev/null +++ b/event-bus-core/src/main/java/dev/kske/eventbus/core/DeadEvent.java @@ -0,0 +1,32 @@ +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; + } + + /** + * @return the event bus that originated 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..f0c065d 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,19 @@ 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(new DeadEvent(this, event)); + } // Reset dispatch state state.isDispatching = false; @@ -89,25 +94,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..94e925d --- /dev/null +++ b/event-bus-core/src/test/java/dev/kske/eventbus/core/DeadTest.java @@ -0,0 +1,37 @@ +package dev.kske.eventbus.core; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.*; + +/** + * 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; + String event = "This event has no handler"; + boolean deadEventHandled; + + @BeforeEach + void registerListener() { + bus = new EventBus(); + bus.registerListener(this); + } + + @Test + void testDeadEvent() { + bus.dispatch(event); + assertTrue(deadEventHandled); + } + + @Event + void onDeadEvent(DeadEvent deadEvent) { + assertEquals(bus, deadEvent.getEventBus()); + assertEquals(event, deadEvent.getEvent()); + deadEventHandled = true; + } +} -- 2.30.2 From b2fe3a9d6ce7080562531eec3ebe97ef183b07db Mon Sep 17 00:00:00 2001 From: kske Date: Sat, 20 Feb 2021 21:40:48 +0100 Subject: [PATCH 2/2] Log unhandled dead events --- .../dev/kske/eventbus/core/DeadEvent.java | 7 ++++- .../java/dev/kske/eventbus/core/EventBus.java | 6 ++++ .../java/dev/kske/eventbus/core/DeadTest.java | 28 +++++++++++++------ .../dev/kske/eventbus/core/DispatchTest.java | 4 +-- 4 files changed, 34 insertions(+), 11 deletions(-) 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 index 0c93b9a..ad0feb8 100644 --- 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 @@ -18,8 +18,13 @@ public final class DeadEvent { this.event = event; } + @Override + public String toString() { + return String.format("DeadEvent[eventBus=%s, event=%s]", eventBus, event); + } + /** - * @return the event bus that originated this event + * @return the event bus that dispatched this event * @since 1.1.0 */ public EventBus getEventBus() { return eventBus; } 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 f0c065d..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 @@ -84,7 +84,13 @@ public final class EventBus { 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 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 index 94e925d..50e1986 100644 --- 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 @@ -2,7 +2,7 @@ package dev.kske.eventbus.core; import static org.junit.jupiter.api.Assertions.*; -import org.junit.jupiter.api.*; +import org.junit.jupiter.api.Test; /** * Tests the dispatching of a dead event if an event could not be delivered. @@ -12,20 +12,32 @@ import org.junit.jupiter.api.*; */ class DeadTest { - EventBus bus; + EventBus bus = new EventBus(); String event = "This event has no handler"; boolean deadEventHandled; - @BeforeEach - void registerListener() { - bus = new EventBus(); - bus.registerListener(this); - } - + /** + * 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 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 */ -- 2.30.2