Add all relevant classes and event bus logic
This commit is contained in:
parent
9fdf2a822b
commit
88ba515cbf
34
src/main/java/dev/kske/eventbus/Event.java
Normal file
34
src/main/java/dev/kske/eventbus/Event.java
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
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;
|
||||||
|
}
|
120
src/main/java/dev/kske/eventbus/EventBus.java
Normal file
120
src/main/java/dev/kske/eventbus/EventBus.java
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
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>
|
||||||
|
* This is a thread-safe implementation.
|
||||||
|
*
|
||||||
|
* @author Kai S. K. Engelbart
|
||||||
|
* @since 0.0.1
|
||||||
|
* @see Event
|
||||||
|
*/
|
||||||
|
public final class EventBus {
|
||||||
|
|
||||||
|
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 HashSet<>());
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
21
src/main/java/dev/kske/eventbus/EventBusException.java
Normal file
21
src/main/java/dev/kske/eventbus/EventBusException.java
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
82
src/main/java/dev/kske/eventbus/EventHandler.java
Normal file
82
src/main/java/dev/kske/eventbus/EventHandler.java
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* @since 0.0.1
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int compareTo(EventHandler other) {
|
||||||
|
int priority = annotation.priority() - other.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(); }
|
||||||
|
}
|
12
src/main/java/dev/kske/eventbus/EventListener.java
Normal file
12
src/main/java/dev/kske/eventbus/EventListener.java
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
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 {}
|
12
src/main/java/dev/kske/eventbus/IEvent.java
Normal file
12
src/main/java/dev/kske/eventbus/IEvent.java
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
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 {}
|
9
src/main/java/dev/kske/eventbus/package-info.java
Normal file
9
src/main/java/dev/kske/eventbus/package-info.java
Normal file
@ -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.Event
|
||||||
|
* @see dev.kske.eventbus.EventBus
|
||||||
|
*/
|
||||||
|
package dev.kske.eventbus;
|
Loading…
Reference in New Issue
Block a user