Add ExceptionEvent #12

Merged
kske merged 2 commits from f/exception-event into develop 2021-02-21 14:04:26 +01:00
5 changed files with 138 additions and 12 deletions
Showing only changes of commit 6a2cad4ae5 - Show all commits

View File

@ -2,6 +2,7 @@ package dev.kske.eventbus.core;
import java.lang.System.Logger;
import java.lang.System.Logger.Level;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
@ -63,6 +64,7 @@ public final class EventBus {
* priority.
*
* @param event the event to dispatch
* @throws EventBusException if an event handler isn't accessible or has an invalid signature
* @since 0.0.1
*/
public void dispatch(Object event) {
@ -81,16 +83,27 @@ public final class EventBus {
state.isCancelled = false;
break;
} else {
handlers.next().execute(event);
try {
handlers.next().execute(event);
} catch (InvocationTargetException e) {
Review

Why only InvocationTargetException and not Throwable?

Why only `InvocationTargetException` and not `Throwable`?
Review

Because the InvocationTargetException specifically wraps an exception thrown by the event handler, while the IllegalAccessException and IllegalArgumentException are only thrown in the case of an invalid configuration.

Because the `InvocationTargetException` specifically wraps an exception thrown by the event handler, while the `IllegalAccessException` and `IllegalArgumentException` are only thrown in the case of an invalid configuration.
if (event instanceof DeadEvent || event instanceof ExceptionEvent)
// Warn about system event not being handled
logger.log(Level.WARNING, event + " not handled", e);
else
// Dispatch exception event
dispatch(new ExceptionEvent(this, event, e.getCause()));
}
}
} else if (!(event instanceof DeadEvent)) {
// Dispatch dead event
dispatch(new DeadEvent(this, event));
} else {
} else if (event instanceof DeadEvent || event instanceof ExceptionEvent) {
// Warn about the dead event not being handled
logger.log(Level.WARNING, "{0} not handled", event);
} else {
// Dispatch dead event
dispatch(new DeadEvent(this, event));
}
// Reset dispatch state

View File

@ -91,17 +91,21 @@ final class EventHandler implements Comparable<EventHandler> {
* Executes the event handler.
*
* @param event the event used as the method parameter
* @throws EventBusException if the handler throws an exception
* @throws EventBusException if the event handler isn't accessible or has an invalid
* signature
* @throws InvocationTargetException if the handler throws an exception
* @since 0.0.1
*/
void execute(Object event) throws EventBusException {
void execute(Object event) throws EventBusException, InvocationTargetException {
try {
if (useParameter)
method.invoke(listener, event);
else
method.invoke(listener);
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
throw new EventBusException("Failed to invoke event handler!", e);
} catch (IllegalArgumentException e) {
throw new EventBusException("Event handler rejected target / argument!", e);
} catch (IllegalAccessException e) {
throw new EventBusException("Event handler is not accessible!", e);
}
}

View File

@ -0,0 +1,47 @@
package dev.kske.eventbus.core;
/**
* Wraps an event that was dispatched but caused an exception in one of its handlers.
* <p>
* Handling exception events is useful as it allows the creation of a centralized exception handling
* mechanism for unexpected exceptions.
*
* @author Kai S. K. Engelbart
* @since 1.1.0
*/
public final class ExceptionEvent {
private final EventBus eventBus;
private final Object event;
private final Throwable cause;
ExceptionEvent(EventBus eventBus, Object event, Throwable cause) {
this.eventBus = eventBus;
this.event = event;
this.cause = cause;
}
@Override
public String toString() {
return String.format("ExceptionEvent[eventBus=%s, event=%s, cause=%s]", eventBus, event,
cause);
}
/**
* @return the event bus that dispatched this event
* @since 1.1.0
*/
public EventBus getEventBus() { return eventBus; }
/**
* @return the event that could not be handled because of an exception
* @since 1.1.0
*/
public Object getEvent() { return event; }
/**
* @return the exception that was thrown while handling the event
* @since 1.1.0
*/
public Throwable getCause() { return cause; }
}

View File

@ -31,7 +31,7 @@ class DeadTest {
/**
* 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.
* exception or an endless recursion and should be logged instead.
*
* @since 1.1.0
*/

View File

@ -0,0 +1,62 @@
package dev.kske.eventbus.core;
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;
/**
* Tests the dispatching of an exception event if an event handler threw an exception.
*
* @author Kai S. K. Engelbart
* @since 1.1.0
*/
class ExceptionTest {
EventBus bus = new EventBus();
String event = "This event will cause an exception";
RuntimeException exception = new RuntimeException("I failed");
boolean exceptionEventHandled;
/**
* Tests exception event delivery.
*
* @since 1.1.0
*/
@Test
void testExceptionEvent() {
bus.registerListener(this);
bus.registerListener(new ExceptionListener());
bus.dispatch(event);
assertTrue(exceptionEventHandled);
bus.clearListeners();
}
/**
* Tests how the event bus reacts to an unhandled exception event. This should not lead to an
* exception or an endless recursion and should be logged instead.
*
* @since 1.1.0
*/
@Test
void testUnhandledExceptionEvent() {
bus.registerListener(this);
bus.dispatch(event);
bus.removeListener(this);
}
@Event(String.class)
void onString() {
throw exception;
}
class ExceptionListener {
@Event
void onExceptionEvent(ExceptionEvent exceptionEvent) {
assertEquals(bus, exceptionEvent.getEventBus());
assertEquals(event, exceptionEvent.getEvent());
assertEquals(exception, exceptionEvent.getCause());
exceptionEventHandled = true;
}
}
}