From 2f077e03f3101d271fac21d3a40e2f981801db93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20S=C3=B6derberg?= Date: Mon, 14 Dec 2020 22:52:27 +0100 Subject: [PATCH] :sparkles: Improve injection --- CHANGELOG.md | 3 + .../annotations/AnnotationParser.java | 15 ++--- .../annotations/injection/RawArgs.java | 1 - .../commandframework/CommandManager.java | 22 ++++++- .../annotations/AnnotationAccessor.java | 31 +++++++++ .../injection/ParameterInjectorRegistry.java | 0 .../annotations/injection/package-info.java | 30 +++++++++ .../context/CommandContext.java | 64 +++++++++++++++++++ .../context/CommandContextFactory.java | 18 ++++++ .../StandardCommandContextFactory.java | 25 +++++--- .../commandframework/CommandTreeTest.java | 18 +++--- .../brigadier/CloudBrigadierManager.java | 2 +- .../bukkit/CloudCommodoreManager.java | 2 +- .../paper/PaperBrigadierListener.java | 2 +- .../VelocityPluginRegistrationHandler.java | 2 +- 15 files changed, 202 insertions(+), 33 deletions(-) rename {cloud-annotations => cloud-core}/src/main/java/cloud/commandframework/annotations/injection/ParameterInjectorRegistry.java (100%) create mode 100644 cloud-core/src/main/java/cloud/commandframework/annotations/injection/package-info.java diff --git a/CHANGELOG.md b/CHANGELOG.md index dec0dcdb..cf36e71d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed + - Moved the parser injector registry into CommandManager and added injection to CommandContext + ## [1.2.0] - 2020-12-07 ### Added 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 1f02c75a..45aff3eb 100644 --- a/cloud-annotations/src/main/java/cloud/commandframework/annotations/AnnotationParser.java +++ b/cloud-annotations/src/main/java/cloud/commandframework/annotations/AnnotationParser.java @@ -73,7 +73,6 @@ 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; @@ -110,10 +109,6 @@ 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 @@ -215,7 +210,7 @@ public final class AnnotationParser { * @since 1.2.0 */ public @NonNull ParameterInjectorRegistry getParameterInjectorRegistry() { - return this.parameterInjectorRegistry; + return this.manager.parameterInjectorRegistry(); } /** @@ -344,8 +339,12 @@ public final class AnnotationParser { } try { /* Construct the handler */ - final CommandExecutionHandler commandExecutionHandler - = new MethodCommandExecutionHandler<>(instance, commandArguments, method, this.parameterInjectorRegistry); + final CommandExecutionHandler commandExecutionHandler = new MethodCommandExecutionHandler<>( + instance, + commandArguments, + method, + this.getParameterInjectorRegistry() + ); builder = builder.handler(commandExecutionHandler); } catch (final Exception e) { throw new RuntimeException("Failed to construct command execution handler", e); 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 index b4588606..f69c56b2 100644 --- a/cloud-annotations/src/main/java/cloud/commandframework/annotations/injection/RawArgs.java +++ b/cloud-annotations/src/main/java/cloud/commandframework/annotations/injection/RawArgs.java @@ -34,7 +34,6 @@ import java.lang.annotation.Target; *

