diff --git a/ERROR_CODES.md b/ERROR_CODES.md index c4b05e2a..14fc0fc1 100644 --- a/ERROR_CODES.md +++ b/ERROR_CODES.md @@ -138,6 +138,27 @@ public class NotAController { // Wrong, should be a controller or component } ``` +### 1010: `Method '*' annotated with @onKey in class '*' must either take no or exactly one parameter of type KeyEvent.` + +- Runtime: ✅ +- Annotation Processor: ✅ + +This error is thrown when a method annotated with `@onKey` has more than one parameter or a parameter that is not of type +`KeyEvent`. + +```java + +@onKey +public void onKey(String key) { // Wrong, should not have a parameter or have a parameter of type KeyEvent + // ... +} + +@onKey +public void onKey(KeyEvent event, String other) { // Wrong, should not have more than one parameter + // ... +} +``` + ## Resources ### 2000: `Could not find resource '*'.` diff --git a/annotation-processor/src/main/java/org/fulib/fx/ControllerAnnotationProcessor.java b/annotation-processor/src/main/java/org/fulib/fx/ControllerAnnotationProcessor.java index 9c7181d7..4b7e3693 100644 --- a/annotation-processor/src/main/java/org/fulib/fx/ControllerAnnotationProcessor.java +++ b/annotation-processor/src/main/java/org/fulib/fx/ControllerAnnotationProcessor.java @@ -7,6 +7,7 @@ import org.fulib.fx.annotation.controller.Resource; import org.fulib.fx.annotation.controller.SubComponent; import org.fulib.fx.annotation.controller.Title; +import org.fulib.fx.annotation.event.onKey; import org.fulib.fx.annotation.param.Params; import org.fulib.fx.annotation.param.ParamsMap; import org.fulib.fx.util.ControllerUtil; @@ -76,9 +77,21 @@ public boolean process(Set annotations, RoundEnvironment checkTitle(element); } + for (Element element : roundEnv.getElementsAnnotatedWith(onKey.class)) { + checkOnKey(element); + } + return true; } + private void checkOnKey(Element element) { + if (element instanceof ExecutableElement method) { + if (!method.getParameters().isEmpty() && !(method.getParameters().size() == 1 && processingEnv.getTypeUtils().isAssignable(method.getParameters().get(0).asType(), processingEnv.getElementUtils().getTypeElement("javafx.scene.input.KeyEvent").asType()))) { + processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, error(1010).formatted(method.getSimpleName(), method.getEnclosingElement().asType().toString()), method); + } + } + } + private void checkResources(Element element) { final String elementType = element.asType().toString(); if (!processingEnv.getTypeUtils().isSubtype(element.asType(), processingEnv.getElementUtils().getTypeElement("java.util.ResourceBundle").asType())) { diff --git a/framework/src/main/java/org/fulib/fx/controller/ControllerManager.java b/framework/src/main/java/org/fulib/fx/controller/ControllerManager.java index 034ad7ae..c561aea6 100644 --- a/framework/src/main/java/org/fulib/fx/controller/ControllerManager.java +++ b/framework/src/main/java/org/fulib/fx/controller/ControllerManager.java @@ -58,7 +58,7 @@ public class ControllerManager { private static ResourceBundle defaultResourceBundle; - private final Map>>> keyEventHandlers = new HashMap<>(); + private final Map> keyEventHandlers = new HashMap<>(); @Inject Lazy app; @@ -233,20 +233,16 @@ else if (view.startsWith("#")) { */ private void registerKeyEvents(Object instance) { Reflection.getMethodsWithAnnotation(instance.getClass(), onKey.class).forEach(method -> { + onKey annotation = method.getAnnotation(onKey.class); EventType type = annotation.type().asEventType(); + EventHandler handler = createKeyEventHandler(method, instance, annotation); + + keyEventHandlers.computeIfAbsent(instance, k -> new HashSet<>()).add(new KeyEventHolder(annotation.target(), type, handler)); switch (annotation.target()) { - case SCENE -> { - EventHandler handler = createKeyEventHandler(method, instance, annotation); - keyEventHandlers.computeIfAbsent(instance, k -> new HashSet<>()).add(new Pair<>(onKey.Target.SCENE, handler)); - app.get().stage().getScene().addEventFilter(type, handler); - } - case STAGE -> { - EventHandler handler = createKeyEventHandler(method, instance, annotation); - keyEventHandlers.computeIfAbsent(instance, k -> new HashSet<>()).add(new Pair<>(onKey.Target.STAGE, handler)); - app.get().stage().addEventFilter(type, handler); - } + case SCENE -> app.get().stage().getScene().addEventFilter(type, handler); + case STAGE -> app.get().stage().addEventFilter(type, handler); } }); } @@ -663,18 +659,15 @@ public void setDefaultResourceBundle(ResourceBundle resourceBundle) { */ private EventHandler createKeyEventHandler(Method method, Object instance, onKey annotation) { boolean hasEventParameter = method.getParameterCount() == 1 && method.getParameterTypes()[0].isAssignableFrom(KeyEvent.class); + + if (!hasEventParameter && method.getParameterCount() != 0) { + throw new RuntimeException(error(1010).formatted(method.getName(), instance.getClass().getName())); + } + method.setAccessible(true); return event -> { - // TODO: Utility method - if ((annotation.code() == KeyCode.UNDEFINED || event.getCode() == annotation.code()) && - (annotation.character().isEmpty() || event.getCharacter().equals(annotation.character())) && - (annotation.text().isEmpty() || event.getText().equals(annotation.text())) && - (event.isShiftDown() || !annotation.shift()) && - (event.isControlDown() || !annotation.control()) && - (event.isAltDown() || !annotation.alt()) && - (event.isMetaDown() || !annotation.meta()) - ) { + if (keyEventMatchesAnnotation(event, annotation)) { try { if (hasEventParameter) { method.invoke(instance, event); @@ -682,31 +675,40 @@ private EventHandler createKeyEventHandler(Method method, Object insta method.invoke(instance); } } catch (IllegalAccessException | InvocationTargetException e) { - throw new RuntimeException("Couldn't call method '" + method.getName() + "' in class '" + instance.getClass().getName() + "' on key event.", e); + throw new RuntimeException(error(1005).formatted(method.getName(), annotation.getClass().getSimpleName(), method.getClass()), e); } } }; } + private boolean keyEventMatchesAnnotation(KeyEvent event, onKey annotation) { + return (annotation.code() == KeyCode.UNDEFINED || event.getCode() == annotation.code()) && + (annotation.character().isEmpty() || event.getCharacter().equals(annotation.character())) && + (annotation.text().isEmpty() || event.getText().equals(annotation.text())) && + (event.isShiftDown() || !annotation.shift()) && + (event.isControlDown() || !annotation.control()) && + (event.isAltDown() || !annotation.alt()) && + (event.isMetaDown() || !annotation.meta()); + } + /** * Clears all key handlers registered for the given instance. * * @param instance The instance to clear the key handlers for */ private void cleanUpListeners(Object instance) { - Collection>> handlers = keyEventHandlers.get(instance); + Collection handlers = keyEventHandlers.get(instance); if (handlers != null) { - for (Pair> handler : handlers) { - switch (handler.getKey()) { - case SCENE -> app.get().stage().getScene().removeEventFilter(KeyEvent.ANY, handler.getValue()); - case STAGE -> app.get().stage().removeEventFilter(KeyEvent.ANY, handler.getValue()); + for (KeyEventHolder holder : handlers) { + switch (holder.target()) { + case SCENE -> app.get().stage().getScene().removeEventFilter(holder.type(), holder.handler()); + case STAGE -> app.get().stage().removeEventFilter(holder.type(), holder.handler()); } } keyEventHandlers.remove(instance); } } - /** * Returns the title of the given controller instance if it has one. * If the title is a key, the title will be looked up in the resource bundle of the controller. diff --git a/framework/src/main/java/org/fulib/fx/util/KeyEventHolder.java b/framework/src/main/java/org/fulib/fx/util/KeyEventHolder.java new file mode 100644 index 00000000..3f9410cb --- /dev/null +++ b/framework/src/main/java/org/fulib/fx/util/KeyEventHolder.java @@ -0,0 +1,13 @@ +package org.fulib.fx.util; + +import javafx.event.EventHandler; +import javafx.event.EventType; +import javafx.scene.input.KeyEvent; +import org.fulib.fx.annotation.event.onKey; + +public record KeyEventHolder( + onKey.Target target, + EventType type, + EventHandler handler +) { +} diff --git a/framework/src/main/resources/org/fulib/fx/lang/error.properties b/framework/src/main/resources/org/fulib/fx/lang/error.properties index 11badd91..e68ddb79 100644 --- a/framework/src/main/resources/org/fulib/fx/lang/error.properties +++ b/framework/src/main/resources/org/fulib/fx/lang/error.properties @@ -9,6 +9,7 @@ 1007=Class '%s' is annotated with both @Controller and @Component. 1008=Method '%s' providing the view for class '%s' must not have any parameters. 1009=Class '%s' annotated with @Title must be a controller or component. +1010=Method '%s' annotated with @onKey in class '%s' must either take no or exactly one parameter of type KeyEvent. # Resources 2000=Could not find resource '%s'.