Compare commits
	
		
			61 Commits
		
	
	
		
			0.0.2
			...
			b758f4cef1
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						
						
							
						
						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
	
				 | 
					
					
						|||
| 
						
						
							
						
						748cb8b71a
	
				 | 
					
					
						|||
| 
						
						
							
						
						0e5f31b63e
	
				 | 
					
					
						|||
| 
						
						
							
						
						ec73be9046
	
				 | 
					
					
						|||
| 
						
						
							
						
						659bd7888f
	
				 | 
					
					
						|||
| 
						
						
							
						
						8aefb43823
	
				 | 
					
					
						|||
| 
						
						
							
						
						9d1707de5b
	
				 | 
					
					
						|||
| 
						
						
							
						
						1d2102d729
	
				 | 
					
					
						|||
| cd2598d5d3 | |||
| 
						
						
							
						
						f6e5c90a44
	
				 | 
					
					
						|||
| 
						
						
							
						
						dbb816c6cb
	
				 | 
					
					
						|||
| 603a838640 | |||
| 
						
						
							
						
						b6b73d335a
	
				 | 
					
					
						|||
| 8cf51441ad | |||
| 
						
						
							
						
						001c0eea7e
	
				 | 
					
					
						|||
| 
						
						
							
						
						ba06b49368
	
				 | 
					
					
						|||
| 
						
						
							
						
						7a3debe444
	
				 | 
					
					
						|||
| 
						
						
							
						
						5f88ad6095
	
				 | 
					
					
						
							
								
								
									
										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.
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										187
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										187
									
								
								README.md
									
									
									
									
									
								
							@@ -3,33 +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.
 | 
				
			||||||
