Add all relevant classes and event bus logic
This commit is contained in:
		
							
								
								
									
										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; | ||||
		Reference in New Issue
	
	Block a user