* This should only be used on {@code String[]} * - * * @since 1.2.0 */ @Target(ElementType.METHOD) diff --git a/cloud-core/src/main/java/cloud/commandframework/CommandManager.java b/cloud-core/src/main/java/cloud/commandframework/CommandManager.java index 5fc32cd6..9ebf6a71 100644 --- a/cloud-core/src/main/java/cloud/commandframework/CommandManager.java +++ b/cloud-core/src/main/java/cloud/commandframework/CommandManager.java @@ -23,6 +23,7 @@ // package cloud.commandframework; +import cloud.commandframework.annotations.injection.ParameterInjectorRegistry; import cloud.commandframework.arguments.CommandArgument; import cloud.commandframework.arguments.CommandSuggestionEngine; import cloud.commandframework.arguments.CommandSyntaxFormatter; @@ -88,6 +89,7 @@ public abstract class CommandManager { private final ServicePipeline servicePipeline = ServicePipeline.builder().build(); private final ParserRegistry parserRegistry = new StandardParserRegistry<>(); private final Collection> commands = new LinkedList<>(); + private final ParameterInjectorRegistry parameterInjectorRegistry = new ParameterInjectorRegistry<>(); private final CommandExecutionCoordinator commandExecutionCoordinator; private final CommandTree commandTree; private final CommandSuggestionEngine commandSuggestionEngine; @@ -128,6 +130,11 @@ public abstract class CommandManager { }, new AcceptingCommandPostprocessor<>()); /* Create the caption registry */ this.captionRegistry = new SimpleCaptionRegistryFactory().create(); + /* Register default injectors */ + this.parameterInjectorRegistry().registerInjector( + CommandContext.class, + (context, annotationAccessor) -> context + ); } /** @@ -158,7 +165,7 @@ public abstract class CommandManager { final CommandContext context = this.commandContextFactory.create( false, commandSender, - this.captionRegistry + this ); final LinkedList inputQueue = new CommandInputTokenizer(input).tokenize(); try { @@ -191,7 +198,7 @@ public abstract class CommandManager { final CommandContext context = this.commandContextFactory.create( true, commandSender, - this.captionRegistry + this ); return this.commandSuggestionEngine.getSuggestions(context, input); } @@ -670,6 +677,17 @@ public abstract class CommandManager { return this.parserRegistry; } + /** + * Get the parameter injector registry instance + * + * @return Parameter injector registry + * @since 1.3.0 + */ + public final @NonNull ParameterInjectorRegistry parameterInjectorRegistry() { + return this.parameterInjectorRegistry; + } + + /** * Get the exception handler for an exception type, if one has been registered * diff --git a/cloud-core/src/main/java/cloud/commandframework/annotations/AnnotationAccessor.java b/cloud-core/src/main/java/cloud/commandframework/annotations/AnnotationAccessor.java index 79b92d00..86381574 100644 --- a/cloud-core/src/main/java/cloud/commandframework/annotations/AnnotationAccessor.java +++ b/cloud-core/src/main/java/cloud/commandframework/annotations/AnnotationAccessor.java @@ -29,6 +29,7 @@ import org.checkerframework.checker.nullness.qual.Nullable; import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; import java.util.Collection; +import java.util.Collections; /** * Managed access for {@link java.lang.annotation.Annotation} instances @@ -37,6 +38,16 @@ import java.util.Collection; */ public interface AnnotationAccessor { + /** + * Get a {@link AnnotationAccessor} that cannot access any annotations + * + * @return Empty annotation accessor + * @since 1.3.0 + */ + static @NonNull AnnotationAccessor empty() { + return new NullAnnotationAccessor(); + } + /** * 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 @@ -67,4 +78,24 @@ public interface AnnotationAccessor { */ @NonNull Collection<@NonNull Annotation> annotations(); + + /** + * Annotation accessor that cannot access any annotations + * + * @since 1.3.0 + */ + final class NullAnnotationAccessor implements AnnotationAccessor { + + @Override + public @Nullable A annotation(@NonNull final Class clazz) { + return null; + } + + @Override + public @NonNull Collection<@NonNull Annotation> annotations() { + return Collections.emptyList(); + } + + } + } diff --git a/cloud-annotations/src/main/java/cloud/commandframework/annotations/injection/ParameterInjectorRegistry.java b/cloud-core/src/main/java/cloud/commandframework/annotations/injection/ParameterInjectorRegistry.java similarity index 100% rename from cloud-annotations/src/main/java/cloud/commandframework/annotations/injection/ParameterInjectorRegistry.java rename to cloud-core/src/main/java/cloud/commandframework/annotations/injection/ParameterInjectorRegistry.java diff --git a/cloud-core/src/main/java/cloud/commandframework/annotations/injection/package-info.java b/cloud-core/src/main/java/cloud/commandframework/annotations/injection/package-info.java new file mode 100644 index 00000000..e7ff3f27 --- /dev/null +++ b/cloud-core/src/main/java/cloud/commandframework/annotations/injection/package-info.java @@ -0,0 +1,30 @@ +// +// 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 + * + * This package will be moved in a future release + */ +package cloud.commandframework.annotations.injection; diff --git a/cloud-core/src/main/java/cloud/commandframework/context/CommandContext.java b/cloud-core/src/main/java/cloud/commandframework/context/CommandContext.java index b6c0afb2..14adf180 100644 --- a/cloud-core/src/main/java/cloud/commandframework/context/CommandContext.java +++ b/cloud-core/src/main/java/cloud/commandframework/context/CommandContext.java @@ -23,6 +23,9 @@ // package cloud.commandframework.context; +import cloud.commandframework.CommandManager; +import cloud.commandframework.annotations.AnnotationAccessor; +import cloud.commandframework.annotations.injection.ParameterInjector; import cloud.commandframework.arguments.CommandArgument; import cloud.commandframework.arguments.flags.FlagContext; import cloud.commandframework.captions.Caption; @@ -55,6 +58,7 @@ public final class CommandContext { private final C commandSender; private final boolean suggestions; private final CaptionRegistry captionRegistry; + private final CommandManager commandManager; private CommandArgument currentArgument = null; @@ -63,18 +67,33 @@ public final class CommandContext { * * @param commandSender Sender of the command * @param captionRegistry Caption registry + * @deprecated Provide a command manager instead of a caption registry */ + @Deprecated public CommandContext(final @NonNull C commandSender, final @NonNull CaptionRegistry captionRegistry) { this(false, commandSender, captionRegistry); } + /** + * Create a new command context instance + * + * @param commandSender Sender of the command + * @param commandManager Command manager + * @since 1.3.0 + */ + public CommandContext(final @NonNull C commandSender, final @NonNull CommandManager commandManager) { + this(false, commandSender, commandManager); + } + /** * Create a new command context instance * * @param suggestions Whether or not the context is created for command suggestions * @param commandSender Sender of the command * @param captionRegistry Caption registry + * @deprecated Provide a command manager instead of a caption registry */ + @Deprecated public CommandContext( final boolean suggestions, final @NonNull C commandSender, @@ -83,6 +102,26 @@ public final class CommandContext { this.commandSender = commandSender; this.suggestions = suggestions; this.captionRegistry = captionRegistry; + this.commandManager = null; + } + + /** + * Create a new command context instance + * + * @param suggestions Whether or not the context is created for command suggestions + * @param commandSender Sender of the command + * @param commandManager Command manager + * @since 1.3.0 + */ + public CommandContext( + final boolean suggestions, + final @NonNull C commandSender, + final @NonNull CommandManager commandManager + ) { + this.commandSender = commandSender; + this.suggestions = suggestions; + this.commandManager = commandManager; + this.captionRegistry = commandManager.getCaptionRegistry(); } /** @@ -328,6 +367,31 @@ public final class CommandContext { this.currentArgument = argument; } + /** + * Attempt to retrieve a value that has been registered to the associated command manager's + * {@link cloud.commandframework.annotations.injection.ParameterInjectorRegistry} + * + * @param clazz Class of type to inject + * @param Type to inject + * @return Optional that may contain the created value + * @since 1.3.0 + */ + @SuppressWarnings("unchecked") + public <@NonNull T> @NonNull Optional inject(final @NonNull Class clazz) { + if (this.commandManager == null) { + throw new UnsupportedOperationException( + "Cannot retrieve injectable values from a command context that is not associated with a command manager" + ); + } + for (final ParameterInjector injector : this.commandManager.parameterInjectorRegistry().injectors(clazz)) { + final Object value = injector.create(this, AnnotationAccessor.empty()); + if (value != null) { + return Optional.of((T) value); + } + } + return Optional.empty(); + } + /** * Used to track performance metrics related to command parsing. This is attached diff --git a/cloud-core/src/main/java/cloud/commandframework/context/CommandContextFactory.java b/cloud-core/src/main/java/cloud/commandframework/context/CommandContextFactory.java index b2e2dfdc..af1f76d3 100644 --- a/cloud-core/src/main/java/cloud/commandframework/context/CommandContextFactory.java +++ b/cloud-core/src/main/java/cloud/commandframework/context/CommandContextFactory.java @@ -23,6 +23,7 @@ // package cloud.commandframework.context; +import cloud.commandframework.CommandManager; import cloud.commandframework.captions.CaptionRegistry; import org.checkerframework.checker.nullness.qual.NonNull; @@ -40,11 +41,28 @@ public interface CommandContextFactory { * @param sender Command sender * @param captionRegistry Caption registry * @return Command context + * @deprecated Provide a command manager instead of a caption registry */ + @Deprecated @NonNull CommandContext create( boolean suggestions, @NonNull C sender, @NonNull CaptionRegistry captionRegistry ); + /** + * Create a new command context + * + * @param suggestions Whether or not the sender is requesting suggestions + * @param sender Command sender + * @param commandManager Command manager + * @return Command context + * @since 1.3.0 + */ + @NonNull CommandContext create( + boolean suggestions, + @NonNull C sender, + @NonNull CommandManager commandManager + ); + } diff --git a/cloud-core/src/main/java/cloud/commandframework/context/StandardCommandContextFactory.java b/cloud-core/src/main/java/cloud/commandframework/context/StandardCommandContextFactory.java index d030aaa0..9658a109 100644 --- a/cloud-core/src/main/java/cloud/commandframework/context/StandardCommandContextFactory.java +++ b/cloud-core/src/main/java/cloud/commandframework/context/StandardCommandContextFactory.java @@ -23,21 +23,15 @@ // package cloud.commandframework.context; +import cloud.commandframework.CommandManager; import cloud.commandframework.captions.CaptionRegistry; import org.checkerframework.checker.nullness.qual.NonNull; +@SuppressWarnings("deprecation") public final class StandardCommandContextFactory implements CommandContextFactory { - /** - * Construct a new command context - * - * @param suggestions Whether or not the sender is requesting suggestions - * @param sender Command sender - * @param captionRegistry Caption registry - * @return Created context - */ @Override - public CommandContext create( + public @NonNull CommandContext create( final boolean suggestions, final @NonNull C sender, final @NonNull CaptionRegistry captionRegistry @@ -49,4 +43,17 @@ public final class StandardCommandContextFactory implements CommandContextFac ); } + @Override + public @NonNull CommandContext create( + final boolean suggestions, + final @NonNull C sender, + final @NonNull CommandManager commandManager + ) { + return new CommandContext( + suggestions, + sender, + commandManager + ); + } + } diff --git a/cloud-core/src/test/java/cloud/commandframework/CommandTreeTest.java b/cloud-core/src/test/java/cloud/commandframework/CommandTreeTest.java index 82fd0959..4513be33 100644 --- a/cloud-core/src/test/java/cloud/commandframework/CommandTreeTest.java +++ b/cloud-core/src/test/java/cloud/commandframework/CommandTreeTest.java @@ -161,7 +161,7 @@ class CommandTreeTest { .parse( new CommandContext<>( new TestCommandSender(), - manager.getCaptionRegistry() + manager ), new LinkedList<>( Arrays.asList( @@ -174,7 +174,7 @@ class CommandTreeTest { .parse( new CommandContext<>( new TestCommandSender(), - manager.getCaptionRegistry() + manager ), new LinkedList<>( Arrays.asList("test", "two")) @@ -182,21 +182,21 @@ class CommandTreeTest { .getSecond().getClass()); manager.getCommandTree() .parse( - new CommandContext<>(new TestCommandSender(), manager.getCaptionRegistry()), + new CommandContext<>(new TestCommandSender(), manager), new LinkedList<>(Arrays.asList("test", "opt")) ) .getFirst().getCommandExecutionHandler().execute(new CommandContext<>( new TestCommandSender(), - manager.getCaptionRegistry() + manager )); manager.getCommandTree() .parse( - new CommandContext<>(new TestCommandSender(), manager.getCaptionRegistry()), + new CommandContext<>(new TestCommandSender(), manager), new LinkedList<>(Arrays.asList("test", "opt", "12")) ) .getFirst().getCommandExecutionHandler().execute(new CommandContext<>( new TestCommandSender(), - manager.getCaptionRegistry() + manager )); } @@ -206,7 +206,7 @@ class CommandTreeTest { .parse( new CommandContext<>( new TestCommandSender(), - manager.getCaptionRegistry() + manager ), new LinkedList<>(Arrays.asList( "other", @@ -216,7 +216,7 @@ class CommandTreeTest { ) .getFirst().getCommandExecutionHandler().execute(new CommandContext<>( new TestCommandSender(), - manager.getCaptionRegistry() + manager )); } @@ -224,7 +224,7 @@ class CommandTreeTest { void getSuggestions() { Assertions.assertFalse( manager.getCommandTree().getSuggestions( - new CommandContext<>(new TestCommandSender(), manager.getCaptionRegistry()), + new CommandContext<>(new TestCommandSender(), manager), new LinkedList<>(Collections.singletonList("test ")) ).isEmpty()); } diff --git a/cloud-minecraft/cloud-brigadier/src/main/java/cloud/commandframework/brigadier/CloudBrigadierManager.java b/cloud-minecraft/cloud-brigadier/src/main/java/cloud/commandframework/brigadier/CloudBrigadierManager.java index 6997eb92..2a2b406e 100644 --- a/cloud-minecraft/cloud-brigadier/src/main/java/cloud/commandframework/brigadier/CloudBrigadierManager.java +++ b/cloud-minecraft/cloud-brigadier/src/main/java/cloud/commandframework/brigadier/CloudBrigadierManager.java @@ -546,7 +546,7 @@ public final class CloudBrigadierManager { commandContext = new CommandContext<>( true, cloudSender, - this.commandManager.getCaptionRegistry() + this.commandManager ); } diff --git a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/CloudCommodoreManager.java b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/CloudCommodoreManager.java index 559404e6..0153f0fb 100644 --- a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/CloudCommodoreManager.java +++ b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/CloudCommodoreManager.java @@ -56,7 +56,7 @@ class CloudCommodoreManager extends BukkitPluginRegistrationHandler { this.brigadierManager = new CloudBrigadierManager<>(commandManager, () -> new CommandContext<>( commandManager.getCommandSenderMapper().apply(Bukkit.getConsoleSender()), - commandManager.getCaptionRegistry() + commandManager )); this.brigadierManager.brigadierSenderMapper( sender -> this.commandManager.getCommandSenderMapper().apply( diff --git a/cloud-minecraft/cloud-paper/src/main/java/cloud/commandframework/paper/PaperBrigadierListener.java b/cloud-minecraft/cloud-paper/src/main/java/cloud/commandframework/paper/PaperBrigadierListener.java index a81ab343..1a6038b3 100644 --- a/cloud-minecraft/cloud-paper/src/main/java/cloud/commandframework/paper/PaperBrigadierListener.java +++ b/cloud-minecraft/cloud-paper/src/main/java/cloud/commandframework/paper/PaperBrigadierListener.java @@ -51,7 +51,7 @@ class PaperBrigadierListener implements Listener { () -> new CommandContext<>( this.paperCommandManager.getCommandSenderMapper() .apply(Bukkit.getConsoleSender()), - this.paperCommandManager.getCaptionRegistry() + this.paperCommandManager ) ); this.brigadierManager.brigadierSenderMapper( diff --git a/cloud-minecraft/cloud-velocity/src/main/java/cloud/commandframework/velocity/VelocityPluginRegistrationHandler.java b/cloud-minecraft/cloud-velocity/src/main/java/cloud/commandframework/velocity/VelocityPluginRegistrationHandler.java index d0c10278..3ee2e5f1 100644 --- a/cloud-minecraft/cloud-velocity/src/main/java/cloud/commandframework/velocity/VelocityPluginRegistrationHandler.java +++ b/cloud-minecraft/cloud-velocity/src/main/java/cloud/commandframework/velocity/VelocityPluginRegistrationHandler.java @@ -49,7 +49,7 @@ final class VelocityPluginRegistrationHandler implements CommandRegistrationH velocityCommandManager.getCommandSenderMapper() .apply(velocityCommandManager.getProxyServer() .getConsoleCommandSource()), - velocityCommandManager.getCaptionRegistry() + velocityCommandManager ) ); this.brigadierManager.brigadierSenderMapper(