6 Commits

Author SHA1 Message Date
0f9b64be48 Merge pull request 'Add DeadEvent' (#9) from f/dead-event into develop
Reviewed-on: https://git.kske.dev/kske/event-bus/pulls/9
Reviewed-by: delvh <leon@kske.dev>
Reviewed-by: DieGurke <maxi@kske.dev>
2021-02-21 09:16:32 +01:00
b2fe3a9d6c Log unhandled dead events 2021-02-20 22:10:48 +01:00
9379e6bb94 Merge pull request 'Additional Warnings in Event Bus Proc' (#8) from f/additional-warnings into develop
Reviewed-on: https://git.kske.dev/kske/event-bus/pulls/8
Reviewed-by: delvh <leon@kske.dev>
2021-02-20 21:46:08 +01:00
0036dc4829 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.
2021-02-19 16:05:11 +01:00
8a30493c52 Warn about unused event handler return values
If an event handler has a non-void return type, a warning is issued as
the event bus cannot use the returned value.

In rare cases this might be justified as the event handler could be
invoked directly.
2021-02-19 11:34:58 +01:00
b56f08e441 Warn about unnecessarily polymorphic event handlers
When Event Bus Proc detects a handler for a final type that uses the
@Polymorphic annotation, it issues a warning.
2021-02-19 11:30:09 +01:00
6 changed files with 130 additions and 22 deletions

View File

@ -0,0 +1,37 @@
package dev.kske.eventbus.core;
/**
* Wraps an event that was dispatched but for which no handler has been bound.
* <p>
* 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; }
}

View File

@ -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<EventHandler> 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<EventHandler> getHandlersFor(Class<?> eventClass) {
private Iterator<EventHandler> getHandlersFor(Class<?> eventClass) {
// Get handlers defined for the event class
Set<EventHandler> handlers = bindings.getOrDefault(eventClass, new TreeSet<>());
TreeSet<EventHandler> 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();
}
/**

View File

@ -68,7 +68,7 @@ final class EventHandler implements Comparable<EventHandler> {
* Compares this to another event handler based on priority. In case of equal priority a
* non-zero value based on hash codes is returned.
* <p>
* 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
*/

View File

@ -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;
}
}

View File

@ -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
*/

View File

@ -63,6 +63,10 @@ public class EventProcessor extends AbstractProcessor {
else
pass = true;
// Warn the user about unused return values
if (useParameter && eventHandler.getReturnType().getKind() != TypeKind.VOID)
warning(eventHandler, "Unused return value");
// Abort checking if the handler signature is incorrect
if (!pass)
continue;
@ -80,14 +84,20 @@ public class EventProcessor extends AbstractProcessor {
}
}
// Detect missing or useless @Polymorphic
boolean polymorphic = eventHandler.getAnnotation(Polymorphic.class) != null;
Element eventElement = ((DeclaredType) eventType).asElement();
// Check for handlers for abstract types that aren't polymorphic
Element eventElement = ((DeclaredType) eventType).asElement();
if (eventHandler.getAnnotation(Polymorphic.class) == null
&& (eventElement.getKind() == ElementKind.INTERFACE
|| eventElement.getModifiers().contains(Modifier.ABSTRACT))) {
if (!polymorphic && (eventElement.getKind() == ElementKind.INTERFACE
|| eventElement.getModifiers().contains(Modifier.ABSTRACT)))
warning(eventHandler,
"Parameter should be instantiable or handler should use @Polymorphic");
}
// Check for handlers for final types that are polymorphic
else if (polymorphic && eventElement.getModifiers().contains(Modifier.FINAL))
warning(eventHandler,
"@Polymorphic should be removed as parameter cannot be subclassed");
}
}