diff --git a/.gitignore b/.gitignore index 731eb43..db59885 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,8 @@ -/target/ -/.settings/ +# Maven build directories +target/ + +# Dependency reduced POM from Maven Shade Plugin +dependency-reduced-pom.xml + +# Eclipse settings directories +.settings/ diff --git a/.project b/.project index 2acd4d0..65850dd 100644 --- a/.project +++ b/.project @@ -5,11 +5,6 @@ - - org.eclipse.jdt.core.javabuilder - - - org.eclipse.m2e.core.maven2Builder @@ -17,7 +12,6 @@ - org.eclipse.jdt.core.javanature org.eclipse.m2e.core.maven2Nature diff --git a/README.md b/README.md index ffc596d..1bd8862 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Additionally, the class containing the method must implement the `EventListener` Lets look at a simple example: we declare the empty class `SimpleEvent` that implements `IEvent` and can thus be used as an event. ```java -import dev.kske.eventbus.IEvent; +import dev.kske.eventbus.core.IEvent; public class SimpleEvent implements IEvent {} ``` @@ -28,7 +28,7 @@ public class SimpleEvent implements IEvent {} Next, an event listener for the `SimpleEvent` is declared: ```java -import dev.kske.eventbus.*; +import dev.kske.eventbus.core.*; public class SimpleEventListener implements EventListener { @@ -62,7 +62,7 @@ is technically possible, however you would still have to create an instance of t ## Event handlers for subtypes -On certain occasions its practical for an event handler to accept both events of the specified type, as well as subclasses of that event. +On certain occasions it's practical for an event handler to accept both events of the specified type, as well as subclasses of that event. To include subtypes for an event handler, use the `includeSubtypes` parameter as follows: ```java @@ -141,7 +141,41 @@ To include it inside your project, just add the Maven repository and the depende dev.kske event-bus - 0.1.0 + 1.0.0 ``` + +Then, require the Event Bus Core module in your `module-info.java`: + +```java +requires dev.kske.eventbus.core; +``` + +# Compile-Time Error Checking with Event Bus AP + +To assist you with writing event listeners, the Event Bus AP (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, missing `EventListener` implementations, event type issues with more to come in future versions. + +When using Maven, it can be registered using the Maven Compiler Plugin: + +```xml + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + + + dev.kske + event-bus-ap + 1.0.0 + + + + +``` + +Alternatively, a JAR file containing the processor is offered with each release for the use within IDEs and environments without Maven support. \ No newline at end of file diff --git a/event-bus-ap/.classpath b/event-bus-ap/.classpath new file mode 100644 index 0000000..42c6248 --- /dev/null +++ b/event-bus-ap/.classpath @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/event-bus-ap/.project b/event-bus-ap/.project new file mode 100644 index 0000000..7976120 --- /dev/null +++ b/event-bus-ap/.project @@ -0,0 +1,23 @@ + + + event-bus-ap + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + + diff --git a/event-bus-ap/pom.xml b/event-bus-ap/pom.xml new file mode 100644 index 0000000..e2e253f --- /dev/null +++ b/event-bus-ap/pom.xml @@ -0,0 +1,57 @@ + + 4.0.0 + + event-bus-ap + + Event Bus Annotation Processor + Annotation processor checking for errors related to the @Event annotation from Event Bus. + + + dev.kske + event-bus + 1.0.0 + + + + + dev.kske + event-bus-core + ${project.version} + + + + + + + + + + + maven-compiler-plugin + + -proc:none + + + + + org.apache.maven.plugins + maven-shade-plugin + 3.2.4 + + + package + + shade + + + true + + + + + + + + \ No newline at end of file diff --git a/event-bus-ap/src/main/java/dev/kske/eventbus/ap/EventProcessor.java b/event-bus-ap/src/main/java/dev/kske/eventbus/ap/EventProcessor.java new file mode 100644 index 0000000..53ad735 --- /dev/null +++ b/event-bus-ap/src/main/java/dev/kske/eventbus/ap/EventProcessor.java @@ -0,0 +1,107 @@ +package dev.kske.eventbus.ap; + +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 annotations, RoundEnvironment roundEnv) { + if (!roundEnv.errorRaised() && !roundEnv.processingOver()) + processRound( + (Set) roundEnv.getElementsAnnotatedWith(Event.class)); + + // Do not claim the processed annotations + return false; + } + + private void processRound(Set eventHandlers) { + for (ExecutableElement eventHandler : eventHandlers) { + TypeElement eventListener = (TypeElement) eventHandler.getEnclosingElement(); + Event eventAnnotation = eventHandler.getAnnotation(Event.class); + + // Determine how the event type is defined + boolean useParameter; + try { + eventAnnotation.eventType(); + throw new EventBusException( + "Could not determine event type of handler " + eventHandler); + } catch (MirroredTypeException e) { + + // Task failed successfully + useParameter = processingEnv.getTypeUtils().isSameType(e.getTypeMirror(), + getTypeMirror(Event.USE_PARAMETER.class)); + } + + // Check for correct method signature and return type + if (eventHandler.getParameters().size() == 0 && useParameter) + error(eventHandler, "The method or the annotation must define the event type"); + + if (eventHandler.getParameters().size() == 1 && !useParameter) + error(eventHandler, + "Either the method or the annotation must define the event type"); + + if (eventHandler.getParameters().size() > 1) + error(eventHandler, "Method must not have more than one parameter"); + + if (eventHandler.getReturnType().getKind() != TypeKind.VOID) + error(eventHandler, "Method must return void"); + + // Get first parameter as type and element + var paramElement = eventHandler.getParameters().get(0); + var paramType = paramElement.asType(); + + // Check for valid event type + if (useParameter && !processingEnv.getTypeUtils().isAssignable(paramType, + getTypeMirror(IEvent.class))) + error(paramElement, "Parameter must implement IEvent"); + + // Check for handlers for abstract types that don't include subtypes + if (!eventAnnotation.includeSubtypes() && paramType.getKind() == TypeKind.DECLARED) { + var declaredElement = ((DeclaredType) paramType).asElement(); + if (declaredElement.getKind() == ElementKind.INTERFACE + || declaredElement.getModifiers().contains(Modifier.ABSTRACT)) + warning(paramElement, + "Parameter should be instantiable or handler should include subtypes"); + } + + // Check listener for interface implementation + if (!eventListener.getInterfaces().contains(getTypeMirror(EventListener.class))) + warning(eventHandler.getEnclosingElement(), + "Class should implement EventListener interface"); + } + } + + 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); + } +} diff --git a/event-bus-ap/src/main/java/dev/kske/eventbus/ap/package-info.java b/event-bus-ap/src/main/java/dev/kske/eventbus/ap/package-info.java new file mode 100644 index 0000000..3a09965 --- /dev/null +++ b/event-bus-ap/src/main/java/dev/kske/eventbus/ap/package-info.java @@ -0,0 +1,7 @@ +/** + * Contains the Event Bus annotation processor. + * + * @author Kai S. K. Engelbart + * @since 1.0.0 + */ +package dev.kske.eventbus.ap; diff --git a/event-bus-ap/src/main/java/module-info.java b/event-bus-ap/src/main/java/module-info.java new file mode 100644 index 0000000..e09c4be --- /dev/null +++ b/event-bus-ap/src/main/java/module-info.java @@ -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; +} diff --git a/event-bus-ap/src/main/resources/META-INF/services/javax.annotation.processing.Processor b/event-bus-ap/src/main/resources/META-INF/services/javax.annotation.processing.Processor new file mode 100644 index 0000000..13befff --- /dev/null +++ b/event-bus-ap/src/main/resources/META-INF/services/javax.annotation.processing.Processor @@ -0,0 +1 @@ +dev.kske.eventbus.ap.EventProcessor \ No newline at end of file diff --git a/.classpath b/event-bus-core/.classpath similarity index 100% rename from .classpath rename to event-bus-core/.classpath diff --git a/event-bus-core/.project b/event-bus-core/.project new file mode 100644 index 0000000..8523046 --- /dev/null +++ b/event-bus-core/.project @@ -0,0 +1,23 @@ + + + event-bus-core + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + + diff --git a/event-bus-core/pom.xml b/event-bus-core/pom.xml new file mode 100644 index 0000000..91ed55d --- /dev/null +++ b/event-bus-core/pom.xml @@ -0,0 +1,30 @@ + + 4.0.0 + + event-bus-core + Event Bus Core + + + dev.kske + event-bus + 1.0.0 + + + + + org.junit.jupiter + junit-jupiter-api + 5.6.2 + test + + + + + + + + + + \ No newline at end of file diff --git a/src/main/java/dev/kske/eventbus/Event.java b/event-bus-core/src/main/java/dev/kske/eventbus/core/Event.java similarity index 98% rename from src/main/java/dev/kske/eventbus/Event.java rename to event-bus-core/src/main/java/dev/kske/eventbus/core/Event.java index 95fb2d3..25ee7a3 100644 --- a/src/main/java/dev/kske/eventbus/Event.java +++ b/event-bus-core/src/main/java/dev/kske/eventbus/core/Event.java @@ -1,4 +1,4 @@ -package dev.kske.eventbus; +package dev.kske.eventbus.core; import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.RetentionPolicy.RUNTIME; diff --git a/src/main/java/dev/kske/eventbus/EventBus.java b/event-bus-core/src/main/java/dev/kske/eventbus/core/EventBus.java similarity index 91% rename from src/main/java/dev/kske/eventbus/EventBus.java rename to event-bus-core/src/main/java/dev/kske/eventbus/core/EventBus.java index 628e0b9..2248668 100644 --- a/src/main/java/dev/kske/eventbus/EventBus.java +++ b/event-bus-core/src/main/java/dev/kske/eventbus/core/EventBus.java @@ -1,4 +1,4 @@ -package dev.kske.eventbus; +package dev.kske.eventbus.core; import java.lang.System.Logger; import java.lang.System.Logger.Level; @@ -51,11 +51,12 @@ public final class EventBus { return instance; } - private final Map, TreeSet> bindings - = new ConcurrentHashMap<>(); - private final Set registeredListeners = ConcurrentHashMap.newKeySet(); - private final ThreadLocal dispatchState - = ThreadLocal.withInitial(DispatchState::new); + private final Map, TreeSet> bindings = + new ConcurrentHashMap<>(); + private final Set registeredListeners = + ConcurrentHashMap.newKeySet(); + private final ThreadLocal dispatchState = + ThreadLocal.withInitial(DispatchState::new); /** * Dispatches an event to all event handlers registered for it in descending order of their @@ -97,9 +98,7 @@ public final class EventBus { private List getHandlersFor(Class eventClass) { // Get handlers defined for the event class - Set handlers - = bindings.containsKey(eventClass) ? bindings.get(eventClass) - : new TreeSet<>(); + Set handlers = bindings.getOrDefault(eventClass, new TreeSet<>()); // Get subtype handlers for (var binding : bindings.entrySet()) @@ -151,20 +150,18 @@ public final class EventBus { // Initialize and bind the handler var handler = new EventHandler(listener, method, annotation); - if (!bindings.containsKey(handler.getEventType())) - bindings.put(handler.getEventType(), new TreeSet<>()); + 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) + if (!handlerBound) logger.log( Level.WARNING, "No event handlers bound for event listener {0}", - listener.getClass().getName() - ); + listener.getClass().getName()); } /** diff --git a/src/main/java/dev/kske/eventbus/EventBusException.java b/event-bus-core/src/main/java/dev/kske/eventbus/core/EventBusException.java similarity index 95% rename from src/main/java/dev/kske/eventbus/EventBusException.java rename to event-bus-core/src/main/java/dev/kske/eventbus/core/EventBusException.java index 5b3eb8e..d67616c 100644 --- a/src/main/java/dev/kske/eventbus/EventBusException.java +++ b/event-bus-core/src/main/java/dev/kske/eventbus/core/EventBusException.java @@ -1,4 +1,4 @@ -package dev.kske.eventbus; +package dev.kske.eventbus.core; /** * This runtime exception is thrown when an event bus error occurs. This can diff --git a/src/main/java/dev/kske/eventbus/EventHandler.java b/event-bus-core/src/main/java/dev/kske/eventbus/core/EventHandler.java similarity index 97% rename from src/main/java/dev/kske/eventbus/EventHandler.java rename to event-bus-core/src/main/java/dev/kske/eventbus/core/EventHandler.java index 53d9085..aae6387 100644 --- a/src/main/java/dev/kske/eventbus/EventHandler.java +++ b/event-bus-core/src/main/java/dev/kske/eventbus/core/EventHandler.java @@ -1,8 +1,8 @@ -package dev.kske.eventbus; +package dev.kske.eventbus.core; import java.lang.reflect.*; -import dev.kske.eventbus.Event.USE_PARAMETER; +import dev.kske.eventbus.core.Event.USE_PARAMETER; /** * Internal representation of an event handling method. diff --git a/src/main/java/dev/kske/eventbus/EventListener.java b/event-bus-core/src/main/java/dev/kske/eventbus/core/EventListener.java similarity index 88% rename from src/main/java/dev/kske/eventbus/EventListener.java rename to event-bus-core/src/main/java/dev/kske/eventbus/core/EventListener.java index bc48ec7..4e54505 100644 --- a/src/main/java/dev/kske/eventbus/EventListener.java +++ b/event-bus-core/src/main/java/dev/kske/eventbus/core/EventListener.java @@ -1,4 +1,4 @@ -package dev.kske.eventbus; +package dev.kske.eventbus.core; /** * Marker interface for event listeners. Event listeners can contain event handling methods to which diff --git a/src/main/java/dev/kske/eventbus/IEvent.java b/event-bus-core/src/main/java/dev/kske/eventbus/core/IEvent.java similarity index 88% rename from src/main/java/dev/kske/eventbus/IEvent.java rename to event-bus-core/src/main/java/dev/kske/eventbus/core/IEvent.java index fe9e843..bb09b18 100644 --- a/src/main/java/dev/kske/eventbus/IEvent.java +++ b/event-bus-core/src/main/java/dev/kske/eventbus/core/IEvent.java @@ -1,4 +1,4 @@ -package dev.kske.eventbus; +package dev.kske.eventbus.core; /** * Marker interface for event objects. Event objects can be used as event handler parameters and diff --git a/event-bus-core/src/main/java/dev/kske/eventbus/core/package-info.java b/event-bus-core/src/main/java/dev/kske/eventbus/core/package-info.java new file mode 100644 index 0000000..816c30d --- /dev/null +++ b/event-bus-core/src/main/java/dev/kske/eventbus/core/package-info.java @@ -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; diff --git a/event-bus-core/src/main/java/module-info.java b/event-bus-core/src/main/java/module-info.java new file mode 100644 index 0000000..c79f442 --- /dev/null +++ b/event-bus-core/src/main/java/module-info.java @@ -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; +} diff --git a/src/test/java/dev/kske/eventbus/CancelTest.java b/event-bus-core/src/test/java/dev/kske/eventbus/core/CancelTest.java similarity index 96% rename from src/test/java/dev/kske/eventbus/CancelTest.java rename to event-bus-core/src/test/java/dev/kske/eventbus/core/CancelTest.java index 8584f48..0d25f93 100644 --- a/src/test/java/dev/kske/eventbus/CancelTest.java +++ b/event-bus-core/src/test/java/dev/kske/eventbus/core/CancelTest.java @@ -1,4 +1,4 @@ -package dev.kske.eventbus; +package dev.kske.eventbus.core; import static org.junit.jupiter.api.Assertions.assertEquals; diff --git a/src/test/java/dev/kske/eventbus/DispatchTest.java b/event-bus-core/src/test/java/dev/kske/eventbus/core/DispatchTest.java similarity index 97% rename from src/test/java/dev/kske/eventbus/DispatchTest.java rename to event-bus-core/src/test/java/dev/kske/eventbus/core/DispatchTest.java index 83c99e7..a683bc7 100644 --- a/src/test/java/dev/kske/eventbus/DispatchTest.java +++ b/event-bus-core/src/test/java/dev/kske/eventbus/core/DispatchTest.java @@ -1,4 +1,4 @@ -package dev.kske.eventbus; +package dev.kske.eventbus.core; import static org.junit.jupiter.api.Assertions.*; diff --git a/src/test/java/dev/kske/eventbus/SimpleEvent.java b/event-bus-core/src/test/java/dev/kske/eventbus/core/SimpleEvent.java similarity index 81% rename from src/test/java/dev/kske/eventbus/SimpleEvent.java rename to event-bus-core/src/test/java/dev/kske/eventbus/core/SimpleEvent.java index a453acc..ede1afa 100644 --- a/src/test/java/dev/kske/eventbus/SimpleEvent.java +++ b/event-bus-core/src/test/java/dev/kske/eventbus/core/SimpleEvent.java @@ -1,4 +1,4 @@ -package dev.kske.eventbus; +package dev.kske.eventbus.core; /** * A simple event for testing purposes. diff --git a/src/test/java/dev/kske/eventbus/SimpleEventSub.java b/event-bus-core/src/test/java/dev/kske/eventbus/core/SimpleEventSub.java similarity index 83% rename from src/test/java/dev/kske/eventbus/SimpleEventSub.java rename to event-bus-core/src/test/java/dev/kske/eventbus/core/SimpleEventSub.java index 3030eaf..c519fc8 100644 --- a/src/test/java/dev/kske/eventbus/SimpleEventSub.java +++ b/event-bus-core/src/test/java/dev/kske/eventbus/core/SimpleEventSub.java @@ -1,4 +1,4 @@ -package dev.kske.eventbus; +package dev.kske.eventbus.core; /** * Subclass of {@link SimpleEvent} for testing purposes. diff --git a/pom.xml b/pom.xml index 4ddb9ac..6832280 100644 --- a/pom.xml +++ b/pom.xml @@ -5,11 +5,17 @@ dev.kske event-bus - 0.1.0 + 1.0.0 + pom Event Bus An event handling framework for Java utilizing annotations. - https://git.kske.dev/zdm/event-bus + https://git.kske.dev/kske/event-bus + + + event-bus-core + event-bus-ap + @@ -33,8 +39,8 @@ - scm:git:https://git.kske.dev/zdm/event-bus.git - scm:git:ssh:git@git.kske.dev:zdm/event-bus.git + scm:git:https://git.kske.dev/kske/event-bus.git + scm:git:ssh://git@git.kske.dev:420/kske/event-bus.git @@ -46,8 +52,7 @@ - - + @@ -89,13 +94,4 @@ - - - - org.junit.jupiter - junit-jupiter-api - 5.6.2 - test - - \ No newline at end of file diff --git a/src/main/java/dev/kske/eventbus/package-info.java b/src/main/java/dev/kske/eventbus/package-info.java deleted file mode 100644 index 99c8ccf..0000000 --- a/src/main/java/dev/kske/eventbus/package-info.java +++ /dev/null @@ -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; diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java deleted file mode 100644 index 8e10850..0000000 --- a/src/main/java/module-info.java +++ /dev/null @@ -1,12 +0,0 @@ -/** - * Contains the public API and implementation of the event bus library. - * - * @author Kai S. K. Engelbart - * @since 0.0.3 - * @see dev.kske.eventbus.Event - * @see dev.kske.eventbus.EventBus - */ -module dev.kske.eventbus { - - exports dev.kske.eventbus; -}