Listener-Level Properties #13

Merged
kske merged 5 commits from f/listener-level-properties into develop 2021-03-17 07:56:22 +01:00
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.
## 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
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;
}
/**
* 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 final Logger logger = System.getLogger(EventBus.class.getName());
@ -165,6 +173,16 @@ public final class EventBus {
logger.log(Level.INFO, "Registering event listener {0}", listener.getClass().getName());
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);
for (var method : listener.getClass().getDeclaredMethods()) {
Event annotation = method.getAnnotation(Event.class);
@ -174,7 +192,7 @@ public final class EventBus {
continue;
// 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<>());
logger.log(Level.DEBUG, "Binding event handler {0}", handler);
bindings.get(handler.getEventType())

View File

@ -13,14 +13,6 @@ import dev.kske.eventbus.core.Event.USE_PARAMETER;
*/
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 Method method;
private final Class<?> eventType;
@ -31,14 +23,17 @@ final class EventHandler implements Comparable<EventHandler> {
/**
* Constructs an event handler.
*
* @param listener the listener containing the handler
* @param method the handler method
* @param annotation the event annotation
* @param listener the listener containing the handler
* @param method the handler method
* @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
* specification
* @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.method = method;
useParameter = annotation.value() == USE_PARAMETER.class;
@ -55,10 +50,12 @@ final class EventHandler implements Comparable<EventHandler> {
// Determine handler properties
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)
? method.getAnnotation(Priority.class).value()
: DEFAULT_PRIORITY;
: defPriority;
// Allow access if the method is non-public
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
* signature
* @throws InvocationTargetException if the handler throws an exception
* @throws EventBusException if the handler has the wrong signature or is inaccessible
* @since 0.0.1
*/
void execute(Object event) throws EventBusException, InvocationTargetException {

View File

@ -1,6 +1,6 @@
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 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.
* <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.
*
* @author Kai S. K. Engelbart
@ -16,5 +19,12 @@ import java.lang.annotation.*;
*/
@Documented
@Retention(RUNTIME)
@Target(METHOD)
public @interface Polymorphic {}
@Target({ METHOD, TYPE })
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;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
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
* priority.
* <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.
* <p>
* The execution order of handlers with the same priority is undefined.
@ -19,7 +22,7 @@ import java.lang.annotation.*;
*/
@Documented
@Retention(RUNTIME)
@Target(METHOD)
@Target({ METHOD, TYPE })
public @interface Priority {
/**

View File

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

View File

@ -65,7 +65,7 @@ public class EventProcessor extends AbstractProcessor {
// Warn the user about unused return values
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
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
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
if (!polymorphic && (eventElement.getKind() == ElementKind.INTERFACE