diff --git a/README.md b/README.md
index 1f78bcc..1ac86e1 100644
--- a/README.md
+++ b/README.md
@@ -221,6 +221,15 @@ The same applies when an exception event handler throws an exception.
To avoid this, system events never cause system events and instead just issue a warning to the logger.
+## Inheritance
+
+When a superclass or an interface of an event listener defines event handlers, they will be detected and registered by Event Bus, even if they are `private`.
+If an event handler is overridden by the listener, the `@Event` annotation of the overridden method is automatically considered present on the overriding method.
+If the overridden method already contains an implementation in the superclass, the superclass implementation is ignored as expected.
+
+The `@Priority` and `@Polymorphic` annotations are inherited both on a class and on a method level.
+If the priority or polymorphism has to be redefined on an inherited handler, the `@Event` annotation has to be added explicitly.
+
## Debugging
In more complex setups, taking a look at the event handler execution order can be helpful for debugging.
diff --git a/core/src/main/java/dev/kske/eventbus/core/EventBus.java b/core/src/main/java/dev/kske/eventbus/core/EventBus.java
index 83bef93..334dd52 100644
--- a/core/src/main/java/dev/kske/eventbus/core/EventBus.java
+++ b/core/src/main/java/dev/kske/eventbus/core/EventBus.java
@@ -2,7 +2,8 @@ package dev.kske.eventbus.core;
import java.lang.System.Logger;
import java.lang.System.Logger.Level;
-import java.lang.reflect.InvocationTargetException;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
@@ -14,9 +15,8 @@ import dev.kske.eventbus.core.handler.*;
*
* A singleton instance of this class can be lazily created and acquired using the
* {@link EventBus#getInstance()} method.
- *
- * This is a thread-safe implementation.
*
+ * @implNote This is a thread-safe implementation.
* @author Kai S. K. Engelbart
* @since 0.0.1
* @see Event
@@ -237,7 +237,7 @@ 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
@@ -257,6 +257,49 @@ public final class EventBus {
listener.getClass().getName());
}
+ /**
+ * Searches for event handling methods declared inside the inheritance hierarchy of an event
+ * listener.
+ *
+ * @param listenerClass the class to inspect
+ * @return all event handling methods defined for the given listener
+ * @since 1.3.0
+ */
+ private Set getHandlerMethods(Class> listenerClass) {
+
+ // Get methods declared by the listener
+ Set methods = getMethodsAnnotatedWith(listenerClass, Event.class);
+
+ // Recursively add superclass handlers
+ Class> superClass = listenerClass.getSuperclass();
+ if (superClass != null && superClass != Object.class)
+ methods.addAll(getHandlerMethods(superClass));
+
+ // Recursively add interface handlers
+ for (Class> iClass : listenerClass.getInterfaces())
+ methods.addAll(getHandlerMethods(iClass));
+
+ return methods;
+ }
+
+ /**
+ * Searches for declared methods with a specific annotation 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.3.0
+ */
+ private Set getMethodsAnnotatedWith(Class> enclosingClass,
+ Class extends Annotation> annotationClass) {
+ var methods = new HashSet();
+ for (var method : enclosingClass.getDeclaredMethods())
+ if (method.isAnnotationPresent(annotationClass))
+ methods.add(method);
+
+ return methods;
+ }
+
/**
* Registers a callback listener, which is a consumer that is invoked when an event occurs. The
* listener is not polymorphic and has the {@link #DEFAULT_PRIORITY}.
diff --git a/core/src/main/java/dev/kske/eventbus/core/Polymorphic.java b/core/src/main/java/dev/kske/eventbus/core/Polymorphic.java
index 1f77726..ec39121 100644
--- a/core/src/main/java/dev/kske/eventbus/core/Polymorphic.java
+++ b/core/src/main/java/dev/kske/eventbus/core/Polymorphic.java
@@ -18,6 +18,7 @@ import java.lang.annotation.*;
* @see Event
*/
@Documented
+@Inherited
@Retention(RUNTIME)
@Target({ METHOD, TYPE })
public @interface Polymorphic {
diff --git a/core/src/main/java/dev/kske/eventbus/core/Priority.java b/core/src/main/java/dev/kske/eventbus/core/Priority.java
index ca82fa3..e180e0f 100644
--- a/core/src/main/java/dev/kske/eventbus/core/Priority.java
+++ b/core/src/main/java/dev/kske/eventbus/core/Priority.java
@@ -21,6 +21,7 @@ import java.lang.annotation.*;
* @see Event
*/
@Documented
+@Inherited
@Retention(RUNTIME)
@Target({ METHOD, TYPE })
public @interface Priority {
diff --git a/core/src/test/java/dev/kske/eventbus/core/InheritanceTest.java b/core/src/test/java/dev/kske/eventbus/core/InheritanceTest.java
new file mode 100644
index 0000000..b72438d
--- /dev/null
+++ b/core/src/test/java/dev/kske/eventbus/core/InheritanceTest.java
@@ -0,0 +1,42 @@
+package dev.kske.eventbus.core;
+
+import static org.junit.jupiter.api.Assertions.assertSame;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests whether event handlers correctly work in the context of an inheritance hierarchy. The
+ * effect of handler priorities is also accounted for.
+ *
+ * @author Kai S. K. Engelbart
+ * @since 1.3.0
+ */
+class InheritanceTest extends SimpleEventListenerBase implements SimpleEventListenerInterface {
+
+ EventBus bus = new EventBus();
+
+ @Test
+ void test() {
+ bus.registerListener(this);
+ var event = new SimpleEvent();
+
+ bus.dispatch(event);
+ assertSame(3, event.getCounter());
+ }
+
+ @Override
+ void onSimpleEventAbstractHandler(SimpleEvent event) {
+ assertSame(1, event.getCounter());
+ }
+
+ @Override
+ public void onSimpleEventInterfaceHandler(SimpleEvent event) {
+ event.increment();
+ }
+
+ @Event
+ private void onSimpleEventPrivate(SimpleEvent event) {
+ assertSame(0, event.getCounter());
+ event.increment();
+ }
+}
diff --git a/core/src/test/java/dev/kske/eventbus/core/SimpleEvent.java b/core/src/test/java/dev/kske/eventbus/core/SimpleEvent.java
index 2baf080..1512107 100644
--- a/core/src/test/java/dev/kske/eventbus/core/SimpleEvent.java
+++ b/core/src/test/java/dev/kske/eventbus/core/SimpleEvent.java
@@ -1,9 +1,31 @@
package dev.kske.eventbus.core;
/**
- * A simple event for testing purposes.
+ * A simple event for testing purposes. The event contains a counter that is supposed to be
+ * incremented when the event is processed by a handler. That way it is possible to test whether all
+ * handlers that were supposed to be invoked were in fact invoked.
*
* @author Kai S. K. Engelbart
* @since 0.0.1
*/
-class SimpleEvent {}
+class SimpleEvent {
+
+ private int counter;
+
+ @Override
+ public String toString() {
+ return String.format("SimpleEvent[%d]", counter);
+ }
+
+ void increment() {
+ ++counter;
+ }
+
+ int getCounter() {
+ return counter;
+ }
+
+ void reset() {
+ counter = 0;
+ }
+}
diff --git a/core/src/test/java/dev/kske/eventbus/core/SimpleEventListenerBase.java b/core/src/test/java/dev/kske/eventbus/core/SimpleEventListenerBase.java
new file mode 100644
index 0000000..1540655
--- /dev/null
+++ b/core/src/test/java/dev/kske/eventbus/core/SimpleEventListenerBase.java
@@ -0,0 +1,25 @@
+package dev.kske.eventbus.core;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * An abstract class defining a package-private and a private handler for {@link SimpleEvent}.
+ *
+ * @author Kai S. K. Engelbart
+ * @since 1.3.0
+ */
+@Priority(200)
+abstract class SimpleEventListenerBase {
+
+ @Event
+ void onSimpleEventAbstractHandler(SimpleEvent event) {
+ fail("This handler should not be invoked");
+ }
+
+ @Priority(150)
+ @Event
+ private void onSimpleEventPrivate(SimpleEvent event) {
+ assertSame(1, event.getCounter());
+ event.increment();
+ }
+}
diff --git a/core/src/test/java/dev/kske/eventbus/core/SimpleEventListenerInterface.java b/core/src/test/java/dev/kske/eventbus/core/SimpleEventListenerInterface.java
new file mode 100644
index 0000000..8766e34
--- /dev/null
+++ b/core/src/test/java/dev/kske/eventbus/core/SimpleEventListenerInterface.java
@@ -0,0 +1,14 @@
+package dev.kske.eventbus.core;
+
+/**
+ * An interface defining a single handler for {@link SimpleEvent}.
+ *
+ * @author Kai S. K. Engelbart
+ * @since 1.3.0
+ */
+interface SimpleEventListenerInterface {
+
+ @Priority(120)
+ @Event
+ void onSimpleEventInterfaceHandler(SimpleEvent event);
+}