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 45c9ef8..ad9065f 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 @@ -2,9 +2,19 @@ package dev.kske.eventbus.core; import java.lang.System.Logger; import java.lang.System.Logger.Level; +import java.lang.annotation.Annotation; import java.lang.reflect.InvocationTargetException; -import java.util.*; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.TreeSet; import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; /** * Event listeners can be registered at an event bus to be notified when an event is dispatched. @@ -188,13 +198,9 @@ public final class EventBus { priority = listener.getClass().getAnnotation(Priority.class).value(); registeredListeners.add(listener); - for (var method : listener.getClass().getDeclaredMethods()) { + for (var method : getHandlerMethods(listener.getClass())) { Event annotation = method.getAnnotation(Event.class); - // Skip methods without annotations - if (annotation == null) - continue; - // Initialize and bind the handler var handler = new EventHandler(listener, method, annotation, polymorphic, priority); bindings.putIfAbsent(handler.getEventType(), new TreeSet<>()); @@ -211,6 +217,44 @@ public final class EventBus { listener.getClass().getName()); } + /** + * Searches for event handlers declared inside the inheritance hierarchy of an event listener. + * + * This handlers defined in both superclasses and interfaces, regardless of visibility. + * + * @param listenerClass the class to inspect + * @return all event handlers defined for the given listener + * @since 1.2.0 + */ + private List getHandlerMethods(Class listenerClass) { + + // Get declared handlers + List methods = getMethodsAnnotatedWith(listenerClass, Event.class); + + // Recursively add superclass handlers + if(listenerClass.getSuperclass() != null) + methods.addAll(getHandlerMethods(listenerClass.getSuperclass())); + + // Recursively add handlers defined by interfaces + for(Class iClass : listenerClass.getInterfaces()) + methods.addAll(getHandlerMethods(iClass)); + + return methods; + } + + /** + * Searches for methods with a specific annotation declared inside a class. + * + * @param enclosingClass the class to inspect + * @param annotationClass the annotation to look for + * @return all methods matching the search criteria + * @since 1.2.0 + */ + private List getMethodsAnnotatedWith(Class enclosingClass, Class annotationClass) { + return Arrays.stream(enclosingClass.getDeclaredMethods()) + .filter(m -> m.isAnnotationPresent(annotationClass)).collect(Collectors.toList()); + } + /** * Removes a specific listener from this event bus. * diff --git a/event-bus-core/src/test/java/dev/kske/eventbus/core/InheritanceTest.java b/event-bus-core/src/test/java/dev/kske/eventbus/core/InheritanceTest.java new file mode 100644 index 0000000..4ab6a77 --- /dev/null +++ b/event-bus-core/src/test/java/dev/kske/eventbus/core/InheritanceTest.java @@ -0,0 +1,36 @@ +package dev.kske.eventbus.core; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +/** + * Tests whether event handlers correctly handle inheritance. + * + * @author Kai S. K. Engelbart + * @since 1.2.0 + */ +class InheritanceTest extends SimpleEventListenerBase implements SimpleEventListenerInterface { + + EventBus bus; + boolean hit; + + @Test + void test() { + bus = new EventBus(); + bus.registerListener(this); + bus.dispatch(new SimpleEvent()); + assertTrue(hit); + } + + @Override + @Event(SimpleEvent.class) + public void onSimpleEventAbstractHandler() { + System.out.println("Subclass!"); + } + + @Override + public void onSimpleEventInterfaceHandler() { + hit = true; + } +} diff --git a/event-bus-core/src/test/java/dev/kske/eventbus/core/SimpleEventListenerBase.java b/event-bus-core/src/test/java/dev/kske/eventbus/core/SimpleEventListenerBase.java new file mode 100644 index 0000000..2b334be --- /dev/null +++ b/event-bus-core/src/test/java/dev/kske/eventbus/core/SimpleEventListenerBase.java @@ -0,0 +1,23 @@ +package dev.kske.eventbus.core; + + +/** + * An interface defining a single handler for {@link SimpleEvent}. + * + * Used for testing event listener inheritance. + * + * @author Kai S. K. Engelbart + * @since 1.2.0 + */ +abstract class SimpleEventListenerBase { + + @Event(SimpleEvent.class) + void onSimpleEventAbstractHandler() { + System.out.println("Test"); + } + + @Event(SimpleEvent.class) + private void onSimpleEventPrivate() { + System.out.println("Private Handler!"); + } +} diff --git a/event-bus-core/src/test/java/dev/kske/eventbus/core/SimpleEventListenerInterface.java b/event-bus-core/src/test/java/dev/kske/eventbus/core/SimpleEventListenerInterface.java new file mode 100644 index 0000000..3835413 --- /dev/null +++ b/event-bus-core/src/test/java/dev/kske/eventbus/core/SimpleEventListenerInterface.java @@ -0,0 +1,16 @@ +package dev.kske.eventbus.core; + + +/** + * An interface defining a single handler for {@link SimpleEvent}. + * + * Used for testing event listener inheritance. + * + * @author Kai S. K. Engelbart + * @since 1.2.0 + */ +interface SimpleEventListenerInterface { + + @Event(SimpleEvent.class) + void onSimpleEventInterfaceHandler(); +}