Additionally, the class containing the method must implement the `EventListener` interface.
 | 
					Alternatively, a parameter-less event handler can be declared as shown [below](#parameter-less-event-handlers).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 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() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -47,26 +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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```java
 | 
				
			||||||
 | 
					@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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Polymorphic Event Handlers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					On certain occasions it's practical for an event handler to accept both events of the specified type, as well as subclasses of that event.
 | 
				
			||||||
 | 
					To include subtypes for an event handler, use the `@Polymorphic` annotation in addition to `@Event`:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```java
 | 
				
			||||||
 | 
					@Event
 | 
				
			||||||
 | 
					@Polymorphic
 | 
				
			||||||
 | 
					private void onSimpleEvent(SimpleEvent event) { ... }
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Event Handler Execution Order
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Sometimes when using multiple handlers for one event, it might be useful to define in which order they will be executed.
 | 
				
			||||||
 | 
					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`:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```java
 | 
				
			||||||
 | 
					@Event
 | 
				
			||||||
 | 
					@Priority(250)
 | 
				
			||||||
 | 
					private void onSimpleEvent(SimpleEvent event) { ... }
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Important:**
 | 
				
			||||||
 | 
					Events are dispatched to handlers in descending order of their priority.
 | 
				
			||||||
 | 
					The execution order is undefined for handlers with the same priority.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Parameter-Less Event Handlers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					In some cases an event handler is not interested in the dispatched event instance.
 | 
				
			||||||
 | 
					To avoid declaring a useless parameter just to specify the event type of the handler, there is an alternative:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```java
 | 
				
			||||||
 | 
					@Event(SimpleEvent.class)
 | 
				
			||||||
 | 
					private void onSimpleEvent() {
 | 
				
			||||||
 | 
						System.out.println("SimpleEvent received!");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Make sure that you **do not** both declare a parameter and specify the event type in the annotation, as this would be ambiguous.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Listener-Level Properties
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					When defining a dedicated event listener that, for example, performs pre- or post-processing, all event handlers will probably have the same non-standard priority.
 | 
				
			||||||
 | 
					Instead of defining that priority for each handler, it can be defined at the listener level by annotating the listener itself.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The same applies to polymorphism.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Event Consumption
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					In some cases it might be useful to stop the propagation of an event.
 | 
				
			||||||
 | 
					Event Bus makes this possible with event consumption:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```java
 | 
				
			||||||
 | 
					@Event(SimpleEvent.class)
 | 
				
			||||||
 | 
					@Priority(100)
 | 
				
			||||||
 | 
					private void onSimpleEvent() {
 | 
				
			||||||
 | 
						EventBus.getInstance().cancel();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Event(SimpleEvent.class)
 | 
				
			||||||
 | 
					@Priority(50)
 | 
				
			||||||
 | 
					private void onSimpleEvent2() {
 | 
				
			||||||
 | 
						System.out.println("Will not be printed!");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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.
 | 
				
			||||||
 | 
					This applies to all event handlers that would have been executed after the one consuming the event.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Important:**
 | 
				
			||||||
 | 
					Avoid cancelling events while using multiple event handlers with the same priority.
 | 
				
			||||||
 | 
					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.0.2</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>
 | 
				
			||||||
							
								
								
									
										30
									
								
								event-bus-core/pom.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								event-bus-core/pom.xml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,30 @@
 | 
				
			|||||||
 | 
					<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.6.2</version>
 | 
				
			||||||
 | 
								<scope>test</scope>
 | 
				
			||||||
 | 
							</dependency>
 | 
				
			||||||
 | 
						</dependencies>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						<build>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							<!-- Disable resource folder -->
 | 
				
			||||||
 | 
							<resources />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						</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 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 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,257 @@
 | 
				
			|||||||
 | 
					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;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 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;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * The priority assigned to every event handler without an explicitly defined priority.
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
 | 
						 * @since 1.1.0
 | 
				
			||||||
 | 
						 * @see Priority
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public static final int DEFAULT_PRIORITY = 100;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private static volatile EventBus singletonInstance;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private static final Logger logger = System.getLogger(EventBus.class.getName());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * 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<?>, TreeSet<EventHandler>>	bindings			=
 | 
				
			||||||
 | 
							new ConcurrentHashMap<>();
 | 
				
			||||||
 | 
						private final Set<Object>							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
 | 
				
			||||||
 | 
						 * @throws EventBusException if an event handler isn't accessible or has an invalid signature
 | 
				
			||||||
 | 
						 * @since 0.0.1
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public void dispatch(Object event) throws EventBusException {
 | 
				
			||||||
 | 
							Objects.requireNonNull(event);
 | 
				
			||||||
 | 
							logger.log(Level.INFO, "Dispatching event {0}", event);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Set dispatch state
 | 
				
			||||||
 | 
							var state = dispatchState.get();
 | 
				
			||||||
 | 
							state.isDispatching = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							Iterator<EventHandler> handlers = getHandlersFor(event.getClass());
 | 
				
			||||||
 | 
							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 (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 if (e.getCause() instanceof Error)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
												// Transparently pass error to the caller
 | 
				
			||||||
 | 
												throw (Error) e.getCause();
 | 
				
			||||||
 | 
											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));
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 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. This includes polymorphic handlers
 | 
				
			||||||
 | 
						 * that are bound to a supertype of the event class.
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
 | 
						 * @param eventClass the event class to use for the search
 | 
				
			||||||
 | 
						 * @return an iterator over the applicable handlers in descending order of priority
 | 
				
			||||||
 | 
						 * @since 0.0.1
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						private Iterator<EventHandler> getHandlersFor(Class<?> eventClass) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Get handlers defined for the event class
 | 
				
			||||||
 | 
							TreeSet<EventHandler> handlers = bindings.getOrDefault(eventClass, new TreeSet<>());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Get polymorphic handlers
 | 
				
			||||||
 | 
							for (var binding : bindings.entrySet())
 | 
				
			||||||
 | 
								if (binding.getKey().isAssignableFrom(eventClass))
 | 
				
			||||||
 | 
									for (var handler : binding.getValue())
 | 
				
			||||||
 | 
										if (handler.isPolymorphic())
 | 
				
			||||||
 | 
											handlers.add(handler);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return handlers.iterator();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * 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(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
 | 
				
			||||||
 | 
								var handler = new EventHandler(listener, method, annotation, polymorphic, priority);
 | 
				
			||||||
 | 
								bindings.putIfAbsent(handler.getEventType(), new TreeSet<>());
 | 
				
			||||||
 | 
								logger.log(Level.DEBUG, "Binding event handler {0}", handler);
 | 
				
			||||||
 | 
								bindings.get(handler.getEventType())
 | 
				
			||||||
 | 
									.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(Object 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<Object> getRegisteredListeners() {
 | 
				
			||||||
 | 
							return Collections.unmodifiableSet(registeredListeners);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,33 @@
 | 
				
			|||||||
 | 
					package dev.kske.eventbus.core;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * This runtime exception is thrown when an event bus error occurs. This can
 | 
				
			||||||
 | 
					 * either occur while registering event listeners with invalid handlers, or when
 | 
				
			||||||
 | 
					 * an event handler throws an exception.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
 | 
					 * @since 0.0.1
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public class EventBusException extends RuntimeException {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private static final long serialVersionUID = 1L;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Creates a new event bus exception.
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
 | 
						 * @param message the message to display
 | 
				
			||||||
 | 
						 * @param cause   the cause of this exception
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public EventBusException(String message, Throwable cause) {
 | 
				
			||||||
 | 
							super(message, cause);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Creates a new event bus exception.
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
 | 
						 * @param message the message to display
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public EventBusException(String message) {
 | 
				
			||||||
 | 
							super(message);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,135 @@
 | 
				
			|||||||
 | 
					package dev.kske.eventbus.core;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.lang.reflect.*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import dev.kske.eventbus.core.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 Object	listener;
 | 
				
			||||||
 | 
						private final Method	method;
 | 
				
			||||||
 | 
						private final Class<?>	eventType;
 | 
				
			||||||
 | 
						private final boolean	useParameter;
 | 
				
			||||||
 | 
						private final boolean	polymorphic;
 | 
				
			||||||
 | 
						private final int		priority;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Constructs an 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 0.0.1
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						EventHandler(Object listener, Method method, Event annotation, boolean defPolymorphism,
 | 
				
			||||||
 | 
							int defPriority) throws EventBusException {
 | 
				
			||||||
 | 
							this.listener	= listener;
 | 
				
			||||||
 | 
							this.method		= method;
 | 
				
			||||||
 | 
							useParameter	= annotation.value() == USE_PARAMETER.class;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 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);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Compares this to another event handler based on 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 descending order of priority from a tree set.
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
 | 
						 * @since 0.0.1
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						@Override
 | 
				
			||||||
 | 
						public int compareTo(EventHandler other) {
 | 
				
			||||||
 | 
							int priority = other.priority - this.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, eventType=%s, useParameter=%b, polymorphic=%b, priority=%d]",
 | 
				
			||||||
 | 
								method, eventType, useParameter, polymorphic, priority);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * 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 0.0.1
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						void execute(Object event) throws EventBusException, InvocationTargetException {
 | 
				
			||||||
 | 
							try {
 | 
				
			||||||
 | 
								if (useParameter)
 | 
				
			||||||
 | 
									method.invoke(listener, event);
 | 
				
			||||||
 | 
								else
 | 
				
			||||||
 | 
									method.invoke(listener);
 | 
				
			||||||
 | 
							} catch (IllegalArgumentException e) {
 | 
				
			||||||
 | 
								throw new EventBusException("Event handler rejected target / argument!", e);
 | 
				
			||||||
 | 
							} catch (IllegalAccessException e) {
 | 
				
			||||||
 | 
								throw new EventBusException("Event handler is not accessible!", e);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * @return the listener containing this handler
 | 
				
			||||||
 | 
						 * @since 0.0.1
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						Object getListener() { return listener; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * @return the event type this handler listens for
 | 
				
			||||||
 | 
						 * @since 0.0.3
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						Class<?> getEventType() { return eventType; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * @return the priority of this handler
 | 
				
			||||||
 | 
						 * @since 0.0.1
 | 
				
			||||||
 | 
						 * @see Priority
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						int getPriority() { return priority; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * @return whether this handler is polymorphic
 | 
				
			||||||
 | 
						 * @since 1.0.0
 | 
				
			||||||
 | 
						 * @see Polymorphic
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						boolean isPolymorphic() { return polymorphic; }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -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,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;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,54 @@
 | 
				
			|||||||
 | 
					package dev.kske.eventbus.core;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import static org.junit.jupiter.api.Assertions.assertEquals;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import org.junit.jupiter.api.*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Tests the event cancellation mechanism of the event bus.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
 | 
					 * @author Leon Hofmeister
 | 
				
			||||||
 | 
					 * @since 0.1.0
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					class CancelTest {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						EventBus	bus;
 | 
				
			||||||
 | 
						int			hits;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Constructs an event bus and registers this test instance as an event listener.
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
 | 
						 * @since 0.1.0
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						@BeforeEach
 | 
				
			||||||
 | 
						void registerListener() {
 | 
				
			||||||
 | 
							bus = new EventBus();
 | 
				
			||||||
 | 
							bus.registerListener(this);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Tests {@link EventBus#cancel()} with two event handlers, of which the first cancels the
 | 
				
			||||||
 | 
						 * event.
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
 | 
						 * @since 0.1.0
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						@Test
 | 
				
			||||||
 | 
						void testCancellation() {
 | 
				
			||||||
 | 
							bus.dispatch(new SimpleEvent());
 | 
				
			||||||
 | 
							assertEquals(1, hits);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Event(SimpleEvent.class)
 | 
				
			||||||
 | 
						@Priority(100)
 | 
				
			||||||
 | 
						void onSimpleFirst() {
 | 
				
			||||||
 | 
							++hits;
 | 
				
			||||||
 | 
							bus.cancel();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Event(SimpleEvent.class)
 | 
				
			||||||
 | 
						@Priority(50)
 | 
				
			||||||
 | 
						void onSimpleSecond() {
 | 
				
			||||||
 | 
							++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
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					class DeadTest {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						EventBus	bus		= new EventBus();
 | 
				
			||||||
 | 
						String		event	= "This event has no handler";
 | 
				
			||||||
 | 
						boolean		deadEventHandled;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Tests dead event delivery.
 | 
				
			||||||
 | 
						 * 
 | 
				
			||||||
 | 
						 * @since 1.1.0
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						@Test
 | 
				
			||||||
 | 
						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
 | 
				
			||||||
 | 
						void testUnhandledDeadEvent() {
 | 
				
			||||||
 | 
							bus.dispatch(event);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Event
 | 
				
			||||||
 | 
						void onDeadEvent(DeadEvent deadEvent) {
 | 
				
			||||||
 | 
							assertEquals(bus, deadEvent.getEventBus());
 | 
				
			||||||
 | 
							assertEquals(event, deadEvent.getEvent());
 | 
				
			||||||
 | 
							deadEventHandled = true;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,64 @@
 | 
				
			|||||||
 | 
					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)
 | 
				
			||||||
 | 
					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
 | 
				
			||||||
 | 
						void registerListener() {
 | 
				
			||||||
 | 
							bus = new EventBus();
 | 
				
			||||||
 | 
							bus.registerListener(this);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Tests {@link EventBus#dispatch(Object)} with multiple handler priorities, a polymorphic
 | 
				
			||||||
 | 
						 * handler and a static handler.
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
 | 
						 * @since 0.0.1
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						@Test
 | 
				
			||||||
 | 
						void testDispatch() {
 | 
				
			||||||
 | 
							bus.dispatch(new SimpleEventSub());
 | 
				
			||||||
 | 
							bus.dispatch(new SimpleEvent());
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@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);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Event
 | 
				
			||||||
 | 
						@Polymorphic(false)
 | 
				
			||||||
 | 
						@Priority(100)
 | 
				
			||||||
 | 
						void onSimpleEventThird(SimpleEvent event) {
 | 
				
			||||||
 | 
							++hits;
 | 
				
			||||||
 | 
							assertEquals(4, 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
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					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
 | 
				
			||||||
 | 
						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
 | 
				
			||||||
 | 
						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;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -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 {}
 | 
				
			||||||
@@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					package dev.kske.eventbus.core;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Subclass of {@link SimpleEvent} for testing purposes.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
 | 
					 * @since 0.0.4
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public class SimpleEventSub extends SimpleEvent {}
 | 
				
			||||||
							
								
								
									
										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
 | 
				
			||||||
							
								
								
									
										89
									
								
								pom.xml
									
									
									
									
									
								
							
							
						
						
									
										89
									
								
								pom.xml
									
									
									
									
									
								
							@@ -5,16 +5,24 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	<groupId>dev.kske</groupId>
 | 
						<groupId>dev.kske</groupId>
 | 
				
			||||||
	<artifactId>event-bus</artifactId>
 | 
						<artifactId>event-bus</artifactId>
 | 
				
			||||||
	<version>0.0.2</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 framework 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,13 +66,37 @@
 | 
				
			|||||||
		<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>
 | 
				
			||||||
		<resources />
 | 
					
 | 
				
			||||||
 | 
							<!-- Disable test resource folder -->
 | 
				
			||||||
		<testResources />
 | 
							<testResources />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		<plugins>
 | 
							<plugins>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								<!-- Support Java 9 modules -->
 | 
				
			||||||
 | 
								<plugin>
 | 
				
			||||||
 | 
									<groupId>org.apache.maven.plugins</groupId>
 | 
				
			||||||
 | 
									<artifactId>maven-compiler-plugin</artifactId>
 | 
				
			||||||
 | 
									<version>3.8.1</version>
 | 
				
			||||||
 | 
								</plugin>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								<!-- Attach sources 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>
 | 
				
			||||||
 | 
									<version>3.2.1</version>
 | 
				
			||||||
				<executions>
 | 
									<executions>
 | 
				
			||||||
					<execution>
 | 
										<execution>
 | 
				
			||||||
						<id>attach-sources</id>
 | 
											<id>attach-sources</id>
 | 
				
			||||||
@@ -60,9 +106,12 @@
 | 
				
			|||||||
					</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>
 | 
				
			||||||
 | 
									<version>3.2.0</version>
 | 
				
			||||||
				<executions>
 | 
									<executions>
 | 
				
			||||||
					<execution>
 | 
										<execution>
 | 
				
			||||||
						<id>attach-javadocs</id>
 | 
											<id>attach-javadocs</id>
 | 
				
			||||||
@@ -72,15 +121,23 @@
 | 
				
			|||||||
					</execution>
 | 
										</execution>
 | 
				
			||||||
				</executions>
 | 
									</executions>
 | 
				
			||||||
			</plugin>
 | 
								</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>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		</plugins>
 | 
							</plugins>
 | 
				
			||||||
	</build>
 | 
						</build>
 | 
				
			||||||
 | 
					 | 
				
			||||||
	<dependencies>
 | 
					 | 
				
			||||||
		<dependency>
 | 
					 | 
				
			||||||
			<groupId>org.junit.jupiter</groupId>
 | 
					 | 
				
			||||||
			<artifactId>junit-jupiter-api</artifactId>
 | 
					 | 
				
			||||||
			<version>5.6.2</version>
 | 
					 | 
				
			||||||
			<scope>test</scope>
 | 
					 | 
				
			||||||
		</dependency>
 | 
					 | 
				
			||||||
	</dependencies>
 | 
					 | 
				
			||||||
</project>
 | 
					</project>
 | 
				
			||||||
@@ -1,34 +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>One parameter of a type that implements {@link IEvent}</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.
 | 
					 | 
				
			||||||
	 *
 | 
					 | 
				
			||||||
	 * @since 0.0.1
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	int priority() default 100;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,137 +0,0 @@
 | 
				
			|||||||
package dev.kske.eventbus;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
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 {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	private static EventBus singletonInstance;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * 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() {
 | 
					 | 
				
			||||||
		if (singletonInstance == null)
 | 
					 | 
				
			||||||
			singletonInstance = new EventBus();
 | 
					 | 
				
			||||||
		return singletonInstance;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	private final Map<Class<? extends IEvent>, Collection<EventHandler>> bindings
 | 
					 | 
				
			||||||
		= new ConcurrentHashMap<>();
 | 
					 | 
				
			||||||
	private final Set<EventListener> registeredListeners = ConcurrentHashMap.newKeySet();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * 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) {
 | 
					 | 
				
			||||||
		getHandlersFor(event.getClass()).forEach(handler -> handler.execute(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) {
 | 
					 | 
				
			||||||
		return bindings.containsKey(eventClass) ? new ArrayList<>(bindings.get(eventClass))
 | 
					 | 
				
			||||||
			: new ArrayList<>();
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * 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 to the specification
 | 
					 | 
				
			||||||
	 * @since 0.0.1
 | 
					 | 
				
			||||||
	 * @see Event
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	public void registerListener(EventListener listener) throws EventBusException {
 | 
					 | 
				
			||||||
		if (registeredListeners.contains(listener))
 | 
					 | 
				
			||||||
			throw new EventBusException(listener + " already registered!");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		registeredListeners.add(listener);
 | 
					 | 
				
			||||||
		for (var method : listener.getClass().getDeclaredMethods()) {
 | 
					 | 
				
			||||||
			Event annotation = method.getAnnotation(Event.class);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			// Skip methods without annotations
 | 
					 | 
				
			||||||
			if (annotation == null)
 | 
					 | 
				
			||||||
				continue;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			// Check for correct method signature and return type
 | 
					 | 
				
			||||||
			if (method.getParameterCount() != 1)
 | 
					 | 
				
			||||||
				throw new EventBusException(method + " does not have an argument count of 1!");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			if (!method.getReturnType().equals(void.class))
 | 
					 | 
				
			||||||
				throw new EventBusException(method + " does not have a return type of void!");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			var param = method.getParameterTypes()[0];
 | 
					 | 
				
			||||||
			if (!IEvent.class.isAssignableFrom(param))
 | 
					 | 
				
			||||||
				throw new EventBusException(param + " is not of type IEvent!");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			@SuppressWarnings("unchecked")
 | 
					 | 
				
			||||||
			var realParam = (Class<? extends IEvent>) param;
 | 
					 | 
				
			||||||
			if (!bindings.containsKey(realParam))
 | 
					 | 
				
			||||||
				bindings.put(realParam, new TreeSet<>());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			bindings.get(realParam).add(new EventHandler(listener, method, annotation));
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Removes a specific listener from this event bus.
 | 
					 | 
				
			||||||
	 *
 | 
					 | 
				
			||||||
	 * @param listener the listener to remove
 | 
					 | 
				
			||||||
	 * @since 0.0.1
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	public void removeListener(EventListener listener) {
 | 
					 | 
				
			||||||
		for (var binding : bindings.values()) {
 | 
					 | 
				
			||||||
			var it = binding.iterator();
 | 
					 | 
				
			||||||
			while (it.hasNext())
 | 
					 | 
				
			||||||
				if (it.next().getListener() == listener)
 | 
					 | 
				
			||||||
					it.remove();
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		registeredListeners.remove(listener);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Removes all event listeners from this event bus.
 | 
					 | 
				
			||||||
	 *
 | 
					 | 
				
			||||||
	 * @since 0.0.1
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	public void clearListeners() {
 | 
					 | 
				
			||||||
		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,21 +0,0 @@
 | 
				
			|||||||
package dev.kske.eventbus;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * This runtime exception is thrown when an event bus error occurs. This can either occur while
 | 
					 | 
				
			||||||
 * registering event listeners with invalid handlers, or when an event handler throws an exception.
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * @author Kai S. K. Engelbart
 | 
					 | 
				
			||||||
 * @since 0.0.1
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
public class EventBusException extends RuntimeException {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	private static final long serialVersionUID = 1L;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	public EventBusException(String message, Throwable cause) {
 | 
					 | 
				
			||||||
		super(message, cause);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	public EventBusException(String message) {
 | 
					 | 
				
			||||||
		super(message);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,85 +0,0 @@
 | 
				
			|||||||
package dev.kske.eventbus;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import java.lang.reflect.*;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * 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;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Constructs an event handler.
 | 
					 | 
				
			||||||
	 *
 | 
					 | 
				
			||||||
	 * @param listener   the listener containing the handler
 | 
					 | 
				
			||||||
	 * @param method     the handler method
 | 
					 | 
				
			||||||
	 * @param annotation the event annotation
 | 
					 | 
				
			||||||
	 * @since 0.0.1
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	EventHandler(EventListener listener, Method method, Event annotation) {
 | 
					 | 
				
			||||||
		this.listener = listener;
 | 
					 | 
				
			||||||
		this.method = method;
 | 
					 | 
				
			||||||
		this.annotation = annotation;
 | 
					 | 
				
			||||||
		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;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * 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 {
 | 
					 | 
				
			||||||
			method.invoke(listener, event);
 | 
					 | 
				
			||||||
		} 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(); }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -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,38 +0,0 @@
 | 
				
			|||||||
package dev.kske.eventbus;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import org.junit.jupiter.api.*;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Tests the of the event bus library.
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * @author Kai S. K. Engelbart
 | 
					 | 
				
			||||||
 * @since 0.0.1
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
class EventBusTest implements EventListener {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	int hits;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	@BeforeEach
 | 
					 | 
				
			||||||
	public void registerListener() {
 | 
					 | 
				
			||||||
		EventBus.getInstance().registerListener(this);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	@Test
 | 
					 | 
				
			||||||
	void testDispatch() {
 | 
					 | 
				
			||||||
		EventBus.getInstance().dispatch(new SimpleEvent());
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	@Event(priority = 50)
 | 
					 | 
				
			||||||
	private void onSimpleEventSecond(SimpleEvent event) {
 | 
					 | 
				
			||||||
		++hits;
 | 
					 | 
				
			||||||
		assertEquals(2, hits);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	@Event(priority = 150)
 | 
					 | 
				
			||||||
	private void onSimpleEventFirst(SimpleEvent event) {
 | 
					 | 
				
			||||||
		++hits;
 | 
					 | 
				
			||||||
		assertEquals(1, hits);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
		Reference in New Issue
	
	Block a user