From c2065aabd1e9155337add00f6e3b7dc665feac6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20S=C3=B6derberg?= Date: Sun, 25 Oct 2020 05:45:38 +0100 Subject: [PATCH] :sparkles: Add parameter injectors (#104) --- CHANGELOG.md | 1 + .../annotations/AnnotationParser.java | 55 ++++++++++--- .../MethodCommandExecutionHandler.java | 36 ++++++-- .../injection/ParameterInjector.java | 53 ++++++++++++ .../injection/ParameterInjectorRegistry.java | 82 +++++++++++++++++++ .../annotations/injection/RawArgs.java | 41 ++++++++++ .../annotations/injection/package-info.java | 28 +++++++ .../annotations/AnnotationParserTest.java | 32 ++++++++ .../annotations/AnnotatedElementAccessor.java | 60 ++++++++++++++ .../annotations/AnnotationAccessor.java | 70 ++++++++++++++++ .../annotations/package-info.java | 28 +++++++ 11 files changed, 469 insertions(+), 17 deletions(-) create mode 100644 cloud-annotations/src/main/java/cloud/commandframework/annotations/injection/ParameterInjector.java create mode 100644 cloud-annotations/src/main/java/cloud/commandframework/annotations/injection/ParameterInjectorRegistry.java create mode 100644 cloud-annotations/src/main/java/cloud/commandframework/annotations/injection/RawArgs.java create mode 100644 cloud-annotations/src/main/java/cloud/commandframework/annotations/injection/package-info.java create mode 100644 cloud-core/src/main/java/cloud/commandframework/annotations/AnnotatedElementAccessor.java create mode 100644 cloud-core/src/main/java/cloud/commandframework/annotations/AnnotationAccessor.java create mode 100644 cloud-core/src/main/java/cloud/commandframework/annotations/package-info.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 213753a2..c69b0a2c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Added access to the CloudBrigadierManager from Brigadier-enabled command managers + - Added parameter injectors ## [1.1.0] - 2020-10-24 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 2ff29100..6b3a8655 100644 --- a/cloud-annotations/src/main/java/cloud/commandframework/annotations/AnnotationParser.java +++ b/cloud-annotations/src/main/java/cloud/commandframework/annotations/AnnotationParser.java @@ -26,6 +26,8 @@ package cloud.commandframework.annotations; import cloud.commandframework.Command; import cloud.commandframework.CommandManager; import cloud.commandframework.Description; +import cloud.commandframework.annotations.injection.ParameterInjectorRegistry; +import cloud.commandframework.annotations.injection.RawArgs; import cloud.commandframework.arguments.CommandArgument; import cloud.commandframework.arguments.flags.CommandFlag; import cloud.commandframework.arguments.parser.ArgumentParseResult; @@ -70,6 +72,7 @@ public final class AnnotationParser { private final SyntaxParser syntaxParser = new SyntaxParser(); private final ArgumentExtractor argumentExtractor = new ArgumentExtractor(); + private final ParameterInjectorRegistry parameterInjectorRegistry = new ParameterInjectorRegistry<>(); private final CommandManager manager; private final Map, Function> annotationMappers; @@ -106,16 +109,26 @@ public final class AnnotationParser { annotation.value(), Caption.of(annotation.failureCaption()) )); + this.getParameterInjectorRegistry().registerInjector( + CommandContext.class, + (context, annotations) -> context + ); + this.getParameterInjectorRegistry().registerInjector( + String[].class, + (context, annotations) -> annotations.annotation(RawArgs.class) == null + ? null + : context.getRawInput().toArray(new String[0]) + ); } @SuppressWarnings("unchecked") static @Nullable A getAnnotationRecursively( - final @NonNull Annotation[] annotations, + final @NonNull AnnotationAccessor annotations, final @NonNull Class clazz, final @NonNull Set> checkedAnnotations ) { A innerCandidate = null; - for (final Annotation annotation : annotations) { + for (final Annotation annotation : annotations.annotations()) { if (!checkedAnnotations.add(annotation.annotationType())) { continue; } @@ -125,7 +138,10 @@ public final class AnnotationParser { if (annotation.annotationType().getPackage().getName().startsWith("java.lang")) { continue; } - final A inner = getAnnotationRecursively(annotation.annotationType().getAnnotations(), clazz, checkedAnnotations); + final A inner = getAnnotationRecursively( + AnnotationAccessor.of(annotation.annotationType()), + clazz, + checkedAnnotations); if (inner != null) { innerCandidate = inner; } @@ -137,9 +153,17 @@ public final class AnnotationParser { final @NonNull Method method, final @NonNull Class clazz ) { - A annotation = getAnnotationRecursively(method.getAnnotations(), clazz, new HashSet<>()); + A annotation = getAnnotationRecursively( + AnnotationAccessor.of(method), + clazz, + new HashSet<>() + ); if (annotation == null) { - annotation = getAnnotationRecursively(method.getDeclaringClass().getAnnotations(), clazz, new HashSet<>()); + annotation = getAnnotationRecursively( + AnnotationAccessor.of(method.getDeclaringClass()), + clazz, + new HashSet<>() + ); } return annotation; } @@ -181,6 +205,17 @@ public final class AnnotationParser { this.preprocessorMappers.put(annotation, preprocessorMapper); } + /** + * Get the parameter injector registry instance that is used to inject non-{@link Argument argument} parameters + * into {@link CommandMethod} annotated {@link Method methods} + * + * @return Parameter injector registry + * @since 1.2.0 + */ + public @NonNull ParameterInjectorRegistry getParameterInjectorRegistry() { + return this.parameterInjectorRegistry; + } + /** * Scan a class instance of {@link CommandMethod} annotations and attempt to * compile them into {@link Command} instances @@ -302,7 +337,7 @@ public final class AnnotationParser { try { /* Construct the handler */ final CommandExecutionHandler commandExecutionHandler - = new MethodCommandExecutionHandler<>(instance, commandArguments, method); + = new MethodCommandExecutionHandler<>(instance, commandArguments, method, this.parameterInjectorRegistry); builder = builder.handler(commandExecutionHandler); } catch (final Exception e) { throw new RuntimeException("Failed to construct command execution handler", e); @@ -399,10 +434,10 @@ public final class AnnotationParser { this.manager.getParserRegistry().getSuggestionProvider(suggestionProviderName); argumentBuilder.withSuggestionsProvider( suggestionsFunction.orElseThrow(() -> - new IllegalArgumentException(String.format( - "There is no suggestion provider with name '%s'. Did you forget to register it?", - suggestionProviderName - ))) + new IllegalArgumentException(String.format( + "There is no suggestion provider with name '%s'. Did you forget to register it?", + suggestionProviderName + ))) ); } /* Build the argument */ diff --git a/cloud-annotations/src/main/java/cloud/commandframework/annotations/MethodCommandExecutionHandler.java b/cloud-annotations/src/main/java/cloud/commandframework/annotations/MethodCommandExecutionHandler.java index 159b90a6..184c7174 100644 --- a/cloud-annotations/src/main/java/cloud/commandframework/annotations/MethodCommandExecutionHandler.java +++ b/cloud-annotations/src/main/java/cloud/commandframework/annotations/MethodCommandExecutionHandler.java @@ -23,6 +23,8 @@ // package cloud.commandframework.annotations; +import cloud.commandframework.annotations.injection.ParameterInjector; +import cloud.commandframework.annotations.injection.ParameterInjectorRegistry; import cloud.commandframework.arguments.CommandArgument; import cloud.commandframework.arguments.flags.FlagContext; import cloud.commandframework.context.CommandContext; @@ -34,6 +36,7 @@ import java.lang.invoke.MethodHandles; import java.lang.reflect.Method; import java.lang.reflect.Parameter; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.Map; @@ -42,17 +45,21 @@ class MethodCommandExecutionHandler implements CommandExecutionHandler { private final Parameter[] parameters; private final MethodHandle methodHandle; private final Map> commandArguments; + private final ParameterInjectorRegistry injectorRegistry; + private final AnnotationAccessor annotationAccessor; MethodCommandExecutionHandler( final @NonNull Object instance, - final @NonNull Map<@NonNull String, - @NonNull CommandArgument<@NonNull C, @NonNull ?>> commandArguments, - @NonNull final Method method + final @NonNull Map<@NonNull String, @NonNull CommandArgument<@NonNull C, @NonNull ?>> commandArguments, + final @NonNull Method method, + final @NonNull ParameterInjectorRegistry injectorRegistry ) throws Exception { this.commandArguments = commandArguments; method.setAccessible(true); this.methodHandle = MethodHandles.lookup().unreflect(method).bindTo(instance); this.parameters = method.getParameters(); + this.injectorRegistry = injectorRegistry; + this.annotationAccessor = AnnotationAccessor.of(method); } @Override @@ -82,10 +89,25 @@ class MethodCommandExecutionHandler implements CommandExecutionHandler { if (parameter.getType().isAssignableFrom(commandContext.getSender().getClass())) { arguments.add(commandContext.getSender()); } else { - throw new IllegalArgumentException(String.format( - "Unknown command parameter '%s' in method '%s'", - parameter.getName(), this.methodHandle.toString() - )); + final Collection> injectors = this.injectorRegistry.injectors(parameter.getType()); + Object value = null; + for (final ParameterInjector injector : injectors) { + value = injector.create( + commandContext, + this.annotationAccessor + ); + if (value != null) { + break; + } + } + if (value != null) { + arguments.add(value); + } else { + throw new IllegalArgumentException(String.format( + "Unknown command parameter '%s' in method '%s'", + parameter.getName(), this.methodHandle.toString() + )); + } } } } diff --git a/cloud-annotations/src/main/java/cloud/commandframework/annotations/injection/ParameterInjector.java b/cloud-annotations/src/main/java/cloud/commandframework/annotations/injection/ParameterInjector.java new file mode 100644 index 00000000..ebeecdbf --- /dev/null +++ b/cloud-annotations/src/main/java/cloud/commandframework/annotations/injection/ParameterInjector.java @@ -0,0 +1,53 @@ +// +// MIT License +// +// Copyright (c) 2020 Alexander Söderberg & Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +package cloud.commandframework.annotations.injection; + +import cloud.commandframework.annotations.AnnotationAccessor; +import cloud.commandframework.context.CommandContext; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; + +/** + * Injector that injects parameters into {@link cloud.commandframework.annotations.CommandMethod} annotated + * methods + * + * @param Command sender type + * @param Type of the value that is injected by this injector + * @since 1.2.0 + */ +@FunctionalInterface +public interface ParameterInjector { + + /** + * Attempt to create a a value that should then be injected into the {@link cloud.commandframework.annotations.CommandMethod} + * annotated method. If the injector cannot (or shouldn't) create a value, it is free to return {@code null}. + * + * @param context Command context that is requesting the injection + * @param annotationAccessor Annotation accessor proxying the method which the value is being injected into + * @return The value, if it could be created. Else {@code null}, in which case no value will be injected + * by this particular injector + */ + @Nullable T create(@NonNull CommandContext context, @NonNull AnnotationAccessor annotationAccessor); + +} diff --git a/cloud-annotations/src/main/java/cloud/commandframework/annotations/injection/ParameterInjectorRegistry.java b/cloud-annotations/src/main/java/cloud/commandframework/annotations/injection/ParameterInjectorRegistry.java new file mode 100644 index 00000000..9ae97b9c --- /dev/null +++ b/cloud-annotations/src/main/java/cloud/commandframework/annotations/injection/ParameterInjectorRegistry.java @@ -0,0 +1,82 @@ +// +// MIT License +// +// Copyright (c) 2020 Alexander Söderberg & Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +package cloud.commandframework.annotations.injection; + +import org.checkerframework.checker.nullness.qual.NonNull; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +/** + * Registry containing mappings between {@link Class classes} and {@link ParameterInjector injectors} + * + * @param Command sender type + * @since 1.2.0 + */ +public final class ParameterInjectorRegistry { + + private volatile int injectorCount = 0; + private final Map, List>> injectors = new HashMap<>(); + + /** + * Register an injector for a particular type + * + * @param clazz Type that the injector should inject for. This type will matched using + * {@link Class#isAssignableFrom(Class)} + * @param injector The injector that should inject the value into the command method + * @param Injected type + */ + public synchronized void registerInjector( + final @NonNull Class clazz, + final @NonNull ParameterInjector injector + ) { + this.injectors.computeIfAbsent(clazz, missingClass -> new LinkedList<>()).add(injector); + this.injectorCount++; + } + + /** + * Get a collection of all injectors that could potentially inject a value of the given type + * + * @param clazz Type to query for + * @param Generic type + * @return Immutable collection containing all injectors that could potentially inject a value of the given type + */ + public synchronized @NonNull Collection<@NonNull ParameterInjector> injectors( + final @NonNull Class clazz + ) { + final List<@NonNull ParameterInjector> injectors = new ArrayList<>(this.injectorCount); + for (final Map.Entry, List>> entry : this.injectors.entrySet()) { + if (clazz.isAssignableFrom(entry.getKey())) { + injectors.addAll(entry.getValue()); + } + } + return Collections.unmodifiableCollection(injectors); + } + +} diff --git a/cloud-annotations/src/main/java/cloud/commandframework/annotations/injection/RawArgs.java b/cloud-annotations/src/main/java/cloud/commandframework/annotations/injection/RawArgs.java new file mode 100644 index 00000000..c7ed5566 --- /dev/null +++ b/cloud-annotations/src/main/java/cloud/commandframework/annotations/injection/RawArgs.java @@ -0,0 +1,41 @@ +// +// MIT License +// +// Copyright (c) 2020 Alexander Söderberg & Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +package cloud.commandframework.annotations.injection; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Used to inject {@link cloud.commandframework.context.CommandContext#getRawInput()} into a + * {@link cloud.commandframework.annotations.CommandMethod} + *

+ * This should only be used on {@code String[]} + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface RawArgs { + +} diff --git a/cloud-annotations/src/main/java/cloud/commandframework/annotations/injection/package-info.java b/cloud-annotations/src/main/java/cloud/commandframework/annotations/injection/package-info.java new file mode 100644 index 00000000..c4b6abf2 --- /dev/null +++ b/cloud-annotations/src/main/java/cloud/commandframework/annotations/injection/package-info.java @@ -0,0 +1,28 @@ +// +// MIT License +// +// Copyright (c) 2020 Alexander Söderberg & Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +/** + * Classes related to parameter injection + */ +package cloud.commandframework.annotations.injection; diff --git a/cloud-annotations/src/test/java/cloud/commandframework/annotations/AnnotationParserTest.java b/cloud-annotations/src/test/java/cloud/commandframework/annotations/AnnotationParserTest.java index 3c00dea2..f0003cb0 100644 --- a/cloud-annotations/src/test/java/cloud/commandframework/annotations/AnnotationParserTest.java +++ b/cloud-annotations/src/test/java/cloud/commandframework/annotations/AnnotationParserTest.java @@ -61,6 +61,11 @@ class AnnotationParserTest { "some-name", (context, input) -> NAMED_SUGGESTIONS ); + /* Register a parameter injector */ + annotationParser.getParameterInjectorRegistry().registerInjector( + InjectableValue.class, + (context, annotations) -> new InjectableValue("Hello World!") + ); } @Test @@ -109,6 +114,11 @@ class AnnotationParserTest { final Regex regex = AnnotationParser.getMethodOrClassAnnotation(annotatedMethod, Regex.class); } + @Test + void testParameterInjection() { + manager.executeCommand(new TestCommandSender(), "inject").join(); + } + @ProxiedBy("proxycommand") @CommandMethod("test|t literal [string]") public void testCommand( @@ -136,6 +146,12 @@ class AnnotationParserTest { ) { } + @CommandMethod("inject") + public void testInjectedParameters( + final InjectableValue injectableValue + ) { + System.out.printf("Injected value: %s\n", injectableValue.toString()); + } @CommandPermission("some.permission") @Target(ElementType.METHOD) @@ -169,4 +185,20 @@ class AnnotationParserTest { private @interface Bad2 { } + + private static final class InjectableValue { + + private final String value; + + private InjectableValue(final String value) { + this.value = value; + } + + @Override + public String toString() { + return this.value; + } + + } + } diff --git a/cloud-core/src/main/java/cloud/commandframework/annotations/AnnotatedElementAccessor.java b/cloud-core/src/main/java/cloud/commandframework/annotations/AnnotatedElementAccessor.java new file mode 100644 index 00000000..02eeeac2 --- /dev/null +++ b/cloud-core/src/main/java/cloud/commandframework/annotations/AnnotatedElementAccessor.java @@ -0,0 +1,60 @@ +// +// MIT License +// +// Copyright (c) 2020 Alexander Söderberg & Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +package cloud.commandframework.annotations; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Objects; + +final class AnnotatedElementAccessor implements AnnotationAccessor { + + private final AnnotatedElement element; + + AnnotatedElementAccessor(final @NonNull AnnotatedElement element) { + this.element = Objects.requireNonNull(element, "Method may not be null"); + } + + @Override + public @Nullable A annotation( + @NonNull final Class clazz + ) { + try { + return element.getAnnotation(clazz); + } catch (final NullPointerException exception) { + return null; + } + } + + @Override + public @NonNull Collection<@NonNull Annotation> annotations() { + return Collections.unmodifiableCollection(Arrays.asList(this.element.getAnnotations())); + } + +} diff --git a/cloud-core/src/main/java/cloud/commandframework/annotations/AnnotationAccessor.java b/cloud-core/src/main/java/cloud/commandframework/annotations/AnnotationAccessor.java new file mode 100644 index 00000000..79b92d00 --- /dev/null +++ b/cloud-core/src/main/java/cloud/commandframework/annotations/AnnotationAccessor.java @@ -0,0 +1,70 @@ +// +// MIT License +// +// Copyright (c) 2020 Alexander Söderberg & Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +package cloud.commandframework.annotations; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.util.Collection; + +/** + * Managed access for {@link java.lang.annotation.Annotation} instances + * + * @since 1.2.0 + */ +public interface AnnotationAccessor { + + /** + * Get a {@link AnnotationAccessor} instance for a {@link AnnotatedElement}, such as + * a {@link Class} or a {@link java.lang.reflect.Method}. This instance can then be + * used as a proxy for retrieving the element's annotations + * + * @param element Annotated element that will be proxied by the accessor + * @return Annotation accessor proxying the given annotated element + */ + static @NonNull AnnotationAccessor of(final @NonNull AnnotatedElement element) { + return new AnnotatedElementAccessor(element); + } + + /** + * Get an annotation instance, if it's present. If the annotation + * isn't available, this will return {@code null} + * + * @param clazz Annotation class + * @param Annotation type + * @return Annotation instance, or {@code null} + */ + @Nullable A annotation(@NonNull Class clazz); + + /** + * Get an immutable collection containing all of the annotations that + * are accessible using the annotation accessor + * + * @return Immutable collection of annotations + */ + @NonNull Collection<@NonNull Annotation> annotations(); + +} diff --git a/cloud-core/src/main/java/cloud/commandframework/annotations/package-info.java b/cloud-core/src/main/java/cloud/commandframework/annotations/package-info.java new file mode 100644 index 00000000..5c407501 --- /dev/null +++ b/cloud-core/src/main/java/cloud/commandframework/annotations/package-info.java @@ -0,0 +1,28 @@ +// +// MIT License +// +// Copyright (c) 2020 Alexander Söderberg & Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +/** + * Annotation and annotation parsing related classes and utilities + */ +package cloud.commandframework.annotations;