diff --git a/CHANGELOG.md b/CHANGELOG.md index 456f4dc3..8139892e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Allow for combined presence flags, such that `-a -b -c` is equivalent to `-abc` + - Allow for class annotations as a default for when an annotation is not present on a method. ## [1.0.2] - 2020-10-18 diff --git a/cloud-annotations/src/main/java/cloud/commandframework/annotations/AnnotationParser.java b/cloud-annotations/src/main/java/cloud/commandframework/annotations/AnnotationParser.java index f489d59d..4c8e6831 100644 --- a/cloud-annotations/src/main/java/cloud/commandframework/annotations/AnnotationParser.java +++ b/cloud-annotations/src/main/java/cloud/commandframework/annotations/AnnotationParser.java @@ -106,6 +106,25 @@ public final class AnnotationParser { )); } + protected static @Nullable A getMethodOrClassAnnotation( + final @NonNull Method method, + final @NonNull Class clazz + ) { + if (method.isAnnotationPresent(clazz)) { + return method.getAnnotation(clazz); + } else if (method.getDeclaringClass().isAnnotationPresent(clazz)) { + return method.getDeclaringClass().getAnnotation(clazz); + } + return null; + } + + protected static boolean methodOrClassHasAnnotation( + final @NonNull Method method, + final @NonNull Class clazz + ) { + return getMethodOrClassAnnotation(method, clazz) != null; + } + /** * Register an annotation mapper * @@ -186,8 +205,8 @@ public final class AnnotationParser { final String commandToken = commandMethod.value().split(" ")[0].split("\\|")[0]; @SuppressWarnings("ALL") final CommandManager manager = this.manager; final SimpleCommandMeta.Builder metaBuilder = SimpleCommandMeta.builder() - .with(this.metaFactory.apply(method.getAnnotations())); - if (method.isAnnotationPresent(Confirmation.class)) { + .with(this.metaFactory.apply(method)); + if (methodOrClassHasAnnotation(method, Confirmation.class)) { metaBuilder.with(CommandConfirmationManager.CONFIRMATION_REQUIRED_META, "true"); } @@ -245,8 +264,9 @@ public final class AnnotationParser { } } - if (method.isAnnotationPresent(CommandPermission.class)) { - builder = builder.permission(method.getAnnotation(CommandPermission.class).value()); + final CommandPermission commandPermission = getMethodOrClassAnnotation(method, CommandPermission.class); + if (commandPermission != null) { + builder = builder.permission(commandPermission.value()); } if (commandMethod.requiredSender() != Object.class) { @@ -263,7 +283,7 @@ public final class AnnotationParser { throw new RuntimeException("Failed to construct command execution handler", e); } /* Check if the command should be hidden */ - if (method.isAnnotationPresent(Hidden.class)) { + if (methodOrClassHasAnnotation(method, Hidden.class)) { builder = builder.hidden(); } /* Apply flags */ diff --git a/cloud-annotations/src/main/java/cloud/commandframework/annotations/CommandDescription.java b/cloud-annotations/src/main/java/cloud/commandframework/annotations/CommandDescription.java index b81b03d7..2dc6cdf9 100644 --- a/cloud-annotations/src/main/java/cloud/commandframework/annotations/CommandDescription.java +++ b/cloud-annotations/src/main/java/cloud/commandframework/annotations/CommandDescription.java @@ -34,7 +34,7 @@ import java.lang.annotation.Target; * Maps to {@link StandardParameters#DESCRIPTION} */ @Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.METHOD) +@Target({ElementType.METHOD, ElementType.TYPE}) public @interface CommandDescription { /** diff --git a/cloud-annotations/src/main/java/cloud/commandframework/annotations/CommandPermission.java b/cloud-annotations/src/main/java/cloud/commandframework/annotations/CommandPermission.java index aac3d23e..907f421c 100644 --- a/cloud-annotations/src/main/java/cloud/commandframework/annotations/CommandPermission.java +++ b/cloud-annotations/src/main/java/cloud/commandframework/annotations/CommandPermission.java @@ -31,7 +31,7 @@ import java.lang.annotation.Target; /** * Equivalent to {@link cloud.commandframework.Command.Builder#permission(String)} */ -@Target(ElementType.METHOD) +@Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface CommandPermission { diff --git a/cloud-annotations/src/main/java/cloud/commandframework/annotations/Confirmation.java b/cloud-annotations/src/main/java/cloud/commandframework/annotations/Confirmation.java index ff961bf6..348d9d33 100644 --- a/cloud-annotations/src/main/java/cloud/commandframework/annotations/Confirmation.java +++ b/cloud-annotations/src/main/java/cloud/commandframework/annotations/Confirmation.java @@ -32,7 +32,7 @@ import java.lang.annotation.Target; * Require confirmation for the command */ @Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.METHOD) +@Target({ElementType.METHOD, ElementType.TYPE}) public @interface Confirmation { } diff --git a/cloud-annotations/src/main/java/cloud/commandframework/annotations/Hidden.java b/cloud-annotations/src/main/java/cloud/commandframework/annotations/Hidden.java index 4bea3cd0..5995c7f2 100644 --- a/cloud-annotations/src/main/java/cloud/commandframework/annotations/Hidden.java +++ b/cloud-annotations/src/main/java/cloud/commandframework/annotations/Hidden.java @@ -33,7 +33,7 @@ import java.lang.annotation.Target; /** * Indicates that the command should be hidden. Similar to {@link Command.Builder#hidden()} */ -@Target(ElementType.METHOD) +@Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface Hidden { diff --git a/cloud-annotations/src/main/java/cloud/commandframework/annotations/MetaFactory.java b/cloud-annotations/src/main/java/cloud/commandframework/annotations/MetaFactory.java index 781d2a90..a87508c0 100644 --- a/cloud-annotations/src/main/java/cloud/commandframework/annotations/MetaFactory.java +++ b/cloud-annotations/src/main/java/cloud/commandframework/annotations/MetaFactory.java @@ -28,9 +28,10 @@ import cloud.commandframework.meta.CommandMeta; import org.checkerframework.checker.nullness.qual.NonNull; import java.lang.annotation.Annotation; +import java.lang.reflect.Method; import java.util.function.Function; -class MetaFactory implements Function<@NonNull Annotation @NonNull [], @NonNull CommandMeta> { +class MetaFactory implements Function<@NonNull Method, @NonNull CommandMeta> { private final AnnotationParser annotationParser; private final Function metaMapper; @@ -44,17 +45,16 @@ class MetaFactory implements Function<@NonNull Annotation @NonNull [], @NonNull } @Override - public @NonNull CommandMeta apply(final @NonNull Annotation @NonNull [] annotations) { + public @NonNull CommandMeta apply(final @NonNull Method method) { final ParserParameters parameters = ParserParameters.empty(); - for (final Annotation annotation : annotations) { - @SuppressWarnings("ALL") final Function function = this.annotationParser.getAnnotationMappers() - .get(annotation.annotationType()); - if (function == null) { - continue; + this.annotationParser.getAnnotationMappers().forEach(((annotationClass, mapper) -> { + final Annotation annotation = AnnotationParser.getMethodOrClassAnnotation(method, annotationClass); + if (annotation != null) { + @SuppressWarnings("ALL") final Function function = (Function) mapper; + //noinspection unchecked + parameters.merge((ParserParameters) function.apply(annotation)); } - //noinspection unchecked - parameters.merge((ParserParameters) function.apply(annotation)); - } + })); return this.metaMapper.apply(parameters); }