Compare commits
3 Commits
Author | SHA1 | Date | |
---|---|---|---|
ba06b49368
|
|||
7a3debe444
|
|||
5f88ad6095
|
29
README.md
29
README.md
@ -12,6 +12,7 @@ In addition, a singleton instance of the event bus is provided by the `EventBus#
|
|||||||
|
|
||||||
To listen to events, register event handling methods using the `Event` annotation.
|
To listen to events, register event handling methods using the `Event` annotation.
|
||||||
For this to work, the method must have a return type of `void` and declare a single parameter of the desired event type.
|
For this to work, the method must have a return type of `void` and declare a single parameter of the desired event type.
|
||||||
|
Alternatively, a parameter-less event handler can be declared as shown [below](#parameter-less-event-handlers).
|
||||||
Additionally, the class containing the method must implement the `EventListener` interface.
|
Additionally, the class containing the method must implement the `EventListener` interface.
|
||||||
|
|
||||||
## A Simple Example
|
## A Simple Example
|
||||||
@ -47,7 +48,31 @@ public class SimpleEventListener implements EventListener {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
In this case, an event bus is created and used locally. In a more sophisticated example the class would acquire an external event bus that is used by multiple classes.
|
In this case, an event bus is created and used locally.
|
||||||
|
In a more sophisticated example the class would acquire an external event bus that is used by multiple classes.
|
||||||
|
|
||||||
|
## Event handlers for subtypes
|
||||||
|
|
||||||
|
On certain occasions its 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:
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Event(includeSubtypes = true)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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:
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Event(eventType = SimpleEvent.class)
|
||||||
|
private void onSimpleEvent() {
|
||||||
|
System.out.println("SimpleEvent received!");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Make sure that you **do not** declare both a parameter and the `eventType` value of the annotation, as this would be ambiguous.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
@ -66,7 +91,7 @@ To include it inside your project, just add the Maven repository and the depende
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>dev.kske</groupId>
|
<groupId>dev.kske</groupId>
|
||||||
<artifactId>event-bus</artifactId>
|
<artifactId>event-bus</artifactId>
|
||||||
<version>0.0.2</version>
|
<version>0.0.4</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
```
|
```
|
||||||
|
15
pom.xml
15
pom.xml
@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
<groupId>dev.kske</groupId>
|
<groupId>dev.kske</groupId>
|
||||||
<artifactId>event-bus</artifactId>
|
<artifactId>event-bus</artifactId>
|
||||||
<version>0.0.2</version>
|
<version>0.0.4</version>
|
||||||
|
|
||||||
<name>Event Bus</name>
|
<name>Event Bus</name>
|
||||||
<description>An event handling framework for Java utilizing annotations.</description>
|
<description>An event handling framework for Java utilizing annotations.</description>
|
||||||
@ -45,9 +45,21 @@
|
|||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|
||||||
|
<!-- Disable resource folders -->
|
||||||
<resources />
|
<resources />
|
||||||
<testResources />
|
<testResources />
|
||||||
|
|
||||||
<plugins>
|
<plugins>
|
||||||
|
|
||||||
|
<!-- Support Java 9 modules -->
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>3.8.1</version>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
<!-- Attach sources and Javadoc to JAR -->
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-source-plugin</artifactId>
|
<artifactId>maven-source-plugin</artifactId>
|
||||||
@ -72,6 +84,7 @@
|
|||||||
</execution>
|
</execution>
|
||||||
</executions>
|
</executions>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
|
|
||||||
|
@ -10,7 +10,11 @@ import java.lang.annotation.*;
|
|||||||
* comply with the following specifications:
|
* comply with the following specifications:
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>Declared inside a class that implements {@link EventListener}</li>
|
* <li>Declared inside a class that implements {@link EventListener}</li>
|
||||||
* <li>One parameter of a type that implements {@link IEvent}</li>
|
* <li>Specifying an event type by either</li>
|
||||||
|
* <ul>
|
||||||
|
* <li>Declaring one parameter of a type that implements {@link IEvent}</li>
|
||||||
|
* <li>Defining the class of the event using the {@link Event#eventType()} value</li>
|
||||||
|
* </ul>
|
||||||
* <li>Return type of {@code void}</li>
|
* <li>Return type of {@code void}</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
@ -31,4 +35,29 @@ public @interface Event {
|
|||||||
* @since 0.0.1
|
* @since 0.0.1
|
||||||
*/
|
*/
|
||||||
int priority() default 100;
|
int priority() default 100;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines whether instances of subtypes of the event type are dispatched to the event handler.
|
||||||
|
*
|
||||||
|
* @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.
|
||||||
|
* <p>
|
||||||
|
* This is useful when the event handler does not utilize the event instance.
|
||||||
|
*
|
||||||
|
* @since 0.0.3
|
||||||
|
*/
|
||||||
|
Class<? extends IEvent> eventType() default USE_PARAMETER.class;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signifies that the event type the handler listens to is determined by the type of its only
|
||||||
|
* parameter.
|
||||||
|
*
|
||||||
|
* @since 0.0.3
|
||||||
|
*/
|
||||||
|
static final class USE_PARAMETER implements IEvent {}
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,7 @@ public final class EventBus {
|
|||||||
return singletonInstance;
|
return singletonInstance;
|
||||||
}
|
}
|
||||||
|
|
||||||
private final Map<Class<? extends IEvent>, Collection<EventHandler>> bindings
|
private final Map<Class<? extends IEvent>, TreeSet<EventHandler>> bindings
|
||||||
= new ConcurrentHashMap<>();
|
= new ConcurrentHashMap<>();
|
||||||
private final Set<EventListener> registeredListeners = ConcurrentHashMap.newKeySet();
|
private final Set<EventListener> registeredListeners = ConcurrentHashMap.newKeySet();
|
||||||
|
|
||||||
@ -43,6 +43,7 @@ public final class EventBus {
|
|||||||
* @since 0.0.1
|
* @since 0.0.1
|
||||||
*/
|
*/
|
||||||
public void dispatch(IEvent event) {
|
public void dispatch(IEvent event) {
|
||||||
|
Objects.requireNonNull(event);
|
||||||
getHandlersFor(event.getClass()).forEach(handler -> handler.execute(event));
|
getHandlersFor(event.getClass()).forEach(handler -> handler.execute(event));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,8 +55,20 @@ public final class EventBus {
|
|||||||
* @since 0.0.1
|
* @since 0.0.1
|
||||||
*/
|
*/
|
||||||
private List<EventHandler> getHandlersFor(Class<? extends IEvent> eventClass) {
|
private List<EventHandler> getHandlersFor(Class<? extends IEvent> eventClass) {
|
||||||
return bindings.containsKey(eventClass) ? new ArrayList<>(bindings.get(eventClass))
|
|
||||||
: new ArrayList<>();
|
// Get handlers defined for the event class
|
||||||
|
Set<EventHandler> handlers
|
||||||
|
= bindings.containsKey(eventClass) ? bindings.get(eventClass)
|
||||||
|
: new TreeSet<>();
|
||||||
|
|
||||||
|
// Get subtype handlers
|
||||||
|
for (var binding : bindings.entrySet())
|
||||||
|
if (binding.getKey().isAssignableFrom(eventClass))
|
||||||
|
for (var handler : binding.getValue())
|
||||||
|
if (handler.includeSubtypes())
|
||||||
|
handlers.add(handler);
|
||||||
|
|
||||||
|
return new ArrayList<>(handlers);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -63,11 +76,12 @@ public final class EventBus {
|
|||||||
*
|
*
|
||||||
* @param listener the listener to register
|
* @param listener the listener to register
|
||||||
* @throws EventBusException if the listener is already registered or a declared event handler
|
* @throws EventBusException if the listener is already registered or a declared event handler
|
||||||
* does not comply to the specification
|
* does not comply with the specification
|
||||||
* @since 0.0.1
|
* @since 0.0.1
|
||||||
* @see Event
|
* @see Event
|
||||||
*/
|
*/
|
||||||
public void registerListener(EventListener listener) throws EventBusException {
|
public void registerListener(EventListener listener) throws EventBusException {
|
||||||
|
Objects.requireNonNull(listener);
|
||||||
if (registeredListeners.contains(listener))
|
if (registeredListeners.contains(listener))
|
||||||
throw new EventBusException(listener + " already registered!");
|
throw new EventBusException(listener + " already registered!");
|
||||||
|
|
||||||
@ -79,23 +93,12 @@ public final class EventBus {
|
|||||||
if (annotation == null)
|
if (annotation == null)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// Check for correct method signature and return type
|
// Initialize and bind the handler
|
||||||
if (method.getParameterCount() != 1)
|
var handler = new EventHandler(listener, method, annotation);
|
||||||
throw new EventBusException(method + " does not have an argument count of 1!");
|
if (!bindings.containsKey(handler.getEventType()))
|
||||||
|
bindings.put(handler.getEventType(), new TreeSet<>());
|
||||||
if (!method.getReturnType().equals(void.class))
|
bindings.get(handler.getEventType())
|
||||||
throw new EventBusException(method + " does not have a return type of void!");
|
.add(handler);
|
||||||
|
|
||||||
var param = method.getParameterTypes()[0];
|
|
||||||
if (!IEvent.class.isAssignableFrom(param))
|
|
||||||
throw new EventBusException(param + " is not of type IEvent!");
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
var realParam = (Class<? extends IEvent>) param;
|
|
||||||
if (!bindings.containsKey(realParam))
|
|
||||||
bindings.put(realParam, new TreeSet<>());
|
|
||||||
|
|
||||||
bindings.get(realParam).add(new EventHandler(listener, method, annotation));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,6 +109,7 @@ public final class EventBus {
|
|||||||
* @since 0.0.1
|
* @since 0.0.1
|
||||||
*/
|
*/
|
||||||
public void removeListener(EventListener listener) {
|
public void removeListener(EventListener listener) {
|
||||||
|
Objects.requireNonNull(listener);
|
||||||
for (var binding : bindings.values()) {
|
for (var binding : bindings.values()) {
|
||||||
var it = binding.iterator();
|
var it = binding.iterator();
|
||||||
while (it.hasNext())
|
while (it.hasNext())
|
||||||
|
@ -2,6 +2,8 @@ package dev.kske.eventbus;
|
|||||||
|
|
||||||
import java.lang.reflect.*;
|
import java.lang.reflect.*;
|
||||||
|
|
||||||
|
import dev.kske.eventbus.Event.USE_PARAMETER;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal representation of an event handling method.
|
* Internal representation of an event handling method.
|
||||||
*
|
*
|
||||||
@ -14,6 +16,7 @@ final class EventHandler implements Comparable<EventHandler> {
|
|||||||
private final EventListener listener;
|
private final EventListener listener;
|
||||||
private final Method method;
|
private final Method method;
|
||||||
private final Event annotation;
|
private final Event annotation;
|
||||||
|
private final Class<? extends IEvent> eventType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an event handler.
|
* Constructs an event handler.
|
||||||
@ -21,12 +24,40 @@ final class EventHandler implements Comparable<EventHandler> {
|
|||||||
* @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
|
||||||
|
* @throws EventBusException if the method or the annotation do not comply with the
|
||||||
|
* specification
|
||||||
* @since 0.0.1
|
* @since 0.0.1
|
||||||
*/
|
*/
|
||||||
EventHandler(EventListener listener, Method method, Event annotation) {
|
@SuppressWarnings("unchecked")
|
||||||
|
EventHandler(EventListener listener, Method method, Event annotation) throws EventBusException {
|
||||||
this.listener = listener;
|
this.listener = listener;
|
||||||
this.method = method;
|
this.method = method;
|
||||||
this.annotation = annotation;
|
this.annotation = annotation;
|
||||||
|
|
||||||
|
// Check for correct method signature and return type
|
||||||
|
if (method.getParameterCount() == 0 && annotation.eventType().equals(USE_PARAMETER.class))
|
||||||
|
throw new EventBusException(method + " does not define an event type!");
|
||||||
|
|
||||||
|
if (method.getParameterCount() == 1 && !annotation.eventType().equals(USE_PARAMETER.class))
|
||||||
|
throw new EventBusException(method + " defines an ambiguous event type!");
|
||||||
|
|
||||||
|
if (method.getParameterCount() > 1)
|
||||||
|
throw new EventBusException(method + " defines more than one parameter!");
|
||||||
|
|
||||||
|
if (!method.getReturnType().equals(void.class))
|
||||||
|
throw new EventBusException(method + " does not have a return type of void!");
|
||||||
|
|
||||||
|
// Determine the event type
|
||||||
|
Class<? extends IEvent> eventType = annotation.eventType();
|
||||||
|
if (eventType.equals(USE_PARAMETER.class)) {
|
||||||
|
var param = method.getParameterTypes()[0];
|
||||||
|
if (!IEvent.class.isAssignableFrom(param))
|
||||||
|
throw new EventBusException(param + " is not of type IEvent!");
|
||||||
|
eventType = (Class<? extends IEvent>) param;
|
||||||
|
}
|
||||||
|
this.eventType = eventType;
|
||||||
|
|
||||||
|
// Allow access if the method is non-public
|
||||||
method.setAccessible(true);
|
method.setAccessible(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,7 +86,10 @@ final class EventHandler implements Comparable<EventHandler> {
|
|||||||
*/
|
*/
|
||||||
void execute(IEvent event) throws EventBusException {
|
void execute(IEvent event) throws EventBusException {
|
||||||
try {
|
try {
|
||||||
method.invoke(listener, event);
|
if (annotation.eventType().equals(USE_PARAMETER.class))
|
||||||
|
method.invoke(listener, event);
|
||||||
|
else
|
||||||
|
method.invoke(listener);
|
||||||
} catch (
|
} catch (
|
||||||
IllegalAccessException
|
IllegalAccessException
|
||||||
| IllegalArgumentException
|
| IllegalArgumentException
|
||||||
@ -82,4 +116,16 @@ final class EventHandler implements Comparable<EventHandler> {
|
|||||||
* @since 0.0.1
|
* @since 0.0.1
|
||||||
*/
|
*/
|
||||||
int getPriority() { return annotation.priority(); }
|
int getPriority() { return annotation.priority(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return whether this handler includes subtypes
|
||||||
|
* @since 0.0.4
|
||||||
|
*/
|
||||||
|
boolean includeSubtypes() { return annotation.includeSubtypes(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the event type this handler listens to
|
||||||
|
* @since 0.0.3
|
||||||
|
*/
|
||||||
|
Class<? extends IEvent> getEventType() { return eventType; }
|
||||||
}
|
}
|
||||||
|
12
src/main/java/module-info.java
Normal file
12
src/main/java/module-info.java
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
/**
|
||||||
|
* Contains the public API and implementation of the event bus library.
|
||||||
|
*
|
||||||
|
* @author Kai S. K. Engelbart
|
||||||
|
* @since 0.0.3
|
||||||
|
* @see dev.kske.eventbus.Event
|
||||||
|
* @see dev.kske.eventbus.EventBus
|
||||||
|
*/
|
||||||
|
module dev.kske.eventbus {
|
||||||
|
|
||||||
|
exports dev.kske.eventbus;
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
package dev.kske.eventbus;
|
package dev.kske.eventbus;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
import org.junit.jupiter.api.*;
|
import org.junit.jupiter.api.*;
|
||||||
|
|
||||||
@ -21,18 +21,29 @@ class EventBusTest implements EventListener {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testDispatch() {
|
void testDispatch() {
|
||||||
|
EventBus.getInstance().dispatch(new SimpleEventSub());
|
||||||
EventBus.getInstance().dispatch(new SimpleEvent());
|
EventBus.getInstance().dispatch(new SimpleEvent());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Event(priority = 50)
|
@Event(
|
||||||
private void onSimpleEventSecond(SimpleEvent event) {
|
eventType = SimpleEvent.class,
|
||||||
|
includeSubtypes = true,
|
||||||
|
priority = 200
|
||||||
|
)
|
||||||
|
private void onSimpleEventFirst() {
|
||||||
++hits;
|
++hits;
|
||||||
assertEquals(2, hits);
|
assertTrue(hits == 1 || hits == 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Event(priority = 150)
|
@Event(eventType = SimpleEvent.class, priority = 150)
|
||||||
private void onSimpleEventFirst(SimpleEvent event) {
|
private void onSimpleEventSecond() {
|
||||||
++hits;
|
++hits;
|
||||||
assertEquals(1, hits);
|
assertEquals(3, hits);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Event(priority = 50)
|
||||||
|
private void onSimpleEventThird(SimpleEvent event) {
|
||||||
|
++hits;
|
||||||
|
assertEquals(4, hits);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
9
src/test/java/dev/kske/eventbus/SimpleEventSub.java
Normal file
9
src/test/java/dev/kske/eventbus/SimpleEventSub.java
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
package dev.kske.eventbus;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subclass of {@link SimpleEvent} for testing purposes.
|
||||||
|
*
|
||||||
|
* @author Kai S. K. Engelbart
|
||||||
|
* @since 0.0.4
|
||||||
|
*/
|
||||||
|
public class SimpleEventSub extends SimpleEvent {}
|
Reference in New Issue
Block a user