Merge pull request 'Listener-Level Properties' (#13) from f/listener-level-properties into develop

Reviewed-on: https://git.kske.dev/kske/event-bus/pulls/13
Reviewed-by: delvh <leon@kske.dev>
This commit is contained in:
Kai S. K. Engelbart 2021-03-17 07:56:22 +01:00
commit 51f10c4144
Signed by: Käfer & Engelbart Git
GPG Key ID: 70F2F9206EDC1FCE
7 changed files with 100 additions and 25 deletions

View File

@ -97,6 +97,13 @@ private void onSimpleEvent() {
Make sure that you **do not** both declare a parameter and specify the event type in the annotation, as this would be ambiguous. Make sure that you **do not** both declare a parameter and specify the event type in the annotation, as this would be ambiguous.
## Listener-Level Properties
When defining a dedicated event listener that, for example, performs pre- or post-processing, all event handlers will probably have the same non-standard priority.
Instead of defining that priority for each handler, it can be defined at the listener level by annotating the listener itself.
The same applies to polymorphism.
## Event Consumption ## Event Consumption
In some cases it might be useful to stop the propagation of an event. In some cases it might be useful to stop the propagation of an event.

View File

@ -30,6 +30,14 @@ public final class EventBus {
boolean isDispatching, isCancelled; boolean isDispatching, isCancelled;
} }
/**
* The priority assigned to every event handler without an explicitly defined priority.
*
* @since 1.1.0
* @see Priority
*/
public static final int DEFAULT_PRIORITY = 100;
private static volatile EventBus singletonInstance; private static volatile EventBus singletonInstance;
private static final Logger logger = System.getLogger(EventBus.class.getName()); private static final Logger logger = System.getLogger(EventBus.class.getName());
@ -169,6 +177,16 @@ public final class EventBus {
logger.log(Level.INFO, "Registering event listener {0}", listener.getClass().getName()); logger.log(Level.INFO, "Registering event listener {0}", listener.getClass().getName());
boolean handlerBound = false; boolean handlerBound = false;
// Predefined handler polymorphism
boolean polymorphic = false;
if (listener.getClass().isAnnotationPresent(Polymorphic.class))
polymorphic = listener.getClass().getAnnotation(Polymorphic.class).value();
// Predefined handler priority
int priority = DEFAULT_PRIORITY;
if (listener.getClass().isAnnotationPresent(Priority.class))
priority = listener.getClass().getAnnotation(Priority.class).value();
registeredListeners.add(listener); registeredListeners.add(listener);
for (var method : listener.getClass().getDeclaredMethods()) { for (var method : listener.getClass().getDeclaredMethods()) {
Event annotation = method.getAnnotation(Event.class); Event annotation = method.getAnnotation(Event.class);
@ -178,7 +196,7 @@ public final class EventBus {
continue; continue;
// Initialize and bind the handler // Initialize and bind the handler
var handler = new EventHandler(listener, method, annotation); var handler = new EventHandler(listener, method, annotation, polymorphic, priority);
bindings.putIfAbsent(handler.getEventType(), new TreeSet<>()); bindings.putIfAbsent(handler.getEventType(), new TreeSet<>());
logger.log(Level.DEBUG, "Binding event handler {0}", handler); logger.log(Level.DEBUG, "Binding event handler {0}", handler);
bindings.get(handler.getEventType()) bindings.get(handler.getEventType())

View File

@ -13,14 +13,6 @@ import dev.kske.eventbus.core.Event.USE_PARAMETER;
*/ */
final class EventHandler implements Comparable<EventHandler> { final class EventHandler implements Comparable<EventHandler> {
/**
* The priority assigned to every event handler without an explicitly defined priority.
*
* @since 1.0.0
* @see Priority
*/
public static final int DEFAULT_PRIORITY = 100;
private final Object listener; private final Object listener;
private final Method method; private final Method method;
private final Class<?> eventType; private final Class<?> eventType;
@ -31,14 +23,17 @@ final class EventHandler implements Comparable<EventHandler> {
/** /**
* Constructs an event handler. * Constructs an event handler.
* *
* @param listener the listener containing the handler * @param listener the listener containing the handler
* @param method the handler method * @param method the handler method
* @param annotation the event annotation * @param annotation the event annotation
* @param defPolymorphism the predefined polymorphism (default or listener-level)
* @param defPriority the predefined priority (default or listener-level)
* @throws EventBusException if the method or the annotation do not comply with the * @throws EventBusException if the method or the annotation do not comply with the
* specification * specification
* @since 0.0.1 * @since 0.0.1
*/ */
EventHandler(Object listener, Method method, Event annotation) throws EventBusException { EventHandler(Object listener, Method method, Event annotation, boolean defPolymorphism,
int defPriority) throws EventBusException {
this.listener = listener; this.listener = listener;
this.method = method; this.method = method;
useParameter = annotation.value() == USE_PARAMETER.class; useParameter = annotation.value() == USE_PARAMETER.class;
@ -55,10 +50,12 @@ final class EventHandler implements Comparable<EventHandler> {
// Determine handler properties // Determine handler properties
eventType = useParameter ? method.getParameterTypes()[0] : annotation.value(); eventType = useParameter ? method.getParameterTypes()[0] : annotation.value();
polymorphic = method.isAnnotationPresent(Polymorphic.class); polymorphic = method.isAnnotationPresent(Polymorphic.class)
? method.getAnnotation(Polymorphic.class).value()
: defPolymorphism;
priority = method.isAnnotationPresent(Priority.class) priority = method.isAnnotationPresent(Priority.class)
? method.getAnnotation(Priority.class).value() ? method.getAnnotation(Priority.class).value()
: DEFAULT_PRIORITY; : defPriority;
// Allow access if the method is non-public // Allow access if the method is non-public
method.setAccessible(true); method.setAccessible(true);
@ -94,6 +91,7 @@ final class EventHandler implements Comparable<EventHandler> {
* @throws EventBusException if the event handler isn't accessible or has an invalid * @throws EventBusException if the event handler isn't accessible or has an invalid
* signature * signature
* @throws InvocationTargetException if the handler throws an exception * @throws InvocationTargetException if the handler throws an exception
* @throws EventBusException if the handler has the wrong signature or is inaccessible
* @since 0.0.1 * @since 0.0.1
*/ */
void execute(Object event) throws EventBusException, InvocationTargetException { void execute(Object event) throws EventBusException, InvocationTargetException {

View File

@ -1,6 +1,6 @@
package dev.kske.eventbus.core; package dev.kske.eventbus.core;
import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME; import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.*; import java.lang.annotation.*;
@ -8,6 +8,9 @@ import java.lang.annotation.*;
/** /**
* Allows an event handler to receive events that are subtypes of the declared event type. * Allows an event handler to receive events that are subtypes of the declared event type.
* <p> * <p>
* When used on a type, the value applies to all event handlers declared within that type that don't
* define a value on their own.
* <p>
* This is useful when defining an event handler for an interface or an abstract class. * This is useful when defining an event handler for an interface or an abstract class.
* *
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
@ -16,5 +19,12 @@ import java.lang.annotation.*;
*/ */
@Documented @Documented
@Retention(RUNTIME) @Retention(RUNTIME)
@Target(METHOD) @Target({ METHOD, TYPE })
public @interface Polymorphic {} public @interface Polymorphic {
/**
* @return whether the event handler is polymorphic
* @since 1.1.0
*/
boolean value() default true;
}

View File

@ -1,6 +1,6 @@
package dev.kske.eventbus.core; package dev.kske.eventbus.core;
import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME; import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.*; import java.lang.annotation.*;
@ -9,6 +9,9 @@ import java.lang.annotation.*;
* Defines the priority of an event handler. Handlers are executed in descending order of their * Defines the priority of an event handler. Handlers are executed in descending order of their
* priority. * priority.
* <p> * <p>
* When used on a type, the value applies to all event handlers declared within that type that don't
* define a value on their own.
* <p>
* Handlers without this annotation have the default priority of 100. * Handlers without this annotation have the default priority of 100.
* <p> * <p>
* The execution order of handlers with the same priority is undefined. * The execution order of handlers with the same priority is undefined.
@ -19,7 +22,7 @@ import java.lang.annotation.*;
*/ */
@Documented @Documented
@Retention(RUNTIME) @Retention(RUNTIME)
@Target(METHOD) @Target({ METHOD, TYPE })
public @interface Priority { public @interface Priority {
/** /**

View File

@ -10,6 +10,8 @@ import org.junit.jupiter.api.*;
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @since 0.0.1 * @since 0.0.1
*/ */
@Polymorphic
@Priority(150)
class DispatchTest { class DispatchTest {
EventBus bus; EventBus bus;
@ -40,20 +42,21 @@ class DispatchTest {
@Event(SimpleEvent.class) @Event(SimpleEvent.class)
@Priority(200) @Priority(200)
@Polymorphic
void onSimpleEventFirst() { void onSimpleEventFirst() {
++hits; ++hits;
assertTrue(hits == 1 || hits == 2); assertTrue(hits == 1 || hits == 2);
} }
@Event(SimpleEvent.class) @Event(SimpleEvent.class)
@Priority(150) @Polymorphic(false)
static void onSimpleEventSecond() { static void onSimpleEventSecond() {
++hits; ++hits;
assertEquals(3, hits); assertEquals(3, hits);
} }
@Event @Event
@Polymorphic(false)
@Priority(100)
void onSimpleEventThird(SimpleEvent event) { void onSimpleEventThird(SimpleEvent event) {
++hits; ++hits;
assertEquals(4, hits); assertEquals(4, hits);

View File

@ -65,7 +65,7 @@ public class EventProcessor extends AbstractProcessor {
// Warn the user about unused return values // Warn the user about unused return values
if (useParameter && eventHandler.getReturnType().getKind() != TypeKind.VOID) if (useParameter && eventHandler.getReturnType().getKind() != TypeKind.VOID)
warning(eventHandler, "Unused return value"); warning(eventHandler, "Unused return value");
// Abort checking if the handler signature is incorrect // Abort checking if the handler signature is incorrect
if (!pass) if (!pass)
@ -84,9 +84,45 @@ public class EventProcessor extends AbstractProcessor {
} }
} }
// Get the listener containing this handler
TypeElement listener = (TypeElement) eventHandler.getEnclosingElement();
// Default properties
boolean defPolymorphic = false;
int defPriority = 100;
// Listener-level polymorphism
Polymorphic listenerPolymorphic = listener.getAnnotation(Polymorphic.class);
boolean hasListenerPolymorphic = listenerPolymorphic != null;
// Listener-level priority
Priority listenerPriority = listener.getAnnotation(Priority.class);
boolean hasListenerPriority = listenerPriority != null;
// Effective polymorphism
boolean polymorphic =
hasListenerPolymorphic ? listenerPolymorphic.value() : defPolymorphic;
boolean hasHandlerPolymorphic = eventHandler.getAnnotation(Polymorphic.class) != null;
if (hasHandlerPolymorphic)
polymorphic = eventHandler.getAnnotation(Polymorphic.class).value();
// Effective priority
int priority = hasListenerPriority ? listenerPriority.value() : defPriority;
boolean hasHandlerPriority = eventHandler.getAnnotation(Priority.class) != null;
if (hasHandlerPriority)
priority = eventHandler.getAnnotation(Priority.class).value();
// Detect useless polymorphism redefinition
if (hasListenerPolymorphic && hasHandlerPolymorphic
&& listenerPolymorphic.value() == polymorphic)
warning(eventHandler, "@Polymorphism is already defined at listener level");
// Detect useless priority redefinition
if (hasListenerPriority && hasHandlerPriority && listenerPriority.value() == priority)
warning(eventHandler, "@Priority is already defined at the listener level");
// Detect missing or useless @Polymorphic // Detect missing or useless @Polymorphic
boolean polymorphic = eventHandler.getAnnotation(Polymorphic.class) != null; Element eventElement = ((DeclaredType) eventType).asElement();
Element eventElement = ((DeclaredType) eventType).asElement();
// Check for handlers for abstract types that aren't polymorphic // Check for handlers for abstract types that aren't polymorphic
if (!polymorphic && (eventElement.getKind() == ElementKind.INTERFACE if (!polymorphic && (eventElement.getKind() == ElementKind.INTERFACE