Merge pull request 'Add Callback Event Handling' (#26) from f/callback-handler into develop
Reviewed-on: https://git.kske.dev/kske/event-bus/pulls/26 Reviewed-by: delvh <leon@kske.dev>
This commit is contained in:
		| @@ -22,7 +22,7 @@ import java.lang.annotation.*; | ||||
| public @interface Event { | ||||
|  | ||||
| 	/** | ||||
| 	 * Defines the event type the handler listens to. If this value is set, the handler is not | ||||
| 	 * Defines the event type the handler listens for. If this value is set, the handler is not | ||||
| 	 * allowed to declare parameters. | ||||
| 	 * <p> | ||||
| 	 * This is useful when the event handler does not utilize the event instance. | ||||
|   | ||||
| @@ -5,6 +5,9 @@ import java.lang.System.Logger.Level; | ||||
| import java.lang.reflect.InvocationTargetException; | ||||
| import java.util.*; | ||||
| import java.util.concurrent.ConcurrentHashMap; | ||||
| import java.util.function.Consumer; | ||||
|  | ||||
| import dev.kske.eventbus.core.handler.*; | ||||
|  | ||||
| /** | ||||
|  * Event listeners can be registered at an event bus to be notified when an event is dispatched. | ||||
| @@ -56,6 +59,19 @@ public final class EventBus { | ||||
|  | ||||
| 	private static final Logger logger = System.getLogger(EventBus.class.getName()); | ||||
|  | ||||
| 	/** | ||||
| 	 * Compares event handlers based on priority, but uses hash codes for equal priorities. | ||||
| 	 * | ||||
| 	 * @implNote As the priority comparator by itself is not consistent with equals (two handlers | ||||
| 	 *           with the same priority are not necessarily equal, but would have a comparison | ||||
| 	 *           result of 0), the hash code is used for the fallback comparison. This way, | ||||
| 	 *           consistency with equals is restored. | ||||
| 	 * @since 1.2.0 | ||||
| 	 */ | ||||
| 	private static final Comparator<EventHandler> byPriority = | ||||
| 		Comparator.comparingInt(EventHandler::getPriority).reversed() | ||||
| 			.thenComparingInt(EventHandler::hashCode); | ||||
|  | ||||
| 	/** | ||||
| 	 * Returns the default event bus, which is a statically initialized singleton instance. | ||||
| 	 * | ||||
| @@ -154,18 +170,19 @@ public final class EventBus { | ||||
| 	 * 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 | ||||
| 	 * @param eventType the event type to use for the search | ||||
| 	 * @return a navigable set containing the applicable handlers in descending order of priority | ||||
| 	 * @since 1.2.0 | ||||
| 	 */ | ||||
| 	private NavigableSet<EventHandler> getHandlersFor(Class<?> eventClass) { | ||||
| 	private NavigableSet<EventHandler> getHandlersFor(Class<?> eventType) { | ||||
|  | ||||
| 		// Get handlers defined for the event class | ||||
| 		TreeSet<EventHandler> handlers = bindings.getOrDefault(eventClass, new TreeSet<>()); | ||||
| 		TreeSet<EventHandler> handlers = | ||||
| 			bindings.getOrDefault(eventType, new TreeSet<>(byPriority)); | ||||
|  | ||||
| 		// Get polymorphic handlers | ||||
| 		for (var binding : bindings.entrySet()) | ||||
| 			if (binding.getKey().isAssignableFrom(eventClass)) | ||||
| 			if (binding.getKey().isAssignableFrom(eventType)) | ||||
| 				for (var handler : binding.getValue()) | ||||
| 					if (handler.isPolymorphic()) | ||||
| 						handlers.add(handler); | ||||
| @@ -223,11 +240,8 @@ public final class EventBus { | ||||
| 				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); | ||||
| 			bindHandler( | ||||
| 				new ReflectiveEventHandler(listener, method, annotation, polymorphic, priority)); | ||||
| 			handlerBound = true; | ||||
| 		} | ||||
|  | ||||
| @@ -238,6 +252,86 @@ public final class EventBus { | ||||
| 				listener.getClass().getName()); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Registers a callback listener, which is a consumer that is invoked when an event occurs. The | ||||
| 	 * listener is not polymorphic and has the {@link #DEFAULT_PRIORITY}. | ||||
| 	 * | ||||
| 	 * @param <E>           the event type the listener listens for | ||||
| 	 * @param eventType     the event type the listener listens for | ||||
| 	 * @param eventListener the callback that is invoked when an event occurs | ||||
| 	 * @since 1.2.0 | ||||
| 	 * @see #registerListener(Class, Consumer, boolean, int) | ||||
| 	 */ | ||||
| 	public <E> void registerListener(Class<E> eventType, Consumer<E> eventListener) { | ||||
| 		registerListener(eventType, eventListener, false, DEFAULT_PRIORITY); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Registers a callback listener, which is a consumer that is invoked when an event occurs. The | ||||
| 	 * listener has the {@link #DEFAULT_PRIORITY}. | ||||
| 	 * | ||||
| 	 * @param <E>           the event type the listener listens for | ||||
| 	 * @param eventType     the event type the listener listens for | ||||
| 	 * @param eventListener the callback that is invoked when an event occurs | ||||
| 	 * @param polymorphic   whether the listener is also invoked for subtypes of the event type | ||||
| 	 * @since 1.2.0 | ||||
| 	 * @see #registerListener(Class, Consumer, boolean, int) | ||||
| 	 */ | ||||
| 	public <E> void registerListener(Class<E> eventType, Consumer<E> eventListener, | ||||
| 		boolean polymorphic) { | ||||
| 		registerListener(eventType, eventListener, polymorphic, DEFAULT_PRIORITY); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Registers a callback listener, which is a consumer that is invoked when an event occurs. The | ||||
| 	 * listener is not polymorphic. | ||||
| 	 * | ||||
| 	 * @param <E>           the event type the listener listens for | ||||
| 	 * @param eventType     the event type the listener listens for | ||||
| 	 * @param eventListener the callback that is invoked when an event occurs | ||||
| 	 * @param priority      the priority to assign to the listener | ||||
| 	 * @since 1.2.0 | ||||
| 	 * @see #registerListener(Class, Consumer, boolean, int) | ||||
| 	 */ | ||||
| 	public <E> void registerListener(Class<E> eventType, Consumer<E> eventListener, int priority) { | ||||
| 		registerListener(eventType, eventListener, false, priority); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Registers a callback listener, which is a consumer that is invoked when an event occurs. | ||||
| 	 * | ||||
| 	 * @param <E>           the event type the listener listens for | ||||
| 	 * @param eventType     the event type the listener listens for | ||||
| 	 * @param eventListener the callback that is invoked when an event occurs | ||||
| 	 * @param polymorphic   whether the listener is also invoked for subtypes of the event type | ||||
| 	 * @param priority      the priority to assign to the listener | ||||
| 	 * @since 1.2.0 | ||||
| 	 */ | ||||
| 	public <E> void registerListener(Class<E> eventType, Consumer<E> eventListener, | ||||
| 		boolean polymorphic, | ||||
| 		int priority) { | ||||
| 		Objects.requireNonNull(eventListener); | ||||
| 		if (registeredListeners.contains(eventListener)) | ||||
| 			throw new EventBusException(eventListener + " already registered!"); | ||||
| 		logger.log(Level.INFO, "Registering callback event listener {0}", | ||||
| 			eventListener.getClass().getName()); | ||||
|  | ||||
| 		registeredListeners.add(eventListener); | ||||
| 		bindHandler(new CallbackEventHandler(eventType, eventListener, polymorphic, priority)); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Inserts a new handler into the {@link #bindings} map. | ||||
| 	 * | ||||
| 	 * @param handler the handler to bind | ||||
| 	 * @since 1.2.0 | ||||
| 	 */ | ||||
| 	private void bindHandler(EventHandler handler) { | ||||
| 		bindings.putIfAbsent(handler.getEventType(), new TreeSet<>(byPriority)); | ||||
| 		logger.log(Level.DEBUG, "Binding event handler {0}", handler); | ||||
| 		bindings.get(handler.getEventType()).add(handler); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Removes a specific listener from this event bus. | ||||
| 	 * | ||||
|   | ||||
| @@ -0,0 +1,73 @@ | ||||
| package dev.kske.eventbus.core.handler; | ||||
|  | ||||
| import java.lang.reflect.InvocationTargetException; | ||||
| import java.util.function.Consumer; | ||||
|  | ||||
| /** | ||||
|  * An event handler wrapping a callback method. | ||||
|  * | ||||
|  * @author Kai S. K. Engelbart | ||||
|  * @since 1.2.0 | ||||
|  */ | ||||
| public final class CallbackEventHandler implements EventHandler { | ||||
|  | ||||
| 	private final Class<?>			eventType; | ||||
| 	private final Consumer<Object>	callback; | ||||
| 	private final boolean			polymorphic; | ||||
| 	private final int				priority; | ||||
|  | ||||
| 	/** | ||||
| 	 * Constructs a callback event handler. | ||||
| 	 * | ||||
| 	 * @param <E>         the event type of the handler | ||||
| 	 * @param eventType   the event type of the handler | ||||
| 	 * @param callback    the callback method to execute when the handler is invoked | ||||
| 	 * @param polymorphic whether the handler is polymorphic | ||||
| 	 * @param priority    the priority of the handler | ||||
| 	 * @since 1.2.0 | ||||
| 	 */ | ||||
| 	@SuppressWarnings("unchecked") | ||||
| 	public <E> CallbackEventHandler(Class<E> eventType, Consumer<E> callback, boolean polymorphic, | ||||
| 		int priority) { | ||||
| 		this.eventType		= eventType; | ||||
| 		this.callback		= (Consumer<Object>) callback; | ||||
| 		this.polymorphic	= polymorphic; | ||||
| 		this.priority		= priority; | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public void execute(Object event) throws InvocationTargetException { | ||||
| 		try { | ||||
| 			callback.accept(event); | ||||
| 		} catch (RuntimeException e) { | ||||
| 			throw new InvocationTargetException(e, "Callback event handler failed!"); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public String toString() { | ||||
| 		return String.format( | ||||
| 			"CallbackEventHandler[eventType=%s, polymorphic=%b, priority=%d]", | ||||
| 			eventType, polymorphic, priority); | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public Consumer<?> getListener() { | ||||
| 		return callback; | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public Class<?> getEventType() { | ||||
| 		return eventType; | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public int getPriority() { | ||||
| 		return priority; | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public boolean isPolymorphic() { | ||||
| 		return polymorphic; | ||||
| 	} | ||||
| } | ||||
| @@ -0,0 +1,53 @@ | ||||
| package dev.kske.eventbus.core.handler; | ||||
|  | ||||
| import java.lang.reflect.InvocationTargetException; | ||||
|  | ||||
| import dev.kske.eventbus.core.*; | ||||
|  | ||||
| /** | ||||
|  * Internal representation of an event handling method. | ||||
|  * | ||||
|  * @author Kai S. K. Engelbart | ||||
|  * @since 1.2.0 | ||||
|  * @see EventBus | ||||
|  */ | ||||
| public interface EventHandler { | ||||
|  | ||||
| 	/** | ||||
| 	 * Executes the event handler. | ||||
| 	 * | ||||
| 	 * @param event the event used as the method parameter | ||||
| 	 * @throws EventBusException         if the event handler isn't accessible or has an invalid | ||||
| 	 *                                   signature | ||||
| 	 * @throws InvocationTargetException if the handler throws an exception | ||||
| 	 * @throws EventBusException         if the handler has the wrong signature or is inaccessible | ||||
| 	 * @since 1.2.0 | ||||
| 	 */ | ||||
| 	void execute(Object event) throws EventBusException, InvocationTargetException; | ||||
|  | ||||
| 	/** | ||||
| 	 * @return the listener containing this handler | ||||
| 	 * @since 1.2.0 | ||||
| 	 */ | ||||
| 	Object getListener(); | ||||
|  | ||||
| 	/** | ||||
| 	 * @return the event type this handler listens for | ||||
| 	 * @since 1.2.0 | ||||
| 	 */ | ||||
| 	Class<?> getEventType(); | ||||
|  | ||||
| 	/** | ||||
| 	 * @return the priority of this handler | ||||
| 	 * @since 1.2.0 | ||||
| 	 * @see Priority | ||||
| 	 */ | ||||
| 	int getPriority(); | ||||
|  | ||||
| 	/** | ||||
| 	 * @return whether this handler also accepts subtypes of the event type | ||||
| 	 * @since 1.2.0 | ||||
| 	 * @see Polymorphic | ||||
| 	 */ | ||||
| 	boolean isPolymorphic(); | ||||
| } | ||||
| @@ -1,17 +1,18 @@ | ||||
| package dev.kske.eventbus.core; | ||||
| package dev.kske.eventbus.core.handler; | ||||
| 
 | ||||
| import java.lang.reflect.*; | ||||
| 
 | ||||
| import dev.kske.eventbus.core.*; | ||||
| import dev.kske.eventbus.core.Event.USE_PARAMETER; | ||||
| 
 | ||||
| /** | ||||
|  * Internal representation of an event handling method. | ||||
|  * An event handler wrapping a method annotated with {@link Event} and executing it using | ||||
|  * reflection. | ||||
|  * | ||||
|  * @author Kai S. K. Engelbart | ||||
|  * @since 0.0.1 | ||||
|  * @see EventBus | ||||
|  * @since 1.2.0 | ||||
|  */ | ||||
| final class EventHandler implements Comparable<EventHandler> { | ||||
| public final class ReflectiveEventHandler implements EventHandler { | ||||
| 
 | ||||
| 	private final Object	listener; | ||||
| 	private final Method	method; | ||||
| @@ -21,7 +22,7 @@ final class EventHandler implements Comparable<EventHandler> { | ||||
| 	private final int		priority; | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Constructs an event handler. | ||||
| 	 * Constructs a reflective event handler. | ||||
| 	 * | ||||
| 	 * @param listener        the listener containing the handler | ||||
| 	 * @param method          the handler method | ||||
| @@ -30,10 +31,10 @@ final class EventHandler implements Comparable<EventHandler> { | ||||
| 	 * @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 | ||||
| 	 * @since 1.2.0 | ||||
| 	 */ | ||||
| 	EventHandler(Object listener, Method method, Event annotation, boolean defPolymorphism, | ||||
| 		int defPriority) throws EventBusException { | ||||
| 	public ReflectiveEventHandler(Object listener, Method method, Event annotation, | ||||
| 		boolean defPolymorphism, int defPriority) throws EventBusException { | ||||
| 		this.listener	= listener; | ||||
| 		this.method		= method; | ||||
| 		useParameter	= annotation.value() == USE_PARAMETER.class; | ||||
| @@ -61,45 +62,13 @@ final class EventHandler implements Comparable<EventHandler> { | ||||
| 		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 { | ||||
| 	public void execute(Object event) throws EventBusException, InvocationTargetException { | ||||
| 		try { | ||||
| 			if (useParameter) | ||||
| 				method.invoke(listener, event); | ||||
| 				method.invoke(getListener(), event); | ||||
| 			else | ||||
| 				method.invoke(listener); | ||||
| 				method.invoke(getListener()); | ||||
| 		} catch (IllegalArgumentException e) { | ||||
| 			throw new EventBusException("Event handler rejected target / argument!", e); | ||||
| 		} catch (IllegalAccessException e) { | ||||
| @@ -107,29 +76,30 @@ final class EventHandler implements Comparable<EventHandler> { | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
| 	 * @return the listener containing this handler | ||||
| 	 * @since 0.0.1 | ||||
| 	 */ | ||||
| 	Object getListener() { return listener; } | ||||
| 	@Override | ||||
| 	public String toString() { | ||||
| 		return String.format( | ||||
| 			"ReflectiveEventHandler[eventType=%s, polymorphic=%b, priority=%d, method=%s, useParameter=%b]", | ||||
| 			eventType, polymorphic, priority, method, useParameter); | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
| 	 * @return the event type this handler listens for | ||||
| 	 * @since 0.0.3 | ||||
| 	 */ | ||||
| 	Class<?> getEventType() { return eventType; } | ||||
| 	@Override | ||||
| 	public Object getListener() { | ||||
| 		return listener; | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
| 	 * @return the priority of this handler | ||||
| 	 * @since 0.0.1 | ||||
| 	 * @see Priority | ||||
| 	 */ | ||||
| 	int getPriority() { return priority; } | ||||
| 	@Override | ||||
| 	public Class<?> getEventType() { | ||||
| 		return eventType; | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
| 	 * @return whether this handler is polymorphic | ||||
| 	 * @since 1.0.0 | ||||
| 	 * @see Polymorphic | ||||
| 	 */ | ||||
| 	boolean isPolymorphic() { return polymorphic; } | ||||
| 	@Override | ||||
| 	public int getPriority() { | ||||
| 		return priority; | ||||
| 	} | ||||
| 
 | ||||
| 	@Override | ||||
| 	public boolean isPolymorphic() { | ||||
| 		return polymorphic; | ||||
| 	} | ||||
| } | ||||
| @@ -0,0 +1,8 @@ | ||||
| /** | ||||
|  * Contains the internal representation of event handling methods. | ||||
|  * | ||||
|  * @author Kai S. K. Engelbart | ||||
|  * @since 1.2.0 | ||||
|  * @see dev.kske.eventbus.core.handler.EventHandler | ||||
|  */ | ||||
| package dev.kske.eventbus.core.handler; | ||||
| @@ -26,6 +26,10 @@ class DispatchTest { | ||||
| 	void registerListener() { | ||||
| 		bus = new EventBus(); | ||||
| 		bus.registerListener(this); | ||||
| 		bus.registerListener(SimpleEvent.class, e -> { | ||||
| 			++hits; | ||||
| 			assertEquals(4, hits); | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| @@ -52,9 +56,9 @@ class DispatchTest { | ||||
| 		assertEquals( | ||||
| 			"Event handler execution order for class dev.kske.eventbus.core.SimpleEvent (3 handler(s)):\n" | ||||
| 				+ "==========================================================================================\n" | ||||
| 				+ "EventHandler[method=void dev.kske.eventbus.core.DispatchTest.onSimpleEventFirst(), eventType=class dev.kske.eventbus.core.SimpleEvent, useParameter=false, polymorphic=true, priority=200]\n" | ||||
| 				+ "EventHandler[method=static void dev.kske.eventbus.core.DispatchTest.onSimpleEventSecond(), eventType=class dev.kske.eventbus.core.SimpleEvent, useParameter=false, polymorphic=false, priority=150]\n" | ||||
| 				+ "EventHandler[method=void dev.kske.eventbus.core.DispatchTest.onSimpleEventThird(dev.kske.eventbus.core.SimpleEvent), eventType=class dev.kske.eventbus.core.SimpleEvent, useParameter=true, polymorphic=false, priority=100]\n" | ||||
| 				+ "ReflectiveEventHandler[eventType=class dev.kske.eventbus.core.SimpleEvent, polymorphic=true, priority=200, method=void dev.kske.eventbus.core.DispatchTest.onSimpleEventFirst(), useParameter=false]\n" | ||||
| 				+ "ReflectiveEventHandler[eventType=class dev.kske.eventbus.core.SimpleEvent, polymorphic=false, priority=150, method=static void dev.kske.eventbus.core.DispatchTest.onSimpleEventSecond(), useParameter=false]\n" | ||||
| 				+ "CallbackEventHandler[eventType=class dev.kske.eventbus.core.SimpleEvent, polymorphic=false, priority=100]\n" | ||||
| 				+ "==========================================================================================", | ||||
| 			executionOrder); | ||||
| 	} | ||||
| @@ -72,12 +76,4 @@ class DispatchTest { | ||||
| 		++hits; | ||||
| 		assertEquals(3, hits); | ||||
| 	} | ||||
|  | ||||
| 	@Event | ||||
| 	@Polymorphic(false) | ||||
| 	@Priority(100) | ||||
| 	void onSimpleEventThird(SimpleEvent event) { | ||||
| 		++hits; | ||||
| 		assertEquals(4, hits); | ||||
| 	} | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	