Replace includeSubtypes with @Polymorphic

The new @Polymorphic annotation serves the exact same purpose as
@Event(includeSubtypes = true), but should be easier to read in complex
handler declarations. It has to be used in conjunction with the @Event
annotation, not instead of it.
This commit is contained in:
Kai S. K. Engelbart 2021-02-15 10:55:30 +01:00
parent e040f6ab1b
commit 3a6ebe9a19
Signed by: kske
GPG Key ID: 8BEB13EC5DF7EF13
7 changed files with 54 additions and 38 deletions

View File

@ -55,21 +55,23 @@ Note that creating static event handlers like this
```java
@Event
private static void onSimpleEvent(SimpleEvent event) ...
private static void onSimpleEvent(SimpleEvent event) { ... }
```
is technically possible, however you would still have to create an instance of the event listener to register it at an event bus.
## Event handlers for subtypes
## Polymorphic Event Handlers
On certain occasions it's practical for an event handler to accept both events of the specified type, as well as subclasses of that event.
To include subtypes for an event handler, use the `includeSubtypes` parameter as follows:
To include subtypes for an event handler, use the `@Polymorphic` annotation in addition to `@Event`:
```java
@Event(includeSubtypes = true)
@Event
@Polymorphic
private void onSimpleEvent(SimpleEvent event) { ... }
```
## Event handler execution order
## Event Handler Execution Order
Sometimes when using multiple handlers for one event, it might be useful to know in which order they will be executed.
Event Bus provides a mechanism to ensure the correct propagation of events: the `priority`.
@ -86,7 +88,7 @@ Events are dispatched top-down, meaning the event handler with the highest prior
If no priority is set or multiple handlers have the same priority, the order of execution is undefined.
## Parameter-less event handlers
## Parameter-Less Event Handlers
In some cases an event handler is not interested in the dispatched event instance.
To avoid declaring a useless parameter just to specify the event type of the handler, there is an alternative:
@ -100,7 +102,7 @@ private void onSimpleEvent() {
Make sure that you **do not** declare both a parameter and the `eventType` value of the annotation, as this would be ambiguous.
## Event consumption
## Event Consumption
In some cases it might be useful to stop the propagation of an event.
Event Bus makes this possible with event consumption:
@ -152,7 +154,7 @@ Then, require the Event Bus Core module in your `module-info.java`:
requires dev.kske.eventbus.core;
```
# Compile-Time Error Checking with Event Bus AP
## Compile-Time Error Checking with Event Bus AP
To assist you with writing event listeners, the Event Bus AP (Annotation Processor) module enforces correct usage of the `@Event` annotation during compile time.
This reduces difficult-to-debug bugs that occur during runtime to compile-time errors which can be easily fixed.

View File

@ -73,8 +73,9 @@ public class EventProcessor extends AbstractProcessor {
getTypeMirror(IEvent.class)))
error(paramElement, "Parameter must implement IEvent");
// Check for handlers for abstract types that don't include subtypes
if (!eventAnnotation.includeSubtypes() && paramType.getKind() == TypeKind.DECLARED) {
// Check for handlers for abstract types that aren't polymorphic
if (eventHandler.getAnnotation(Polymorphic.class) == null
&& paramType.getKind() == TypeKind.DECLARED) {
var declaredElement = ((DeclaredType) paramType).asElement();
if (declaredElement.getKind() == ElementKind.INTERFACE
|| declaredElement.getModifiers().contains(Modifier.ABSTRACT))

View File

@ -21,6 +21,7 @@ import java.lang.annotation.*;
*
* @author Kai S. K. Engelbart
* @since 0.0.1
* @see Polymorphic
*/
@Documented
@Retention(RUNTIME)
@ -38,14 +39,6 @@ public @interface Event {
*/
int priority() default 100;
/**
* Defines whether instances of subtypes of the event type are dispatched to the event handler.
*
* @return whether the event handler includes subtypes
* @since 0.0.4
*/
boolean includeSubtypes() default false;
/**
* Defines the event type the handler listens to. If this value is set, the handler is not
* allowed to declare parameters.

View File

@ -104,7 +104,7 @@ public final class EventBus {
for (var binding : bindings.entrySet())
if (binding.getKey().isAssignableFrom(eventClass))
for (var handler : binding.getValue())
if (handler.includeSubtypes())
if (handler.isPolymorphic())
handlers.add(handler);
return new ArrayList<>(handlers);

View File

@ -13,10 +13,11 @@ import dev.kske.eventbus.core.Event.USE_PARAMETER;
*/
final class EventHandler implements Comparable<EventHandler> {
private final EventListener listener;
private final Method method;
private final Event annotation;
private final Class<? extends IEvent> eventType;
private final EventListener listener;
private final Method method;
private final Event annotation;
private final Class<? extends IEvent> eventType;
private final boolean polymorphic;
/**
* Constructs an event handler.
@ -30,9 +31,9 @@ final class EventHandler implements Comparable<EventHandler> {
*/
@SuppressWarnings("unchecked")
EventHandler(EventListener listener, Method method, Event annotation) throws EventBusException {
this.listener = listener;
this.method = method;
this.annotation = annotation;
this.listener = listener;
this.method = method;
this.annotation = annotation;
// Check for correct method signature and return type
if (method.getParameterCount() == 0 && annotation.eventType().equals(USE_PARAMETER.class))
@ -55,7 +56,8 @@ final class EventHandler implements Comparable<EventHandler> {
throw new EventBusException(param + " is not of type IEvent!");
eventType = (Class<? extends IEvent>) param;
}
this.eventType = eventType;
this.eventType = eventType;
polymorphic = method.isAnnotationPresent(Polymorphic.class);
// Allow access if the method is non-public
method.setAccessible(true);
@ -65,7 +67,7 @@ final class EventHandler implements Comparable<EventHandler> {
* Compares this to another event handler based on {@link Event#priority()}. In case of equal
* priority a non-zero value based on hash codes is returned.
* <p>
* This is used to retrieve event handlers in the correct order from a tree set.
* This is used to retrieve event handlers in order of descending priority from a tree set.
*
* @since 0.0.1
*/
@ -91,15 +93,11 @@ final class EventHandler implements Comparable<EventHandler> {
*/
void execute(IEvent event) throws EventBusException {
try {
if (annotation.eventType().equals(USE_PARAMETER.class))
if (annotation.eventType() == USE_PARAMETER.class)
method.invoke(listener, event);
else
method.invoke(listener);
} catch (
IllegalAccessException
| IllegalArgumentException
| InvocationTargetException e
) {
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
throw new EventBusException("Failed to invoke event handler!", e);
}
}
@ -123,10 +121,11 @@ final class EventHandler implements Comparable<EventHandler> {
int getPriority() { return annotation.priority(); }
/**
* @return whether this handler includes subtypes
* @since 0.0.4
* @return whether this handler is polymorphic
* @since 1.0.0
* @see Polymorphic
*/
boolean includeSubtypes() { return annotation.includeSubtypes(); }
boolean isPolymorphic() { return polymorphic; }
/**
* @return the event type this handler listens to

View File

@ -0,0 +1,20 @@
package dev.kske.eventbus.core;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.*;
/**
* Allows an event handler to receive events that are subtypes of the declared event type.
* <p>
* This is useful when defining an event handler for an interface or an abstract class.
*
* @author Kai S. K. Engelbart
* @since 1.0.0
* @see Event
*/
@Documented
@Retention(RUNTIME)
@Target(METHOD)
public @interface Polymorphic {}

View File

@ -38,7 +38,8 @@ class DispatchTest implements EventListener {
bus.dispatch(new SimpleEvent());
}
@Event(eventType = SimpleEvent.class, includeSubtypes = true, priority = 200)
@Event(eventType = SimpleEvent.class, priority = 200)
@Polymorphic
void onSimpleEventFirst() {
++hits;
assertTrue(hits == 1 || hits == 2);