Compare commits
63 Commits
0.1.0
...
5ddef71c26
Author | SHA1 | Date | |
---|---|---|---|
5ddef71c26
![]() |
|||
85b2da391a
![]() |
|||
46a358da97
|
|||
6bf9e1097a
|
|||
e67b64678b
![]() |
|||
c614beb063
|
|||
d3abb0aca3
|
|||
ee688929fd
|
|||
897d794b86
![]() |
|||
40d48cb959
![]() |
|||
b760c58298
|
|||
872b395374
|
|||
82c66c45ec
|
|||
866a547114
![]() |
|||
33ebf0302b
|
|||
b915a5c490
![]() |
|||
205a183db7
|
|||
74447dea59
|
|||
6eebd3c121
|
|||
b758f4cef1
|
|||
0dcad7d178
|
|||
c0cda7341b
![]() |
|||
b804243f4e
|
|||
0aef1c299b
|
|||
51f10c4144
![]() |
|||
f74b953db8
|
|||
52719d22d4
![]() |
|||
122106bf39
|
|||
7357198d45
|
|||
32dfe64c0f
|
|||
2ec0a82a96
|
|||
6c74af608c
|
|||
d9ddc0e1a9
![]() |
|||
7c3cd017de
|
|||
6a2cad4ae5
|
|||
0f9b64be48
![]() |
|||
b2fe3a9d6c
|
|||
9379e6bb94
![]() |
|||
0036dc4829
|
|||
8a30493c52
|
|||
b56f08e441
|
|||
4a5b94a9b7
|
|||
ff35e7f37d
|
|||
1dd9e05c38
|
|||
39c51c8953
![]() |
|||
002180ed3b
|
|||
603fe80df6
![]() |
|||
cd2e7ad023
|
|||
9b1c708514
|
|||
3a6ebe9a19
|
|||
e040f6ab1b
|
|||
ebc11555f6
|
|||
955e2d82b4
![]() |
|||
ab01845178
|
|||
023acb9172
|
|||
fd255d65cc
|
|||
9701e862df
|
|||
dcc578076a
|
|||
883efed342
|
|||
273531e352
![]() |
|||
8b1e3a8c4a
![]() |
|||
d098b83d85
![]() |
|||
cc266ca408
|
10
.gitignore
vendored
10
.gitignore
vendored
@ -1,2 +1,8 @@
|
|||||||
/target/
|
# Maven build directories
|
||||||
/.settings/
|
target/
|
||||||
|
|
||||||
|
# Dependency reduced POM from Maven Shade Plugin
|
||||||
|
dependency-reduced-pom.xml
|
||||||
|
|
||||||
|
# Eclipse settings directories
|
||||||
|
.settings/
|
||||||
|
6
.project
6
.project
@ -5,11 +5,6 @@
|
|||||||
<projects>
|
<projects>
|
||||||
</projects>
|
</projects>
|
||||||
<buildSpec>
|
<buildSpec>
|
||||||
<buildCommand>
|
|
||||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
|
||||||
<arguments>
|
|
||||||
</arguments>
|
|
||||||
</buildCommand>
|
|
||||||
<buildCommand>
|
<buildCommand>
|
||||||
<name>org.eclipse.m2e.core.maven2Builder</name>
|
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||||
<arguments>
|
<arguments>
|
||||||
@ -17,7 +12,6 @@
|
|||||||
</buildCommand>
|
</buildCommand>
|
||||||
</buildSpec>
|
</buildSpec>
|
||||||
<natures>
|
<natures>
|
||||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
|
||||||
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||||
</natures>
|
</natures>
|
||||||
</projectDescription>
|
</projectDescription>
|
||||||
|
5
CODE_OF_CONDUCT.md
Normal file
5
CODE_OF_CONDUCT.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# Contributor Code of Conduct
|
||||||
|
|
||||||
|
This project adheres to No Code of Conduct. We are all adults. We accept anyone's contributions. Nothing else matters.
|
||||||
|
|
||||||
|
For more information please visit the [No Code of Conduct](https://nocodeofconduct.com) homepage.
|
26
LICENSE
26
LICENSE
@ -1,19 +1,13 @@
|
|||||||
MIT License Copyright (c) 2020 Kai S. K. Engelbart
|
Copyright 2021 Kai S. K. Engelbart
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
you may not use this file except in compliance with the License.
|
||||||
in the Software without restriction, including without limitation the rights
|
You may obtain a copy of the License at
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is furnished
|
|
||||||
to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice (including the next
|
https://www.apache.org/licenses/LICENSE-2.0
|
||||||
paragraph) shall be included in all copies or substantial portions of the
|
|
||||||
Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
Unless required by applicable law or agreed to in writing, software
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
See the License for the specific language governing permissions and
|
||||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
|
limitations under the License.
|
||||||
OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
|
172
README.md
172
README.md
@ -3,34 +3,31 @@
|
|||||||
## Introduction
|
## Introduction
|
||||||
|
|
||||||
This library allows passing events between different objects without them having a direct reference to each other.
|
This library allows passing events between different objects without them having a direct reference to each other.
|
||||||
Any class can be made an event by implementing the `IEvent` interface.
|
Any object can serve as an event.
|
||||||
|
|
||||||
Using an instance of the `EventBus` class, an instant of the event class can be dispatched.
|
Using an instance of the `EventBus` class, an instance of the event class can be dispatched.
|
||||||
This means that it will be forwarded to all listeners registered for it at the event bus.
|
This means that it will be forwarded to all listeners registered for it at the event bus.
|
||||||
|
|
||||||
In addition, a singleton instance of the event bus is provided by the `EventBus#getInstance()` method.
|
In addition, a singleton instance of the event bus is provided by the `EventBus#getInstance()` method.
|
||||||
|
|
||||||
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 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).
|
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.
|
|
||||||
|
|
||||||
## A Simple Example
|
## A Simple Example
|
||||||
|
|
||||||
Lets look at a simple example: we declare the empty class `SimpleEvent` that implements `IEvent` and can thus be used as an event.
|
Lets look at a simple example: we declare the empty class `SimpleEvent` whose objects can be used as events.
|
||||||
|
|
||||||
```java
|
```java
|
||||||
import dev.kske.eventbus.IEvent;
|
public class SimpleEvent {}
|
||||||
|
|
||||||
public class SimpleEvent implements IEvent {}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Next, an event listener for the `SimpleEvent` is declared:
|
Next, an event listener for the `SimpleEvent` is declared:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
import dev.kske.eventbus.*;
|
import dev.kske.eventbus.core.*;
|
||||||
|
|
||||||
public class SimpleEventListener implements EventListener {
|
public class SimpleEventListener {
|
||||||
|
|
||||||
public SimpleEventListener() {
|
public SimpleEventListener() {
|
||||||
|
|
||||||
@ -48,100 +45,171 @@ 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.
|
|
||||||
|
|
||||||
Note that creating static event handlers like this
|
Note that creating static event handlers like this
|
||||||
|
|
||||||
```java
|
```java
|
||||||
@Event
|
@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.
|
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 its practical for an event handler to accept both events of the specified type, as well as subclasses of that event.
|
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
|
```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.
|
Sometimes when using multiple handlers for one event, it might be useful to define in which order they will be executed.
|
||||||
Event Bus provides a mechanism to ensure the correct propagation of events: the `priority`.
|
Event Bus assigns a priority to every handler, which is `100` by default, but can be explicitly set using the `@Priority` annotation in addition to `@Event`:
|
||||||
|
|
||||||
Priority can be set on the `@Event` annotation like that:
|
|
||||||
```java
|
```java
|
||||||
@Event(priority=100)
|
@Event
|
||||||
|
@Priority(250)
|
||||||
|
private void onSimpleEvent(SimpleEvent event) { ... }
|
||||||
```
|
```
|
||||||
|
|
||||||
The default priority for events is `100`.
|
|
||||||
|
|
||||||
**Important:**
|
**Important:**
|
||||||
Events are dispatched top-down, meaning the event handler with the highest priority will be executed first.
|
Events are dispatched to handlers in descending order of their priority.
|
||||||
|
The execution order is undefined for handlers with the same priority.
|
||||||
|
|
||||||
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.
|
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:
|
To avoid declaring a useless parameter just to specify the event type of the handler, there is an alternative:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
@Event(eventType = SimpleEvent.class)
|
@Event(SimpleEvent.class)
|
||||||
private void onSimpleEvent() {
|
private void onSimpleEvent() {
|
||||||
System.out.println("SimpleEvent received!");
|
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.
|
Make sure that you **do not** both declare a parameter and specify the event type in the annotation, as this would be ambiguous.
|
||||||
|
|
||||||
## Event consumption
|
## Listener-Level Properties
|
||||||
|
|
||||||
There are cases when it would be useful to stop event propagation after a certain condition has been fulfilled.
|
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.
|
||||||
Event Bus provides a mechanism to consume events:
|
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.
|
||||||
|
Event Bus makes this possible with event consumption:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
@Event(eventType = SimpleEvent.class, priority=1000)
|
@Event(SimpleEvent.class)
|
||||||
|
@Priority(100)
|
||||||
private void onSimpleEvent() {
|
private void onSimpleEvent() {
|
||||||
EventBus.getInstance().cancel();
|
EventBus.getInstance().cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Event(eventType = SimpleEvent.class, priority=900)
|
@Event(SimpleEvent.class)
|
||||||
|
@Priority(50)
|
||||||
private void onSimpleEvent2() {
|
private void onSimpleEvent2() {
|
||||||
System.out.println("Will not be printed!");
|
System.out.println("Will not be printed!");
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
In this example, the second method will not be executed as the event will no longer be forwarded.
|
In this example, the second method will not be executed as it has a lower priority and the event will not be propagated after consumption.
|
||||||
Any event handler with a lower priority than the one canceling it will not get executed.
|
This applies to all event handlers that would have been executed after the one consuming the event.
|
||||||
|
|
||||||
**Important:**
|
**Important:**
|
||||||
Please avoid cancelling events when (multiple) event handlers have the same priority as the one cancelling it:
|
Avoid cancelling events while using multiple event handlers with the same priority.
|
||||||
It is undefined whether those will be executed or not.
|
As event handlers are ordered by priority, it is not defined which of them will be executed after the event has been consumed.
|
||||||
|
|
||||||
|
## System Events
|
||||||
|
|
||||||
|
To accommodate for special circumstances in an event distribution, system events have been introduced.
|
||||||
|
At the moment, there are two system events, which are explained in this section.
|
||||||
|
|
||||||
|
### Detecting Unhandled Events
|
||||||
|
|
||||||
|
When an event is dispatched but not delivered to any handler, a dead event is dispatched that wraps the original event.
|
||||||
|
You can declare a dead event handler to respond to this situation:
|
||||||
|
|
||||||
|
```java
|
||||||
|
private void onDeadEvent(DeadEvent deadEvent) { ... }
|
||||||
|
```
|
||||||
|
|
||||||
|
### Detecting Exceptions Thrown by Event Handlers
|
||||||
|
|
||||||
|
When an event handler throws an exception, an exception event is dispatched that wraps the original event.
|
||||||
|
A exception handler is declared as follows:
|
||||||
|
|
||||||
|
```java
|
||||||
|
private void onExceptionEvent(ExceptionEvent ExceptionEvent) { ... }
|
||||||
|
```
|
||||||
|
|
||||||
|
Both system events reference the event bus that caused them and a warning is logged if they are unhandled.
|
||||||
|
|
||||||
|
### What About Endless Recursion Caused By Dead Events and Exception Events?
|
||||||
|
|
||||||
|
As one might imagine, an unhandled dead event would theoretically lead to an endless recursion.
|
||||||
|
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.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
Event Bus is currently hosted at [kske.dev](https://kske.dev).
|
Event Bus is available in Maven Central.
|
||||||
To include it inside your project, just add the Maven repository and the dependency to your `pom.xml`:
|
To include it inside your project, just add the following dependency to your `pom.xml`:
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<repositories>
|
|
||||||
<repository>
|
|
||||||
<id>kske-repo</id>
|
|
||||||
<url>https://kske.dev/maven-repo</url>
|
|
||||||
</repository>
|
|
||||||
</repositories>
|
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>dev.kske</groupId>
|
<groupId>dev.kske</groupId>
|
||||||
<artifactId>event-bus</artifactId>
|
<artifactId>event-bus-core</artifactId>
|
||||||
<version>0.1.0</version>
|
<version>1.1.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Then, require the Event Bus Core module in your `module-info.java`:
|
||||||
|
|
||||||
|
```java
|
||||||
|
requires dev.kske.eventbus.core;
|
||||||
|
```
|
||||||
|
|
||||||
|
If you intend to use event handlers that are inaccessible to Event Bus by means of Java language access control, make sure to allow reflective access from your module:
|
||||||
|
|
||||||
|
```java
|
||||||
|
opens my.module to dev.kske.eventbus.core;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Compile-Time Error Checking with Event Bus Proc
|
||||||
|
|
||||||
|
To assist you with writing event listeners, the Event Bus Proc (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.
|
||||||
|
|
||||||
|
The event annotation processor detects invalid event handlers and event type issues with more to come in future versions.
|
||||||
|
|
||||||
|
When using Maven, it can be registered using the Maven Compiler Plugin:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>3.8.1</version>
|
||||||
|
<configuration>
|
||||||
|
<annotationProcessorPaths>
|
||||||
|
<annotationProcessorPath>
|
||||||
|
<groupId>dev.kske</groupId>
|
||||||
|
<artifactId>event-bus-proc</artifactId>
|
||||||
|
<version>1.1.0</version>
|
||||||
|
</annotationProcessorPath>
|
||||||
|
</annotationProcessorPaths>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
```
|
||||||
|
|
||||||
|
Alternatively, a JAR file containing the processor is offered with each release for the use within IDEs and environments without Maven support.
|
||||||
|
23
event-bus-core/.project
Normal file
23
event-bus-core/.project
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<projectDescription>
|
||||||
|
<name>event-bus-core</name>
|
||||||
|
<comment></comment>
|
||||||
|
<projects>
|
||||||
|
</projects>
|
||||||
|
<buildSpec>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
</buildSpec>
|
||||||
|
<natures>
|
||||||
|
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||||
|
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||||
|
</natures>
|
||||||
|
</projectDescription>
|
39
event-bus-core/pom.xml
Normal file
39
event-bus-core/pom.xml
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<artifactId>event-bus-core</artifactId>
|
||||||
|
<name>Event Bus Core</name>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>dev.kske</groupId>
|
||||||
|
<artifactId>event-bus</artifactId>
|
||||||
|
<version>1.1.0</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter-api</artifactId>
|
||||||
|
<version>5.8.1</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
|
||||||
|
<!-- Disable resource folder -->
|
||||||
|
<resources />
|
||||||
|
|
||||||
|
<!-- Run unit tests -->
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
|
<version>3.0.0-M5</version>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
|
||||||
|
</build>
|
||||||
|
</project>
|
@ -0,0 +1,37 @@
|
|||||||
|
package dev.kske.eventbus.core;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wraps an event that was dispatched but for which no handler has been bound.
|
||||||
|
* <p>
|
||||||
|
* Handling dead events is useful as it can identify a poorly configured event distribution.
|
||||||
|
*
|
||||||
|
* @author Kai S. K. Engelbart
|
||||||
|
* @since 1.1.0
|
||||||
|
*/
|
||||||
|
public final class DeadEvent {
|
||||||
|
|
||||||
|
private final EventBus eventBus;
|
||||||
|
private final Object event;
|
||||||
|
|
||||||
|
DeadEvent(EventBus eventBus, Object event) {
|
||||||
|
this.eventBus = eventBus;
|
||||||
|
this.event = event;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return String.format("DeadEvent[eventBus=%s, event=%s]", eventBus, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the event bus that dispatched this event
|
||||||
|
* @since 1.1.0
|
||||||
|
*/
|
||||||
|
public EventBus getEventBus() { return eventBus; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the event that could not be delivered
|
||||||
|
* @since 1.1.0
|
||||||
|
*/
|
||||||
|
public Object getEvent() { return event; }
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
package dev.kske.eventbus.core;
|
||||||
|
|
||||||
|
import static java.lang.annotation.ElementType.METHOD;
|
||||||
|
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||||
|
|
||||||
|
import java.lang.annotation.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates that a method is an event handler.
|
||||||
|
* <p>
|
||||||
|
* To be successfully used as such, the method has to specify the event type by either declaring one
|
||||||
|
* parameter of that type or setting the annotation value to the corresponding class.
|
||||||
|
*
|
||||||
|
* @author Kai S. K. Engelbart
|
||||||
|
* @since 0.0.1
|
||||||
|
* @see Polymorphic
|
||||||
|
* @see Priority
|
||||||
|
*/
|
||||||
|
@Documented
|
||||||
|
@Retention(RUNTIME)
|
||||||
|
@Target(METHOD)
|
||||||
|
public @interface Event {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines the event type the handler listens for. 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.
|
||||||
|
*
|
||||||
|
* @return the event type accepted by the handler
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
Class<?> value() 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 {}
|
||||||
|
}
|
@ -0,0 +1,414 @@
|
|||||||
|
package dev.kske.eventbus.core;
|
||||||
|
|
||||||
|
import java.lang.System.Logger;
|
||||||
|
import java.lang.System.Logger.Level;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import dev.kske.eventbus.core.handler.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event listeners can be registered at an event bus to be notified when an event is dispatched.
|
||||||
|
* <p>
|
||||||
|
* A singleton instance of this class can be lazily created and acquired using the
|
||||||
|
* {@link EventBus#getInstance()} method.
|
||||||
|
* <p>
|
||||||
|
* This is a thread-safe implementation.
|
||||||
|
*
|
||||||
|
* @author Kai S. K. Engelbart
|
||||||
|
* @since 0.0.1
|
||||||
|
* @see Event
|
||||||
|
*/
|
||||||
|
public final class EventBus {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds the state of the dispatching process on one thread.
|
||||||
|
*
|
||||||
|
* @since 0.1.0
|
||||||
|
*/
|
||||||
|
private static final class DispatchState {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates that the last event handler invoked has called {@link EventBus#cancel}. In that
|
||||||
|
* case, the event is not dispatched further.
|
||||||
|
*
|
||||||
|
* @since 0.1.0
|
||||||
|
*/
|
||||||
|
boolean isCancelled;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is incremented when {@link EventBus#dispatch(Object)} is invoked and decremented when it
|
||||||
|
* finishes. This allows keeping track of nested dispatches.
|
||||||
|
*
|
||||||
|
* @since 1.2.0
|
||||||
|
*/
|
||||||
|
int nestingCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 final EventBus singletonInstance = new EventBus();
|
||||||
|
|
||||||
|
private static final Logger logger = System.getLogger(EventBus.class.getName());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compares event handlers based on priority, but uses hash codes for equal priorities.
|
||||||
|
*
|
||||||
|
* @implNote As the priority comparator by itself is not consistent with equals (two handlers
|
||||||
|
* with the same priority are not necessarily equal, but would have a comparison
|
||||||
|
* result of 0), the hash code is used for the fallback comparison. This way,
|
||||||
|
* consistency with equals is restored.
|
||||||
|
* @since 1.2.0
|
||||||
|
*/
|
||||||
|
private static final Comparator<EventHandler> byPriority =
|
||||||
|
Comparator.comparingInt(EventHandler::getPriority).reversed()
|
||||||
|
.thenComparingInt(EventHandler::hashCode);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the default event bus, which is a statically initialized singleton instance.
|
||||||
|
*
|
||||||
|
* @return the default event bus
|
||||||
|
* @since 0.0.2
|
||||||
|
*/
|
||||||
|
public static EventBus getInstance() {
|
||||||
|
return singletonInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event handler bindings (target class to handlers registered for that class), does not contain
|
||||||
|
* other (polymorphic) handlers.
|
||||||
|
*
|
||||||
|
* @since 0.0.1
|
||||||
|
*/
|
||||||
|
private final Map<Class<?>, TreeSet<EventHandler>> bindings = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores all registered event listeners (which declare event handlers) and prevents them from
|
||||||
|
* being garbage collected.
|
||||||
|
*
|
||||||
|
* @since 0.0.1
|
||||||
|
*/
|
||||||
|
private final Set<Object> registeredListeners = ConcurrentHashMap.newKeySet();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current event dispatching state, local to each thread.
|
||||||
|
*
|
||||||
|
* @since 0.1.0
|
||||||
|
*/
|
||||||
|
private final ThreadLocal<DispatchState> dispatchState =
|
||||||
|
ThreadLocal.withInitial(DispatchState::new);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatches an event to all event handlers registered for it in descending order of their
|
||||||
|
* priority.
|
||||||
|
*
|
||||||
|
* @param event the event to dispatch
|
||||||
|
* @throws EventBusException if an event handler isn't accessible or has an invalid signature
|
||||||
|
* @throws NullPointerException if the specified event is {@code null}
|
||||||
|
* @since 0.0.1
|
||||||
|
*/
|
||||||
|
public void dispatch(Object event) throws EventBusException {
|
||||||
|
Objects.requireNonNull(event);
|
||||||
|
logger.log(Level.INFO, "Dispatching event {0}", event);
|
||||||
|
|
||||||
|
// Look up dispatch state
|
||||||
|
var state = dispatchState.get();
|
||||||
|
|
||||||
|
// Increment nesting count (becomes > 1 during nested dispatches)
|
||||||
|
++state.nestingCount;
|
||||||
|
|
||||||
|
Iterator<EventHandler> handlers = getHandlersFor(event.getClass()).iterator();
|
||||||
|
if (handlers.hasNext()) {
|
||||||
|
while (handlers.hasNext())
|
||||||
|
if (state.isCancelled) {
|
||||||
|
logger.log(Level.INFO, "Cancelled dispatching event {0}", event);
|
||||||
|
state.isCancelled = false;
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
handlers.next().execute(event);
|
||||||
|
} catch (InvocationTargetException e) {
|
||||||
|
if (e.getCause() instanceof Error)
|
||||||
|
|
||||||
|
// Transparently pass error to the caller
|
||||||
|
throw (Error) e.getCause();
|
||||||
|
else if (event instanceof DeadEvent || event instanceof ExceptionEvent)
|
||||||
|
|
||||||
|
// Warn about system event not being handled
|
||||||
|
logger.log(Level.WARNING, event + " not handled due to exception", e);
|
||||||
|
else
|
||||||
|
|
||||||
|
// Dispatch exception event
|
||||||
|
dispatch(new ExceptionEvent(this, event, e.getCause()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (event instanceof DeadEvent || event instanceof ExceptionEvent) {
|
||||||
|
|
||||||
|
// Warn about the dead event not being handled
|
||||||
|
logger.log(Level.WARNING, "{0} not handled", event);
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// Dispatch dead event
|
||||||
|
dispatch(new DeadEvent(this, event));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decrement nesting count (becomes 0 when all dispatches on the thread are finished)
|
||||||
|
--state.nestingCount;
|
||||||
|
|
||||||
|
logger.log(Level.DEBUG, "Finished dispatching event {0}", event);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Searches for the event handlers bound to an event class. This includes polymorphic handlers
|
||||||
|
* that are bound to a supertype of the event class.
|
||||||
|
*
|
||||||
|
* @param eventType the event type to use for the search
|
||||||
|
* @return a navigable set containing the applicable handlers in descending order of priority
|
||||||
|
* @since 1.2.0
|
||||||
|
*/
|
||||||
|
private NavigableSet<EventHandler> getHandlersFor(Class<?> eventType) {
|
||||||
|
|
||||||
|
// Get handlers defined for the event class
|
||||||
|
TreeSet<EventHandler> handlers =
|
||||||
|
bindings.getOrDefault(eventType, new TreeSet<>(byPriority));
|
||||||
|
|
||||||
|
// Get polymorphic handlers
|
||||||
|
for (var binding : bindings.entrySet())
|
||||||
|
if (binding.getKey().isAssignableFrom(eventType))
|
||||||
|
for (var handler : binding.getValue())
|
||||||
|
if (handler.isPolymorphic())
|
||||||
|
handlers.add(handler);
|
||||||
|
|
||||||
|
return handlers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancels an event that is currently dispatched from inside an event handler.
|
||||||
|
*
|
||||||
|
* @throws EventBusException if the calling thread is not an active dispatching thread
|
||||||
|
* @since 0.1.0
|
||||||
|
*/
|
||||||
|
public void cancel() {
|
||||||
|
var state = dispatchState.get();
|
||||||
|
if (state.nestingCount > 0 && !state.isCancelled)
|
||||||
|
state.isCancelled = true;
|
||||||
|
else
|
||||||
|
throw new EventBusException("Calling thread not an active dispatching thread!");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers an event listener at this event bus.
|
||||||
|
*
|
||||||
|
* @param listener the listener to register
|
||||||
|
* @throws EventBusException if the listener is already registered or a declared event
|
||||||
|
* handler does not comply with the specification
|
||||||
|
* @throws NullPointerException if the specified listener is {@code null}
|
||||||
|
* @since 0.0.1
|
||||||
|
* @see Event
|
||||||
|
*/
|
||||||
|
public void registerListener(Object listener) throws EventBusException {
|
||||||
|
Objects.requireNonNull(listener);
|
||||||
|
if (registeredListeners.contains(listener))
|
||||||
|
throw new EventBusException(listener + " already registered!");
|
||||||
|
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);
|
||||||
|
|
||||||
|
// Skip methods without annotations
|
||||||
|
if (annotation == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Initialize and bind the handler
|
||||||
|
bindHandler(
|
||||||
|
new ReflectiveEventHandler(listener, method, annotation, polymorphic, priority));
|
||||||
|
handlerBound = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!handlerBound)
|
||||||
|
logger.log(
|
||||||
|
Level.WARNING,
|
||||||
|
"No event handlers bound for event listener {0}",
|
||||||
|
listener.getClass().getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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}.
|
||||||
|
*
|
||||||
|
* @param <E> the event type the listener listens for
|
||||||
|
* @param eventType the event type the listener listens for
|
||||||
|
* @param eventListener the callback that is invoked when an event occurs
|
||||||
|
* @since 1.2.0
|
||||||
|
* @see #registerListener(Class, Consumer, boolean, int)
|
||||||
|
*/
|
||||||
|
public <E> void registerListener(Class<E> eventType, Consumer<E> eventListener) {
|
||||||
|
registerListener(eventType, eventListener, false, DEFAULT_PRIORITY);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a callback listener, which is a consumer that is invoked when an event occurs. The
|
||||||
|
* listener has the {@link #DEFAULT_PRIORITY}.
|
||||||
|
*
|
||||||
|
* @param <E> the event type the listener listens for
|
||||||
|
* @param eventType the event type the listener listens for
|
||||||
|
* @param eventListener the callback that is invoked when an event occurs
|
||||||
|
* @param polymorphic whether the listener is also invoked for subtypes of the event type
|
||||||
|
* @since 1.2.0
|
||||||
|
* @see #registerListener(Class, Consumer, boolean, int)
|
||||||
|
*/
|
||||||
|
public <E> void registerListener(Class<E> eventType, Consumer<E> eventListener,
|
||||||
|
boolean polymorphic) {
|
||||||
|
registerListener(eventType, eventListener, polymorphic, DEFAULT_PRIORITY);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a callback listener, which is a consumer that is invoked when an event occurs. The
|
||||||
|
* listener is not polymorphic.
|
||||||
|
*
|
||||||
|
* @param <E> the event type the listener listens for
|
||||||
|
* @param eventType the event type the listener listens for
|
||||||
|
* @param eventListener the callback that is invoked when an event occurs
|
||||||
|
* @param priority the priority to assign to the listener
|
||||||
|
* @since 1.2.0
|
||||||
|
* @see #registerListener(Class, Consumer, boolean, int)
|
||||||
|
*/
|
||||||
|
public <E> void registerListener(Class<E> eventType, Consumer<E> eventListener, int priority) {
|
||||||
|
registerListener(eventType, eventListener, false, priority);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a callback listener, which is a consumer that is invoked when an event occurs.
|
||||||
|
*
|
||||||
|
* @param <E> the event type the listener listens for
|
||||||
|
* @param eventType the event type the listener listens for
|
||||||
|
* @param eventListener the callback that is invoked when an event occurs
|
||||||
|
* @param polymorphic whether the listener is also invoked for subtypes of the event type
|
||||||
|
* @param priority the priority to assign to the listener
|
||||||
|
* @since 1.2.0
|
||||||
|
*/
|
||||||
|
public <E> void registerListener(Class<E> eventType, Consumer<E> eventListener,
|
||||||
|
boolean polymorphic,
|
||||||
|
int priority) {
|
||||||
|
Objects.requireNonNull(eventListener);
|
||||||
|
if (registeredListeners.contains(eventListener))
|
||||||
|
throw new EventBusException(eventListener + " already registered!");
|
||||||
|
logger.log(Level.INFO, "Registering callback event listener {0}",
|
||||||
|
eventListener.getClass().getName());
|
||||||
|
|
||||||
|
registeredListeners.add(eventListener);
|
||||||
|
bindHandler(new CallbackEventHandler(eventType, eventListener, polymorphic, priority));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inserts a new handler into the {@link #bindings} map.
|
||||||
|
*
|
||||||
|
* @param handler the handler to bind
|
||||||
|
* @since 1.2.0
|
||||||
|
*/
|
||||||
|
private void bindHandler(EventHandler handler) {
|
||||||
|
bindings.putIfAbsent(handler.getEventType(), new TreeSet<>(byPriority));
|
||||||
|
logger.log(Level.DEBUG, "Binding event handler {0}", handler);
|
||||||
|
bindings.get(handler.getEventType()).add(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a specific listener from this event bus.
|
||||||
|
*
|
||||||
|
* @param listener the listener to remove
|
||||||
|
* @since 0.0.1
|
||||||
|
*/
|
||||||
|
public void removeListener(Object listener) {
|
||||||
|
Objects.requireNonNull(listener);
|
||||||
|
logger.log(Level.INFO, "Removing event listener {0}", listener.getClass().getName());
|
||||||
|
|
||||||
|
// Remove bindings from binding map
|
||||||
|
for (var binding : bindings.values()) {
|
||||||
|
var it = binding.iterator();
|
||||||
|
while (it.hasNext()) {
|
||||||
|
var handler = it.next();
|
||||||
|
if (handler.getListener() == listener) {
|
||||||
|
logger.log(Level.DEBUG, "Unbinding event handler {0}", handler);
|
||||||
|
it.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the listener itself
|
||||||
|
registeredListeners.remove(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all event listeners from this event bus.
|
||||||
|
*
|
||||||
|
* @since 0.0.1
|
||||||
|
*/
|
||||||
|
public void clearListeners() {
|
||||||
|
logger.log(Level.INFO, "Clearing event listeners");
|
||||||
|
bindings.clear();
|
||||||
|
registeredListeners.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a string describing the event handlers that would be executed for a specific event
|
||||||
|
* type, in order and without actually executing them.
|
||||||
|
*
|
||||||
|
* @apiNote Using this method is only recommended for debugging purposes, as the output depends
|
||||||
|
* on implementation internals which may be subject to change.
|
||||||
|
* @implNote Nested dispatches are not accounted for, as this would require actually executing
|
||||||
|
* the handlers.
|
||||||
|
* @param eventType the event type to generate the execution order for
|
||||||
|
* @return a human-readable event handler list suitable for debugging purposes
|
||||||
|
* @since 1.2.0
|
||||||
|
*/
|
||||||
|
public String printExecutionOrder(Class<?> eventType) {
|
||||||
|
var handlers = getHandlersFor(eventType);
|
||||||
|
var sj = new StringJoiner("\n");
|
||||||
|
|
||||||
|
// Output header line
|
||||||
|
sj.add(String.format("Event handler execution order for %s (%d handler(s)):", eventType,
|
||||||
|
handlers.size()));
|
||||||
|
sj.add(
|
||||||
|
"==========================================================================================");
|
||||||
|
|
||||||
|
// Individual handlers
|
||||||
|
for (var handler : handlers)
|
||||||
|
sj.add(handler.toString());
|
||||||
|
|
||||||
|
// Bottom line
|
||||||
|
sj.add(
|
||||||
|
"==========================================================================================");
|
||||||
|
|
||||||
|
return sj.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides an unmodifiable view of the event listeners registered at this event bus.
|
||||||
|
*
|
||||||
|
* @return all registered event listeners
|
||||||
|
* @since 0.0.1
|
||||||
|
*/
|
||||||
|
public Set<Object> getRegisteredListeners() {
|
||||||
|
return Collections.unmodifiableSet(registeredListeners);
|
||||||
|
}
|
||||||
|
}
|
@ -1,14 +1,18 @@
|
|||||||
package dev.kske.eventbus;
|
package dev.kske.eventbus.core;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This runtime exception is thrown when an event bus error occurs. This can
|
* This unchecked exception is specific to the event bus and can be thrown under the following
|
||||||
* either occur while registering event listeners with invalid handlers, or when
|
* circumstances:
|
||||||
* an event handler throws an exception.
|
* <ul>
|
||||||
|
* <li>An event handler throws an exception (which is stored as the cause)</li>
|
||||||
|
* <li>An event listener with an invalid event handler is registered</li>
|
||||||
|
* <li>{@link EventBus#cancel()} is invoked from outside an active dispatch thread</li>
|
||||||
|
* </ul>
|
||||||
*
|
*
|
||||||
* @author Kai S. K. Engelbart
|
* @author Kai S. K. Engelbart
|
||||||
* @since 0.0.1
|
* @since 0.0.1
|
||||||
*/
|
*/
|
||||||
public class EventBusException extends RuntimeException {
|
public final class EventBusException extends RuntimeException {
|
||||||
|
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
@ -0,0 +1,47 @@
|
|||||||
|
package dev.kske.eventbus.core;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wraps an event that was dispatched but caused an exception in one of its handlers.
|
||||||
|
* <p>
|
||||||
|
* Handling exception events is useful as it allows the creation of a centralized exception handling
|
||||||
|
* mechanism for unexpected exceptions.
|
||||||
|
*
|
||||||
|
* @author Kai S. K. Engelbart
|
||||||
|
* @since 1.1.0
|
||||||
|
*/
|
||||||
|
public final class ExceptionEvent {
|
||||||
|
|
||||||
|
private final EventBus eventBus;
|
||||||
|
private final Object event;
|
||||||
|
private final Throwable cause;
|
||||||
|
|
||||||
|
ExceptionEvent(EventBus eventBus, Object event, Throwable cause) {
|
||||||
|
this.eventBus = eventBus;
|
||||||
|
this.event = event;
|
||||||
|
this.cause = cause;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return String.format("ExceptionEvent[eventBus=%s, event=%s, cause=%s]", eventBus, event,
|
||||||
|
cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the event bus that dispatched this event
|
||||||
|
* @since 1.1.0
|
||||||
|
*/
|
||||||
|
public EventBus getEventBus() { return eventBus; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the event that could not be handled because of an exception
|
||||||
|
* @since 1.1.0
|
||||||
|
*/
|
||||||
|
public Object getEvent() { return event; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the exception that was thrown while handling the event
|
||||||
|
* @since 1.1.0
|
||||||
|
*/
|
||||||
|
public Throwable getCause() { return cause; }
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
package dev.kske.eventbus.core;
|
||||||
|
|
||||||
|
import static java.lang.annotation.ElementType.*;
|
||||||
|
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>
|
||||||
|
* 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
|
||||||
|
* @since 1.0.0
|
||||||
|
* @see Event
|
||||||
|
*/
|
||||||
|
@Documented
|
||||||
|
@Retention(RUNTIME)
|
||||||
|
@Target({ METHOD, TYPE })
|
||||||
|
public @interface Polymorphic {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return whether the event handler is polymorphic
|
||||||
|
* @since 1.1.0
|
||||||
|
*/
|
||||||
|
boolean value() default true;
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
package dev.kske.eventbus.core;
|
||||||
|
|
||||||
|
import static java.lang.annotation.ElementType.*;
|
||||||
|
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||||
|
|
||||||
|
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.
|
||||||
|
*
|
||||||
|
* @author Kai S. K. Engelbart
|
||||||
|
* @since 1.0.0
|
||||||
|
* @see Event
|
||||||
|
*/
|
||||||
|
@Documented
|
||||||
|
@Retention(RUNTIME)
|
||||||
|
@Target({ METHOD, TYPE })
|
||||||
|
public @interface Priority {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the priority of the event handler
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
int value();
|
||||||
|
}
|
@ -0,0 +1,73 @@
|
|||||||
|
package dev.kske.eventbus.core.handler;
|
||||||
|
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An event handler wrapping a callback method.
|
||||||
|
*
|
||||||
|
* @author Kai S. K. Engelbart
|
||||||
|
* @since 1.2.0
|
||||||
|
*/
|
||||||
|
public final class CallbackEventHandler implements EventHandler {
|
||||||
|
|
||||||
|
private final Class<?> eventType;
|
||||||
|
private final Consumer<Object> callback;
|
||||||
|
private final boolean polymorphic;
|
||||||
|
private final int priority;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a callback event handler.
|
||||||
|
*
|
||||||
|
* @param <E> the event type of the handler
|
||||||
|
* @param eventType the event type of the handler
|
||||||
|
* @param callback the callback method to execute when the handler is invoked
|
||||||
|
* @param polymorphic whether the handler is polymorphic
|
||||||
|
* @param priority the priority of the handler
|
||||||
|
* @since 1.2.0
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <E> CallbackEventHandler(Class<E> eventType, Consumer<E> callback, boolean polymorphic,
|
||||||
|
int priority) {
|
||||||
|
this.eventType = eventType;
|
||||||
|
this.callback = (Consumer<Object>) callback;
|
||||||
|
this.polymorphic = polymorphic;
|
||||||
|
this.priority = priority;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(Object event) throws InvocationTargetException {
|
||||||
|
try {
|
||||||
|
callback.accept(event);
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
throw new InvocationTargetException(e, "Callback event handler failed!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return String.format(
|
||||||
|
"CallbackEventHandler[eventType=%s, polymorphic=%b, priority=%d]",
|
||||||
|
eventType, polymorphic, priority);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Consumer<?> getListener() {
|
||||||
|
return callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<?> getEventType() {
|
||||||
|
return eventType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPriority() {
|
||||||
|
return priority;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isPolymorphic() {
|
||||||
|
return polymorphic;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,53 @@
|
|||||||
|
package dev.kske.eventbus.core.handler;
|
||||||
|
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
|
||||||
|
import dev.kske.eventbus.core.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal representation of an event handling method.
|
||||||
|
*
|
||||||
|
* @author Kai S. K. Engelbart
|
||||||
|
* @since 1.2.0
|
||||||
|
* @see EventBus
|
||||||
|
*/
|
||||||
|
public interface EventHandler {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes the event handler.
|
||||||
|
*
|
||||||
|
* @param event the event used as the method parameter
|
||||||
|
* @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 1.2.0
|
||||||
|
*/
|
||||||
|
void execute(Object event) throws EventBusException, InvocationTargetException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the listener containing this handler
|
||||||
|
* @since 1.2.0
|
||||||
|
*/
|
||||||
|
Object getListener();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the event type this handler listens for
|
||||||
|
* @since 1.2.0
|
||||||
|
*/
|
||||||
|
Class<?> getEventType();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the priority of this handler
|
||||||
|
* @since 1.2.0
|
||||||
|
* @see Priority
|
||||||
|
*/
|
||||||
|
int getPriority();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return whether this handler also accepts subtypes of the event type
|
||||||
|
* @since 1.2.0
|
||||||
|
* @see Polymorphic
|
||||||
|
*/
|
||||||
|
boolean isPolymorphic();
|
||||||
|
}
|
@ -0,0 +1,105 @@
|
|||||||
|
package dev.kske.eventbus.core.handler;
|
||||||
|
|
||||||
|
import java.lang.reflect.*;
|
||||||
|
|
||||||
|
import dev.kske.eventbus.core.*;
|
||||||
|
import dev.kske.eventbus.core.Event.USE_PARAMETER;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An event handler wrapping a method annotated with {@link Event} and executing it using
|
||||||
|
* reflection.
|
||||||
|
*
|
||||||
|
* @author Kai S. K. Engelbart
|
||||||
|
* @since 1.2.0
|
||||||
|
*/
|
||||||
|
public final class ReflectiveEventHandler implements EventHandler {
|
||||||
|
|
||||||
|
private final Object listener;
|
||||||
|
private final Method method;
|
||||||
|
private final Class<?> eventType;
|
||||||
|
private final boolean useParameter;
|
||||||
|
private final boolean polymorphic;
|
||||||
|
private final int priority;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a reflective event handler.
|
||||||
|
*
|
||||||
|
* @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 1.2.0
|
||||||
|
*/
|
||||||
|
public ReflectiveEventHandler(Object listener, Method method, Event annotation,
|
||||||
|
boolean defPolymorphism, int defPriority) throws EventBusException {
|
||||||
|
this.listener = listener;
|
||||||
|
this.method = method;
|
||||||
|
useParameter = annotation.value() == USE_PARAMETER.class;
|
||||||
|
|
||||||
|
// Check handler signature
|
||||||
|
if (method.getParameterCount() == 0 && useParameter)
|
||||||
|
throw new EventBusException(method + " does not define an event type!");
|
||||||
|
|
||||||
|
if (method.getParameterCount() == 1 && !useParameter)
|
||||||
|
throw new EventBusException(method + " defines an ambiguous event type!");
|
||||||
|
|
||||||
|
if (method.getParameterCount() > 1)
|
||||||
|
throw new EventBusException(method + " defines more than one parameter!");
|
||||||
|
|
||||||
|
// Determine handler properties
|
||||||
|
eventType = useParameter ? method.getParameterTypes()[0] : annotation.value();
|
||||||
|
polymorphic = method.isAnnotationPresent(Polymorphic.class)
|
||||||
|
? method.getAnnotation(Polymorphic.class).value()
|
||||||
|
: defPolymorphism;
|
||||||
|
priority = method.isAnnotationPresent(Priority.class)
|
||||||
|
? method.getAnnotation(Priority.class).value()
|
||||||
|
: defPriority;
|
||||||
|
|
||||||
|
// Allow access if the method is non-public
|
||||||
|
method.setAccessible(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(Object event) throws EventBusException, InvocationTargetException {
|
||||||
|
try {
|
||||||
|
if (useParameter)
|
||||||
|
method.invoke(getListener(), event);
|
||||||
|
else
|
||||||
|
method.invoke(getListener());
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
throw new EventBusException("Event handler rejected target / argument!", e);
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
throw new EventBusException("Event handler is not accessible!", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return String.format(
|
||||||
|
"ReflectiveEventHandler[eventType=%s, polymorphic=%b, priority=%d, method=%s, useParameter=%b]",
|
||||||
|
eventType, polymorphic, priority, method, useParameter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getListener() {
|
||||||
|
return listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<?> getEventType() {
|
||||||
|
return eventType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPriority() {
|
||||||
|
return priority;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isPolymorphic() {
|
||||||
|
return polymorphic;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
/**
|
||||||
|
* Contains the internal representation of event handling methods.
|
||||||
|
*
|
||||||
|
* @author Kai S. K. Engelbart
|
||||||
|
* @since 1.2.0
|
||||||
|
* @see dev.kske.eventbus.core.handler.EventHandler
|
||||||
|
*/
|
||||||
|
package dev.kske.eventbus.core.handler;
|
@ -0,0 +1,9 @@
|
|||||||
|
/**
|
||||||
|
* Contains the public API and implementation of the Event Bus library.
|
||||||
|
*
|
||||||
|
* @author Kai S. K. Engelbart
|
||||||
|
* @since 0.0.1
|
||||||
|
* @see dev.kske.eventbus.core.Event
|
||||||
|
* @see dev.kske.eventbus.core.EventBus
|
||||||
|
*/
|
||||||
|
package dev.kske.eventbus.core;
|
12
event-bus-core/src/main/java/module-info.java
Normal file
12
event-bus-core/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.core.Event
|
||||||
|
* @see dev.kske.eventbus.core.EventBus
|
||||||
|
*/
|
||||||
|
module dev.kske.eventbus.core {
|
||||||
|
|
||||||
|
exports dev.kske.eventbus.core;
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package dev.kske.eventbus;
|
package dev.kske.eventbus.core;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
@ -11,7 +11,7 @@ import org.junit.jupiter.api.*;
|
|||||||
* @author Leon Hofmeister
|
* @author Leon Hofmeister
|
||||||
* @since 0.1.0
|
* @since 0.1.0
|
||||||
*/
|
*/
|
||||||
class CancelTest implements EventListener {
|
public class CancelTest {
|
||||||
|
|
||||||
EventBus bus;
|
EventBus bus;
|
||||||
int hits;
|
int hits;
|
||||||
@ -22,7 +22,7 @@ class CancelTest implements EventListener {
|
|||||||
* @since 0.1.0
|
* @since 0.1.0
|
||||||
*/
|
*/
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void registerListener() {
|
public void registerListener() {
|
||||||
bus = new EventBus();
|
bus = new EventBus();
|
||||||
bus.registerListener(this);
|
bus.registerListener(this);
|
||||||
}
|
}
|
||||||
@ -34,18 +34,20 @@ class CancelTest implements EventListener {
|
|||||||
* @since 0.1.0
|
* @since 0.1.0
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
void testCancellation() {
|
public void testCancellation() {
|
||||||
bus.dispatch(new SimpleEvent());
|
bus.dispatch(new SimpleEvent());
|
||||||
assertEquals(1, hits);
|
assertEquals(1, hits);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Event(eventType = SimpleEvent.class, priority = 100)
|
@Event(SimpleEvent.class)
|
||||||
|
@Priority(100)
|
||||||
void onSimpleFirst() {
|
void onSimpleFirst() {
|
||||||
++hits;
|
++hits;
|
||||||
bus.cancel();
|
bus.cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Event(eventType = SimpleEvent.class, priority = 50)
|
@Event(SimpleEvent.class)
|
||||||
|
@Priority(50)
|
||||||
void onSimpleSecond() {
|
void onSimpleSecond() {
|
||||||
++hits;
|
++hits;
|
||||||
}
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
package dev.kske.eventbus.core;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the dispatching of a dead event if an event could not be delivered.
|
||||||
|
*
|
||||||
|
* @author Kai S. K. Engelbart
|
||||||
|
* @since 1.1.0
|
||||||
|
*/
|
||||||
|
public class DeadTest {
|
||||||
|
|
||||||
|
EventBus bus = new EventBus();
|
||||||
|
String event = "This event has no handler";
|
||||||
|
boolean deadEventHandled;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests dead event delivery.
|
||||||
|
*
|
||||||
|
* @since 1.1.0
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testDeadEvent() {
|
||||||
|
bus.registerListener(this);
|
||||||
|
bus.dispatch(event);
|
||||||
|
assertTrue(deadEventHandled);
|
||||||
|
bus.removeListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests how the event bus reacts to an unhandled dead event. This should not lead to an
|
||||||
|
* exception or an endless recursion and should be logged instead.
|
||||||
|
*
|
||||||
|
* @since 1.1.0
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testUnhandledDeadEvent() {
|
||||||
|
bus.dispatch(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Event
|
||||||
|
void onDeadEvent(DeadEvent deadEvent) {
|
||||||
|
assertEquals(bus, deadEvent.getEventBus());
|
||||||
|
assertEquals(event, deadEvent.getEvent());
|
||||||
|
deadEventHandled = true;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,79 @@
|
|||||||
|
package dev.kske.eventbus.core;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the dispatching mechanism of the event bus.
|
||||||
|
*
|
||||||
|
* @author Kai S. K. Engelbart
|
||||||
|
* @since 0.0.1
|
||||||
|
*/
|
||||||
|
@Polymorphic
|
||||||
|
@Priority(150)
|
||||||
|
public class DispatchTest {
|
||||||
|
|
||||||
|
EventBus bus;
|
||||||
|
static int hits;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an event bus and registers this test instance as an event listener.
|
||||||
|
*
|
||||||
|
* @since 0.0.1
|
||||||
|
*/
|
||||||
|
@BeforeEach
|
||||||
|
public void registerListener() {
|
||||||
|
bus = new EventBus();
|
||||||
|
bus.registerListener(this);
|
||||||
|
bus.registerListener(SimpleEvent.class, e -> {
|
||||||
|
++hits;
|
||||||
|
assertEquals(4, hits);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests {@link EventBus#dispatch(Object)} with multiple handler priorities, a polymorphic
|
||||||
|
* handler and a static handler.
|
||||||
|
*
|
||||||
|
* @since 0.0.1
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testDispatch() {
|
||||||
|
bus.dispatch(new SimpleEventSub());
|
||||||
|
bus.dispatch(new SimpleEvent());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests {@link EventBus#printExecutionOrder(Class)} based on the currently registered handlers.
|
||||||
|
*
|
||||||
|
* @since 1.2.0
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testPrintExecutionOrder() {
|
||||||
|
String executionOrder = bus.printExecutionOrder(SimpleEvent.class);
|
||||||
|
System.out.println(executionOrder);
|
||||||
|
assertEquals(
|
||||||
|
"Event handler execution order for class dev.kske.eventbus.core.SimpleEvent (3 handler(s)):\n"
|
||||||
|
+ "==========================================================================================\n"
|
||||||
|
+ "ReflectiveEventHandler[eventType=class dev.kske.eventbus.core.SimpleEvent, polymorphic=true, priority=200, method=void dev.kske.eventbus.core.DispatchTest.onSimpleEventFirst(), useParameter=false]\n"
|
||||||
|
+ "ReflectiveEventHandler[eventType=class dev.kske.eventbus.core.SimpleEvent, polymorphic=false, priority=150, method=static void dev.kske.eventbus.core.DispatchTest.onSimpleEventSecond(), useParameter=false]\n"
|
||||||
|
+ "CallbackEventHandler[eventType=class dev.kske.eventbus.core.SimpleEvent, polymorphic=false, priority=100]\n"
|
||||||
|
+ "==========================================================================================",
|
||||||
|
executionOrder);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Event(SimpleEvent.class)
|
||||||
|
@Priority(200)
|
||||||
|
void onSimpleEventFirst() {
|
||||||
|
++hits;
|
||||||
|
assertTrue(hits == 1 || hits == 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Event(SimpleEvent.class)
|
||||||
|
@Polymorphic(false)
|
||||||
|
static void onSimpleEventSecond() {
|
||||||
|
++hits;
|
||||||
|
assertEquals(3, hits);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,62 @@
|
|||||||
|
package dev.kske.eventbus.core;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the dispatching of an exception event if an event handler threw an exception.
|
||||||
|
*
|
||||||
|
* @author Kai S. K. Engelbart
|
||||||
|
* @since 1.1.0
|
||||||
|
*/
|
||||||
|
public class ExceptionTest {
|
||||||
|
|
||||||
|
EventBus bus = new EventBus();
|
||||||
|
String event = "This event will cause an exception";
|
||||||
|
RuntimeException exception = new RuntimeException("I failed");
|
||||||
|
boolean exceptionEventHandled;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests exception event delivery.
|
||||||
|
*
|
||||||
|
* @since 1.1.0
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testExceptionEvent() {
|
||||||
|
bus.registerListener(this);
|
||||||
|
bus.registerListener(new ExceptionListener());
|
||||||
|
bus.dispatch(event);
|
||||||
|
assertTrue(exceptionEventHandled);
|
||||||
|
bus.clearListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests how the event bus reacts to an unhandled exception event. This should not lead to an
|
||||||
|
* exception or an endless recursion and should be logged instead.
|
||||||
|
*
|
||||||
|
* @since 1.1.0
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testUnhandledExceptionEvent() {
|
||||||
|
bus.registerListener(this);
|
||||||
|
bus.dispatch(event);
|
||||||
|
bus.removeListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Event(String.class)
|
||||||
|
void onString() {
|
||||||
|
throw exception;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ExceptionListener {
|
||||||
|
|
||||||
|
@Event
|
||||||
|
void onExceptionEvent(ExceptionEvent exceptionEvent) {
|
||||||
|
assertEquals(bus, exceptionEvent.getEventBus());
|
||||||
|
assertEquals(event, exceptionEvent.getEvent());
|
||||||
|
assertEquals(exception, exceptionEvent.getCause());
|
||||||
|
exceptionEventHandled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,73 @@
|
|||||||
|
package dev.kske.eventbus.core;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests nested event dispatches.
|
||||||
|
*
|
||||||
|
* @author Kai S. K. Engelbart
|
||||||
|
* @since 1.2.0
|
||||||
|
*/
|
||||||
|
public class NestedTest {
|
||||||
|
|
||||||
|
EventBus bus;
|
||||||
|
boolean nestedHit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an event bus and registers this test instance as an event listener.
|
||||||
|
*
|
||||||
|
* @since 1.2.0
|
||||||
|
*/
|
||||||
|
@BeforeEach
|
||||||
|
public void registerListener() {
|
||||||
|
bus = new EventBus();
|
||||||
|
bus.registerListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatches a simple event, which should in turn cause a string to be dispatched as a nested
|
||||||
|
* event. If the corresponding handler sets {@link #nestedHit} to {@code true}, the test is
|
||||||
|
* successful.
|
||||||
|
*
|
||||||
|
* @since 1.2.0
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testNestedDispatch() {
|
||||||
|
bus.dispatch(new SimpleEvent());
|
||||||
|
assertTrue(nestedHit);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatches a string as a nested event and cancels the current dispatch afterwards.
|
||||||
|
*
|
||||||
|
* @since 1.2.0
|
||||||
|
*/
|
||||||
|
@Event(SimpleEvent.class)
|
||||||
|
void onSimpleEvent() {
|
||||||
|
bus.dispatch("Nested event");
|
||||||
|
bus.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets {@link #nestedHit} to {@code true} indicating that nested dispatches work.
|
||||||
|
*
|
||||||
|
* @since 1.2.0
|
||||||
|
*/
|
||||||
|
@Event(String.class)
|
||||||
|
void onString() {
|
||||||
|
nestedHit = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fails the test if an exception is caused during the dispatch.
|
||||||
|
*
|
||||||
|
* @param e the event containing the exception
|
||||||
|
* @since 1.2.0
|
||||||
|
*/
|
||||||
|
@Event
|
||||||
|
void onException(ExceptionEvent e) {
|
||||||
|
fail("Exception during dispatch", e.getCause());
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package dev.kske.eventbus;
|
package dev.kske.eventbus.core;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A simple event for testing purposes.
|
* A simple event for testing purposes.
|
||||||
@ -6,4 +6,4 @@ package dev.kske.eventbus;
|
|||||||
* @author Kai S. K. Engelbart
|
* @author Kai S. K. Engelbart
|
||||||
* @since 0.0.1
|
* @since 0.0.1
|
||||||
*/
|
*/
|
||||||
public class SimpleEvent implements IEvent {}
|
public class SimpleEvent {}
|
@ -1,4 +1,4 @@
|
|||||||
package dev.kske.eventbus;
|
package dev.kske.eventbus.core;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Subclass of {@link SimpleEvent} for testing purposes.
|
* Subclass of {@link SimpleEvent} for testing purposes.
|
32
event-bus-proc/.classpath
Normal file
32
event-bus-proc/.classpath
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<classpath>
|
||||||
|
<classpathentry kind="src" output="target/classes" path="src/main/java">
|
||||||
|
<attributes>
|
||||||
|
<attribute name="optional" value="true"/>
|
||||||
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
|
</attributes>
|
||||||
|
</classpathentry>
|
||||||
|
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
|
||||||
|
<attributes>
|
||||||
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
|
</attributes>
|
||||||
|
</classpathentry>
|
||||||
|
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
|
||||||
|
<attributes>
|
||||||
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
|
</attributes>
|
||||||
|
</classpathentry>
|
||||||
|
<classpathentry kind="src" output="target/test-classes" path="home/kske/git/event-bus/event-bus-ap">
|
||||||
|
<attributes>
|
||||||
|
<attribute name="optional" value="true"/>
|
||||||
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
|
<attribute name="test" value="true"/>
|
||||||
|
</attributes>
|
||||||
|
</classpathentry>
|
||||||
|
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
|
||||||
|
<attributes>
|
||||||
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
|
</attributes>
|
||||||
|
</classpathentry>
|
||||||
|
<classpathentry kind="output" path="target/classes"/>
|
||||||
|
</classpath>
|
23
event-bus-proc/.project
Normal file
23
event-bus-proc/.project
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<projectDescription>
|
||||||
|
<name>event-bus-proc</name>
|
||||||
|
<comment></comment>
|
||||||
|
<projects>
|
||||||
|
</projects>
|
||||||
|
<buildSpec>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
</buildSpec>
|
||||||
|
<natures>
|
||||||
|
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||||
|
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||||
|
</natures>
|
||||||
|
</projectDescription>
|
75
event-bus-proc/pom.xml
Normal file
75
event-bus-proc/pom.xml
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<artifactId>event-bus-proc</artifactId>
|
||||||
|
|
||||||
|
<name>Event Bus Annotation Processor</name>
|
||||||
|
<description>Annotation processor checking for errors related to the @Event annotation from Event Bus.</description>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>dev.kske</groupId>
|
||||||
|
<artifactId>event-bus</artifactId>
|
||||||
|
<version>1.1.0</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>dev.kske</groupId>
|
||||||
|
<artifactId>event-bus-core</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
|
||||||
|
<!-- Disable test folder -->
|
||||||
|
<testSourceDirectory />
|
||||||
|
|
||||||
|
<plugins>
|
||||||
|
|
||||||
|
<!-- Prevent annotation processing error during compilation -->
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<compilerArgument>-proc:none</compilerArgument>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
<!-- Include event-bus-core classes into JAR -->
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-shade-plugin</artifactId>
|
||||||
|
<version>3.2.4</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>shade</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<minimizeJar>true</minimizeJar>
|
||||||
|
<filters>
|
||||||
|
<filter>
|
||||||
|
<artifact>dev.kske:event-bus-core</artifact>
|
||||||
|
<excludes>
|
||||||
|
<exclude>META-INF/MANIFEST.MF</exclude>
|
||||||
|
</excludes>
|
||||||
|
</filter>
|
||||||
|
<filter>
|
||||||
|
<artifact>*:*</artifact>
|
||||||
|
<excludes>
|
||||||
|
<exclude>module-info.class</exclude>
|
||||||
|
<exclude>META-INF/maven/**</exclude>
|
||||||
|
</excludes>
|
||||||
|
</filter>
|
||||||
|
</filters>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
|
||||||
|
</build>
|
||||||
|
</project>
|
@ -0,0 +1,155 @@
|
|||||||
|
package dev.kske.eventbus.proc;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import javax.annotation.processing.*;
|
||||||
|
import javax.lang.model.SourceVersion;
|
||||||
|
import javax.lang.model.element.*;
|
||||||
|
import javax.lang.model.type.*;
|
||||||
|
import javax.tools.Diagnostic.Kind;
|
||||||
|
|
||||||
|
import dev.kske.eventbus.core.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This annotation processor checks event handlers for common mistakes which can only be detected
|
||||||
|
* during runtime otherwise.
|
||||||
|
*
|
||||||
|
* @author Kai S. K. Engelbart
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
@SupportedAnnotationTypes("dev.kske.eventbus.core.Event")
|
||||||
|
@SupportedSourceVersion(SourceVersion.RELEASE_11)
|
||||||
|
public class EventProcessor extends AbstractProcessor {
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@Override
|
||||||
|
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
|
||||||
|
if (!roundEnv.errorRaised() && !roundEnv.processingOver())
|
||||||
|
processRound(
|
||||||
|
(Set<ExecutableElement>) roundEnv.getElementsAnnotatedWith(Event.class));
|
||||||
|
|
||||||
|
// Do not claim the processed annotations
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processRound(Set<ExecutableElement> eventHandlers) {
|
||||||
|
for (ExecutableElement eventHandler : eventHandlers) {
|
||||||
|
Event eventAnnotation = eventHandler.getAnnotation(Event.class);
|
||||||
|
TypeMirror eventType;
|
||||||
|
|
||||||
|
// Determine the event type and how it is defined
|
||||||
|
boolean useParameter;
|
||||||
|
try {
|
||||||
|
eventAnnotation.value();
|
||||||
|
throw new EventBusException(
|
||||||
|
"Could not determine event type of handler " + eventHandler);
|
||||||
|
} catch (MirroredTypeException e) {
|
||||||
|
|
||||||
|
// Task failed successfully
|
||||||
|
eventType = e.getTypeMirror();
|
||||||
|
useParameter = processingEnv.getTypeUtils().isSameType(eventType,
|
||||||
|
getTypeMirror(Event.USE_PARAMETER.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check handler signature
|
||||||
|
boolean pass = false;
|
||||||
|
if (useParameter && eventHandler.getParameters().size() == 0)
|
||||||
|
error(eventHandler, "The method or the annotation must define the event type");
|
||||||
|
else if (!useParameter && eventHandler.getParameters().size() == 1)
|
||||||
|
error(eventHandler,
|
||||||
|
"Either the method or the annotation must define the event type");
|
||||||
|
else if (eventHandler.getParameters().size() > 1)
|
||||||
|
error(eventHandler, "Method must not have more than one parameter");
|
||||||
|
else
|
||||||
|
pass = true;
|
||||||
|
|
||||||
|
// Warn the user about unused return values
|
||||||
|
if (useParameter && eventHandler.getReturnType().getKind() != TypeKind.VOID)
|
||||||
|
warning(eventHandler, "Unused return value");
|
||||||
|
|
||||||
|
// Abort checking if the handler signature is incorrect
|
||||||
|
if (!pass)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Additional checks if parameter is used
|
||||||
|
if (useParameter) {
|
||||||
|
VariableElement paramElement = eventHandler.getParameters().get(0);
|
||||||
|
eventType = paramElement.asType();
|
||||||
|
|
||||||
|
// Check if parameter is object
|
||||||
|
// Abort checking otherwise
|
||||||
|
if (eventType.getKind() != TypeKind.DECLARED) {
|
||||||
|
error(paramElement, "Event must be an object");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
Element eventElement = ((DeclaredType) eventType).asElement();
|
||||||
|
|
||||||
|
// Check for handlers for abstract types that aren't polymorphic
|
||||||
|
if (!polymorphic && (eventElement.getKind() == ElementKind.INTERFACE
|
||||||
|
|| eventElement.getModifiers().contains(Modifier.ABSTRACT)))
|
||||||
|
warning(eventHandler,
|
||||||
|
"Parameter should be instantiable or handler should use @Polymorphic");
|
||||||
|
|
||||||
|
// Check for handlers for final types that are polymorphic
|
||||||
|
else if (polymorphic && eventElement.getModifiers().contains(Modifier.FINAL))
|
||||||
|
warning(eventHandler,
|
||||||
|
"@Polymorphic should be removed as parameter cannot be subclassed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private TypeMirror getTypeMirror(Class<?> clazz) {
|
||||||
|
return getTypeElement(clazz).asType();
|
||||||
|
}
|
||||||
|
|
||||||
|
private TypeElement getTypeElement(Class<?> clazz) {
|
||||||
|
return processingEnv.getElementUtils().getTypeElement(clazz.getCanonicalName());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void warning(Element e, String msg, Object... args) {
|
||||||
|
processingEnv.getMessager().printMessage(Kind.WARNING, String.format(msg, args), e);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void error(Element e, String msg, Object... args) {
|
||||||
|
processingEnv.getMessager().printMessage(Kind.ERROR, String.format(msg, args), e);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* Contains the Event Bus annotation processor.
|
||||||
|
*
|
||||||
|
* @author Kai S. K. Engelbart
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
package dev.kske.eventbus.proc;
|
12
event-bus-proc/src/main/java/module-info.java
Normal file
12
event-bus-proc/src/main/java/module-info.java
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
/**
|
||||||
|
* Contains an annotation processor for checking for errors related to the
|
||||||
|
* {@link dev.kske.eventbus.core.Event} annotation from Event Bus.
|
||||||
|
*
|
||||||
|
* @author Kai S. K. Engelbart
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
module dev.kske.eventbus.ap {
|
||||||
|
|
||||||
|
requires java.compiler;
|
||||||
|
requires dev.kske.eventbus.core;
|
||||||
|
}
|
@ -0,0 +1 @@
|
|||||||
|
dev.kske.eventbus.proc.EventProcessor
|
104
pom.xml
104
pom.xml
@ -5,16 +5,24 @@
|
|||||||
|
|
||||||
<groupId>dev.kske</groupId>
|
<groupId>dev.kske</groupId>
|
||||||
<artifactId>event-bus</artifactId>
|
<artifactId>event-bus</artifactId>
|
||||||
<version>0.1.0</version>
|
<version>1.1.0</version>
|
||||||
|
<packaging>pom</packaging>
|
||||||
|
|
||||||
<name>Event Bus</name>
|
<name>Event Bus</name>
|
||||||
<description>An event handling framework for Java utilizing annotations.</description>
|
<description>An event handling library for Java utilizing annotations.</description>
|
||||||
<url>https://git.kske.dev/zdm/event-bus</url>
|
<url>https://git.kske.dev/kske/event-bus</url>
|
||||||
|
|
||||||
|
<modules>
|
||||||
|
<module>event-bus-core</module>
|
||||||
|
<module>event-bus-proc</module>
|
||||||
|
</modules>
|
||||||
|
|
||||||
<licenses>
|
<licenses>
|
||||||
<license>
|
<license>
|
||||||
<name>MIT License</name>
|
<name>Apache License, Version 2.0</name>
|
||||||
<url>http://www.opensource.org/licenses/mit-license.php</url>
|
<url>https://www.apache.org/licenses/LICENSE-2.0.txt</url>
|
||||||
|
<distribution>repo</distribution>
|
||||||
|
<comments>A business-friendly OSS license</comments>
|
||||||
</license>
|
</license>
|
||||||
</licenses>
|
</licenses>
|
||||||
|
|
||||||
@ -32,9 +40,23 @@
|
|||||||
</developer>
|
</developer>
|
||||||
</developers>
|
</developers>
|
||||||
|
|
||||||
|
<contributors>
|
||||||
|
<contributor>
|
||||||
|
<name>Leon Hofmeister</name>
|
||||||
|
<email>leon@kske.dev</email>
|
||||||
|
<url>https://git.kske.dev/delvh</url>
|
||||||
|
<roles>
|
||||||
|
<role>qa</role>
|
||||||
|
<role>tester</role>
|
||||||
|
</roles>
|
||||||
|
<timezone>Europe/Berlin</timezone>
|
||||||
|
</contributor>
|
||||||
|
</contributors>
|
||||||
|
|
||||||
<scm>
|
<scm>
|
||||||
<connection>scm:git:https://git.kske.dev/zdm/event-bus.git</connection>
|
<connection>scm:git:https://git.kske.dev/kske/event-bus.git</connection>
|
||||||
<developerConnection>scm:git:ssh:git@git.kske.dev:zdm/event-bus.git</developerConnection>
|
<developerConnection>scm:git:ssh://git@git.kske.dev:420/kske/event-bus.git</developerConnection>
|
||||||
|
<url>https://git.kske.dev/kske/event-bus</url>
|
||||||
</scm>
|
</scm>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
@ -44,10 +66,21 @@
|
|||||||
<maven.compiler.target>11</maven.compiler.target>
|
<maven.compiler.target>11</maven.compiler.target>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
|
<!-- Configure deployment to OSSRH -->
|
||||||
|
<distributionManagement>
|
||||||
|
<snapshotRepository>
|
||||||
|
<id>ossrh</id>
|
||||||
|
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
|
||||||
|
</snapshotRepository>
|
||||||
|
<repository>
|
||||||
|
<id>ossrh</id>
|
||||||
|
<url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
|
||||||
|
</repository>
|
||||||
|
</distributionManagement>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|
||||||
<!-- Disable resource folders -->
|
<!-- Disable test resource folder -->
|
||||||
<resources />
|
|
||||||
<testResources />
|
<testResources />
|
||||||
|
|
||||||
<plugins>
|
<plugins>
|
||||||
@ -59,7 +92,7 @@
|
|||||||
<version>3.8.1</version>
|
<version>3.8.1</version>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
|
||||||
<!-- Attach sources and Javadoc to JAR -->
|
<!-- Attach sources 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>
|
||||||
@ -73,6 +106,8 @@
|
|||||||
</execution>
|
</execution>
|
||||||
</executions>
|
</executions>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
|
||||||
|
<!-- Attach Javadoc to JAR -->
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-javadoc-plugin</artifactId>
|
<artifactId>maven-javadoc-plugin</artifactId>
|
||||||
@ -85,17 +120,46 @@
|
|||||||
</goals>
|
</goals>
|
||||||
</execution>
|
</execution>
|
||||||
</executions>
|
</executions>
|
||||||
|
|
||||||
|
<!-- Support JDK-style Javadoc tags -->
|
||||||
|
<configuration>
|
||||||
|
<tags>
|
||||||
|
<tag>
|
||||||
|
<name>apiNote</name>
|
||||||
|
<placement>a</placement>
|
||||||
|
<head>API Note:</head>
|
||||||
|
</tag>
|
||||||
|
<tag>
|
||||||
|
<name>implSpec</name>
|
||||||
|
<placement>a</placement>
|
||||||
|
<head>Implementation Requirements:</head>
|
||||||
|
</tag>
|
||||||
|
<tag>
|
||||||
|
<name>implNote</name>
|
||||||
|
<placement>a</placement>
|
||||||
|
<head>Implementation Note:</head>
|
||||||
|
</tag>
|
||||||
|
</tags>
|
||||||
|
</configuration>
|
||||||
|
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
<!-- GPG sign JAR -->
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-gpg-plugin</artifactId>
|
||||||
|
<version>1.6</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>sign-artifacts</id>
|
||||||
|
<phase>verify</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>sign</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
|
</project>
|
||||||
<dependencies>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.junit.jupiter</groupId>
|
|
||||||
<artifactId>junit-jupiter-api</artifactId>
|
|
||||||
<version>5.6.2</version>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
</project>
|
|
||||||
|
@ -1,67 +0,0 @@
|
|||||||
package dev.kske.eventbus;
|
|
||||||
|
|
||||||
import static java.lang.annotation.ElementType.METHOD;
|
|
||||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
|
||||||
|
|
||||||
import java.lang.annotation.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates that a method is an event handler. To be successfully used as such, the method has to
|
|
||||||
* comply with the following specifications:
|
|
||||||
* <ul>
|
|
||||||
* <li>Declared inside a class that implements {@link EventListener}</li>
|
|
||||||
* <li>Specifying an event type by either
|
|
||||||
* <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>
|
|
||||||
* <li>Return type of {@code void}</li>
|
|
||||||
* </ul>
|
|
||||||
*
|
|
||||||
* @author Kai S. K. Engelbart
|
|
||||||
* @since 0.0.1
|
|
||||||
*/
|
|
||||||
@Documented
|
|
||||||
@Retention(RUNTIME)
|
|
||||||
@Target(METHOD)
|
|
||||||
public @interface Event {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Defines the priority of the event handler. Handlers are executed in descending order of their
|
|
||||||
* priority.
|
|
||||||
* <p>
|
|
||||||
* The execution order of handlers with the same priority is undefined.
|
|
||||||
*
|
|
||||||
* @return the priority of the event handler
|
|
||||||
* @since 0.0.1
|
|
||||||
*/
|
|
||||||
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.
|
|
||||||
* <p>
|
|
||||||
* This is useful when the event handler does not utilize the event instance.
|
|
||||||
*
|
|
||||||
* @return the event type accepted by the handler
|
|
||||||
* @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 {}
|
|
||||||
}
|
|
@ -1,213 +0,0 @@
|
|||||||
package dev.kske.eventbus;
|
|
||||||
|
|
||||||
import java.lang.System.Logger;
|
|
||||||
import java.lang.System.Logger.Level;
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Event listeners can be registered at an event bus to be notified when an event is dispatched.
|
|
||||||
* <p>
|
|
||||||
* A singleton instance of this class can be lazily created and acquired using the
|
|
||||||
* {@link EventBus#getInstance()} method.
|
|
||||||
* <p>
|
|
||||||
* This is a thread-safe implementation.
|
|
||||||
*
|
|
||||||
* @author Kai S. K. Engelbart
|
|
||||||
* @since 0.0.1
|
|
||||||
* @see Event
|
|
||||||
*/
|
|
||||||
public final class EventBus {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Holds the state of the dispatching process on one thread.
|
|
||||||
*
|
|
||||||
* @since 0.1.0
|
|
||||||
*/
|
|
||||||
private static final class DispatchState {
|
|
||||||
|
|
||||||
boolean isDispatching, isCancelled;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static volatile EventBus singletonInstance;
|
|
||||||
|
|
||||||
private static final Logger logger = System.getLogger(EventBus.class.getName());
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Produces a singleton instance of the event bus. It is lazily initialized on the first call.
|
|
||||||
*
|
|
||||||
* @return a singleton instance of the event bus.
|
|
||||||
* @since 0.0.2
|
|
||||||
*/
|
|
||||||
public static EventBus getInstance() {
|
|
||||||
EventBus instance = singletonInstance;
|
|
||||||
if (instance == null)
|
|
||||||
synchronized (EventBus.class) {
|
|
||||||
if ((instance = singletonInstance) == null) {
|
|
||||||
logger.log(Level.DEBUG, "Initializing singleton event bus instance");
|
|
||||||
instance = singletonInstance = new EventBus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
private final Map<Class<? extends IEvent>, TreeSet<EventHandler>> bindings
|
|
||||||
= new ConcurrentHashMap<>();
|
|
||||||
private final Set<EventListener> registeredListeners = ConcurrentHashMap.newKeySet();
|
|
||||||
private final ThreadLocal<DispatchState> dispatchState
|
|
||||||
= ThreadLocal.withInitial(DispatchState::new);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dispatches an event to all event handlers registered for it in descending order of their
|
|
||||||
* priority.
|
|
||||||
*
|
|
||||||
* @param event the event to dispatch
|
|
||||||
* @since 0.0.1
|
|
||||||
*/
|
|
||||||
public void dispatch(IEvent event) {
|
|
||||||
Objects.requireNonNull(event);
|
|
||||||
logger.log(Level.INFO, "Dispatching event {0}", event);
|
|
||||||
|
|
||||||
// Set dispatch state
|
|
||||||
var state = dispatchState.get();
|
|
||||||
state.isDispatching = true;
|
|
||||||
|
|
||||||
for (var handler : getHandlersFor(event.getClass()))
|
|
||||||
if (state.isCancelled) {
|
|
||||||
logger.log(Level.INFO, "Cancelled dispatching event {0}", event);
|
|
||||||
state.isCancelled = false;
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
handler.execute(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset dispatch state
|
|
||||||
state.isDispatching = false;
|
|
||||||
|
|
||||||
logger.log(Level.DEBUG, "Finished dispatching event {0}", event);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Searches for the event handlers bound to an event class.
|
|
||||||
*
|
|
||||||
* @param eventClass the event class to use for the search
|
|
||||||
* @return all event handlers registered for the event class
|
|
||||||
* @since 0.0.1
|
|
||||||
*/
|
|
||||||
private List<EventHandler> getHandlersFor(Class<? extends IEvent> eventClass) {
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cancels an event that is currently dispatched from inside an event handler.
|
|
||||||
*
|
|
||||||
* @throws EventBusException if the calling thread is not an active dispatching thread
|
|
||||||
* @since 0.1.0
|
|
||||||
*/
|
|
||||||
public void cancel() {
|
|
||||||
var state = dispatchState.get();
|
|
||||||
if (state.isDispatching && !state.isCancelled)
|
|
||||||
state.isCancelled = true;
|
|
||||||
else
|
|
||||||
throw new EventBusException("Calling thread not an active dispatching thread!");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Registers an event listener at this event bus.
|
|
||||||
*
|
|
||||||
* @param listener the listener to register
|
|
||||||
* @throws EventBusException if the listener is already registered or a declared event handler
|
|
||||||
* does not comply with the specification
|
|
||||||
* @since 0.0.1
|
|
||||||
* @see Event
|
|
||||||
*/
|
|
||||||
public void registerListener(EventListener listener) throws EventBusException {
|
|
||||||
Objects.requireNonNull(listener);
|
|
||||||
if (registeredListeners.contains(listener))
|
|
||||||
throw new EventBusException(listener + " already registered!");
|
|
||||||
logger.log(Level.INFO, "Registering event listener {0}", listener.getClass().getName());
|
|
||||||
boolean handlerBound = false;
|
|
||||||
|
|
||||||
registeredListeners.add(listener);
|
|
||||||
for (var method : listener.getClass().getDeclaredMethods()) {
|
|
||||||
Event annotation = method.getAnnotation(Event.class);
|
|
||||||
|
|
||||||
// Skip methods without annotations
|
|
||||||
if (annotation == null)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// Initialize and bind the handler
|
|
||||||
var handler = new EventHandler(listener, method, annotation);
|
|
||||||
if (!bindings.containsKey(handler.getEventType()))
|
|
||||||
bindings.put(handler.getEventType(), new TreeSet<>());
|
|
||||||
logger.log(Level.DEBUG, "Binding event handler {0}", handler);
|
|
||||||
bindings.get(handler.getEventType())
|
|
||||||
.add(handler);
|
|
||||||
handlerBound = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!handlerBound)
|
|
||||||
logger.log(
|
|
||||||
Level.WARNING,
|
|
||||||
"No event handlers bound for event listener {0}",
|
|
||||||
listener.getClass().getName()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes a specific listener from this event bus.
|
|
||||||
*
|
|
||||||
* @param listener the listener to remove
|
|
||||||
* @since 0.0.1
|
|
||||||
*/
|
|
||||||
public void removeListener(EventListener listener) {
|
|
||||||
Objects.requireNonNull(listener);
|
|
||||||
logger.log(Level.INFO, "Removing event listener {0}", listener.getClass().getName());
|
|
||||||
|
|
||||||
for (var binding : bindings.values()) {
|
|
||||||
var it = binding.iterator();
|
|
||||||
while (it.hasNext()) {
|
|
||||||
var handler = it.next();
|
|
||||||
if (handler.getListener() == listener) {
|
|
||||||
logger.log(Level.DEBUG, "Unbinding event handler {0}", handler);
|
|
||||||
it.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
registeredListeners.remove(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes all event listeners from this event bus.
|
|
||||||
*
|
|
||||||
* @since 0.0.1
|
|
||||||
*/
|
|
||||||
public void clearListeners() {
|
|
||||||
logger.log(Level.INFO, "Clearing event listeners");
|
|
||||||
bindings.clear();
|
|
||||||
registeredListeners.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides an unmodifiable view of the event listeners registered at this event bus.
|
|
||||||
*
|
|
||||||
* @return all registered event listeners
|
|
||||||
* @since 0.0.1
|
|
||||||
*/
|
|
||||||
public Set<EventListener> getRegisteredListeners() {
|
|
||||||
return Collections.unmodifiableSet(registeredListeners);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,136 +0,0 @@
|
|||||||
package dev.kske.eventbus;
|
|
||||||
|
|
||||||
import java.lang.reflect.*;
|
|
||||||
|
|
||||||
import dev.kske.eventbus.Event.USE_PARAMETER;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Internal representation of an event handling method.
|
|
||||||
*
|
|
||||||
* @author Kai S. K. Engelbart
|
|
||||||
* @since 0.0.1
|
|
||||||
* @see EventBus
|
|
||||||
*/
|
|
||||||
final class EventHandler implements Comparable<EventHandler> {
|
|
||||||
|
|
||||||
private final EventListener listener;
|
|
||||||
private final Method method;
|
|
||||||
private final Event annotation;
|
|
||||||
private final Class<? extends IEvent> eventType;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs an event handler.
|
|
||||||
*
|
|
||||||
* @param listener the listener containing the handler
|
|
||||||
* @param method the handler method
|
|
||||||
* @param annotation the event annotation
|
|
||||||
* @throws EventBusException if the method or the annotation do not comply with the
|
|
||||||
* specification
|
|
||||||
* @since 0.0.1
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
EventHandler(EventListener listener, Method method, Event annotation) throws EventBusException {
|
|
||||||
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))
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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.
|
|
||||||
*
|
|
||||||
* @since 0.0.1
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public int compareTo(EventHandler other) {
|
|
||||||
int priority = other.annotation.priority() - annotation.priority();
|
|
||||||
if (priority == 0)
|
|
||||||
priority = listener.hashCode() - other.listener.hashCode();
|
|
||||||
return priority == 0 ? hashCode() - other.hashCode() : priority;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return String.format("EventHandler[method=%s, annotation=%s]", method, annotation);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Executes the event handler.
|
|
||||||
*
|
|
||||||
* @param event the event used as the method parameter
|
|
||||||
* @throws EventBusException if the handler throws an exception
|
|
||||||
* @since 0.0.1
|
|
||||||
*/
|
|
||||||
void execute(IEvent event) throws EventBusException {
|
|
||||||
try {
|
|
||||||
if (annotation.eventType().equals(USE_PARAMETER.class))
|
|
||||||
method.invoke(listener, event);
|
|
||||||
else
|
|
||||||
method.invoke(listener);
|
|
||||||
} catch (
|
|
||||||
IllegalAccessException
|
|
||||||
| IllegalArgumentException
|
|
||||||
| InvocationTargetException e
|
|
||||||
) {
|
|
||||||
throw new EventBusException("Failed to invoke event handler!", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the listener containing this handler
|
|
||||||
* @since 0.0.1
|
|
||||||
*/
|
|
||||||
EventListener getListener() { return listener; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the event annotation
|
|
||||||
* @since 0.0.1
|
|
||||||
*/
|
|
||||||
Event getAnnotation() { return annotation; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the priority of the event annotation
|
|
||||||
* @since 0.0.1
|
|
||||||
*/
|
|
||||||
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; }
|
|
||||||
}
|
|
@ -1,12 +0,0 @@
|
|||||||
package dev.kske.eventbus;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Marker interface for event listeners. Event listeners can contain event handling methods to which
|
|
||||||
* events can be dispatched.
|
|
||||||
*
|
|
||||||
* @author Kai S. K. Engelbart
|
|
||||||
* @since 0.0.1
|
|
||||||
* @see Event
|
|
||||||
* @see EventBus
|
|
||||||
*/
|
|
||||||
public interface EventListener {}
|
|
@ -1,12 +0,0 @@
|
|||||||
package dev.kske.eventbus;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Marker interface for event objects. Event objects can be used as event handler parameters and
|
|
||||||
* thus can be dispatched to the event bus.
|
|
||||||
*
|
|
||||||
* @author Kai S. K. Engelbart
|
|
||||||
* @since 0.0.1
|
|
||||||
* @see Event
|
|
||||||
* @see EventBus
|
|
||||||
*/
|
|
||||||
public interface IEvent {}
|
|
@ -1,9 +0,0 @@
|
|||||||
/**
|
|
||||||
* Contains the public API and implementation of the event bus library.
|
|
||||||
*
|
|
||||||
* @author Kai S. K. Engelbart
|
|
||||||
* @since 0.0.1
|
|
||||||
* @see dev.kske.eventbus.Event
|
|
||||||
* @see dev.kske.eventbus.EventBus
|
|
||||||
*/
|
|
||||||
package dev.kske.eventbus;
|
|
@ -1,12 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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,58 +0,0 @@
|
|||||||
package dev.kske.eventbus;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests the dispatching mechanism of the event bus.
|
|
||||||
*
|
|
||||||
* @author Kai S. K. Engelbart
|
|
||||||
* @since 0.0.1
|
|
||||||
*/
|
|
||||||
class DispatchTest implements EventListener {
|
|
||||||
|
|
||||||
EventBus bus;
|
|
||||||
static int hits;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs an event bus and registers this test instance as an event listener.
|
|
||||||
*
|
|
||||||
* @since 0.0.1
|
|
||||||
*/
|
|
||||||
@BeforeEach
|
|
||||||
void registerListener() {
|
|
||||||
bus = new EventBus();
|
|
||||||
bus.registerListener(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests {@link EventBus#dispatch(IEvent)} with multiple handler priorities, a subtype handler
|
|
||||||
* and a static handler.
|
|
||||||
*
|
|
||||||
* @since 0.0.1
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
void testDispatch() {
|
|
||||||
bus.dispatch(new SimpleEventSub());
|
|
||||||
bus.dispatch(new SimpleEvent());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Event(eventType = SimpleEvent.class, includeSubtypes = true, priority = 200)
|
|
||||||
void onSimpleEventFirst() {
|
|
||||||
++hits;
|
|
||||||
assertTrue(hits == 1 || hits == 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Event(eventType = SimpleEvent.class, priority = 150)
|
|
||||||
static void onSimpleEventSecond() {
|
|
||||||
++hits;
|
|
||||||
assertEquals(3, hits);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Event(priority = 100)
|
|
||||||
void onSimpleEventThird(SimpleEvent event) {
|
|
||||||
++hits;
|
|
||||||
assertEquals(4, hits);
|
|
||||||
}
|
|
||||||
}
|
|
Reference in New Issue
Block a user