Improve injection

This commit is contained in:
Alexander Söderberg 2020-12-14 22:52:27 +01:00 committed by Alexander Söderberg
parent e241420ee9
commit 2f077e03f3
15 changed files with 202 additions and 33 deletions

View file

@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
### Changed
- Moved the parser injector registry into CommandManager and added injection to CommandContext
## [1.2.0] - 2020-12-07 ## [1.2.0] - 2020-12-07
### Added ### Added

View file

@ -73,7 +73,6 @@ public final class AnnotationParser<C> {
private final SyntaxParser syntaxParser = new SyntaxParser(); private final SyntaxParser syntaxParser = new SyntaxParser();
private final ArgumentExtractor argumentExtractor = new ArgumentExtractor(); private final ArgumentExtractor argumentExtractor = new ArgumentExtractor();
private final ParameterInjectorRegistry<C> parameterInjectorRegistry = new ParameterInjectorRegistry<>();
private final CommandManager<C> manager; private final CommandManager<C> manager;
private final Map<Class<? extends Annotation>, Function<? extends Annotation, ParserParameters>> annotationMappers; private final Map<Class<? extends Annotation>, Function<? extends Annotation, ParserParameters>> annotationMappers;
@ -110,10 +109,6 @@ public final class AnnotationParser<C> {
annotation.value(), annotation.value(),
Caption.of(annotation.failureCaption()) Caption.of(annotation.failureCaption())
)); ));
this.getParameterInjectorRegistry().registerInjector(
CommandContext.class,
(context, annotations) -> context
);
this.getParameterInjectorRegistry().registerInjector( this.getParameterInjectorRegistry().registerInjector(
String[].class, String[].class,
(context, annotations) -> annotations.annotation(RawArgs.class) == null (context, annotations) -> annotations.annotation(RawArgs.class) == null
@ -215,7 +210,7 @@ public final class AnnotationParser<C> {
* @since 1.2.0 * @since 1.2.0
*/ */
public @NonNull ParameterInjectorRegistry<C> getParameterInjectorRegistry() { public @NonNull ParameterInjectorRegistry<C> getParameterInjectorRegistry() {
return this.parameterInjectorRegistry; return this.manager.parameterInjectorRegistry();
} }
/** /**
@ -344,8 +339,12 @@ public final class AnnotationParser<C> {
} }
try { try {
/* Construct the handler */ /* Construct the handler */
final CommandExecutionHandler<C> commandExecutionHandler final CommandExecutionHandler<C> commandExecutionHandler = new MethodCommandExecutionHandler<>(
= new MethodCommandExecutionHandler<>(instance, commandArguments, method, this.parameterInjectorRegistry); instance,
commandArguments,
method,
this.getParameterInjectorRegistry()
);
builder = builder.handler(commandExecutionHandler); builder = builder.handler(commandExecutionHandler);
} catch (final Exception e) { } catch (final Exception e) {
throw new RuntimeException("Failed to construct command execution handler", e); throw new RuntimeException("Failed to construct command execution handler", e);

View file

@ -34,7 +34,6 @@ import java.lang.annotation.Target;
* <p> * <p>
* This should only be used on {@code String[]} * This should only be used on {@code String[]}
* *
*
* @since 1.2.0 * @since 1.2.0
*/ */
@Target(ElementType.METHOD) @Target(ElementType.METHOD)

View file

@ -23,6 +23,7 @@
// //
package cloud.commandframework; package cloud.commandframework;
import cloud.commandframework.annotations.injection.ParameterInjectorRegistry;
import cloud.commandframework.arguments.CommandArgument; import cloud.commandframework.arguments.CommandArgument;
import cloud.commandframework.arguments.CommandSuggestionEngine; import cloud.commandframework.arguments.CommandSuggestionEngine;
import cloud.commandframework.arguments.CommandSyntaxFormatter; import cloud.commandframework.arguments.CommandSyntaxFormatter;
@ -88,6 +89,7 @@ public abstract class CommandManager<C> {
private final ServicePipeline servicePipeline = ServicePipeline.builder().build(); private final ServicePipeline servicePipeline = ServicePipeline.builder().build();
private final ParserRegistry<C> parserRegistry = new StandardParserRegistry<>(); private final ParserRegistry<C> parserRegistry = new StandardParserRegistry<>();
private final Collection<Command<C>> commands = new LinkedList<>(); private final Collection<Command<C>> commands = new LinkedList<>();
private final ParameterInjectorRegistry<C> parameterInjectorRegistry = new ParameterInjectorRegistry<>();
private final CommandExecutionCoordinator<C> commandExecutionCoordinator; private final CommandExecutionCoordinator<C> commandExecutionCoordinator;
private final CommandTree<C> commandTree; private final CommandTree<C> commandTree;
private final CommandSuggestionEngine<C> commandSuggestionEngine; private final CommandSuggestionEngine<C> commandSuggestionEngine;
@ -128,6 +130,11 @@ public abstract class CommandManager<C> {
}, new AcceptingCommandPostprocessor<>()); }, new AcceptingCommandPostprocessor<>());
/* Create the caption registry */ /* Create the caption registry */
this.captionRegistry = new SimpleCaptionRegistryFactory<C>().create(); this.captionRegistry = new SimpleCaptionRegistryFactory<C>().create();
/* Register default injectors */
this.parameterInjectorRegistry().registerInjector(
CommandContext.class,
(context, annotationAccessor) -> context
);
} }
/** /**
@ -158,7 +165,7 @@ public abstract class CommandManager<C> {
final CommandContext<C> context = this.commandContextFactory.create( final CommandContext<C> context = this.commandContextFactory.create(
false, false,
commandSender, commandSender,
this.captionRegistry this
); );
final LinkedList<String> inputQueue = new CommandInputTokenizer(input).tokenize(); final LinkedList<String> inputQueue = new CommandInputTokenizer(input).tokenize();
try { try {
@ -191,7 +198,7 @@ public abstract class CommandManager<C> {
final CommandContext<C> context = this.commandContextFactory.create( final CommandContext<C> context = this.commandContextFactory.create(
true, true,
commandSender, commandSender,
this.captionRegistry this
); );
return this.commandSuggestionEngine.getSuggestions(context, input); return this.commandSuggestionEngine.getSuggestions(context, input);
} }
@ -670,6 +677,17 @@ public abstract class CommandManager<C> {
return this.parserRegistry; return this.parserRegistry;
} }
/**
* Get the parameter injector registry instance
*
* @return Parameter injector registry
* @since 1.3.0
*/
public final @NonNull ParameterInjectorRegistry<C> parameterInjectorRegistry() {
return this.parameterInjectorRegistry;
}
/** /**
* Get the exception handler for an exception type, if one has been registered * Get the exception handler for an exception type, if one has been registered
* *

View file

@ -29,6 +29,7 @@ import org.checkerframework.checker.nullness.qual.Nullable;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement; import java.lang.reflect.AnnotatedElement;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
/** /**
* Managed access for {@link java.lang.annotation.Annotation} instances * Managed access for {@link java.lang.annotation.Annotation} instances
@ -37,6 +38,16 @@ import java.util.Collection;
*/ */
public interface AnnotationAccessor { 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 * 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 * 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(); @NonNull Collection<@NonNull Annotation> annotations();
/**
* Annotation accessor that cannot access any annotations
*
* @since 1.3.0
*/
final class NullAnnotationAccessor implements AnnotationAccessor {
@Override
public <A extends Annotation> @Nullable A annotation(@NonNull final Class<A> clazz) {
return null;
}
@Override
public @NonNull Collection<@NonNull Annotation> annotations() {
return Collections.emptyList();
}
}
} }

View file

@ -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;

View file

@ -23,6 +23,9 @@
// //
package cloud.commandframework.context; 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.CommandArgument;
import cloud.commandframework.arguments.flags.FlagContext; import cloud.commandframework.arguments.flags.FlagContext;
import cloud.commandframework.captions.Caption; import cloud.commandframework.captions.Caption;
@ -55,6 +58,7 @@ public final class CommandContext<C> {
private final C commandSender; private final C commandSender;
private final boolean suggestions; private final boolean suggestions;
private final CaptionRegistry<C> captionRegistry; private final CaptionRegistry<C> captionRegistry;
private final CommandManager<C> commandManager;
private CommandArgument<C, ?> currentArgument = null; private CommandArgument<C, ?> currentArgument = null;
@ -63,18 +67,33 @@ public final class CommandContext<C> {
* *
* @param commandSender Sender of the command * @param commandSender Sender of the command
* @param captionRegistry Caption registry * @param captionRegistry Caption registry
* @deprecated Provide a command manager instead of a caption registry
*/ */
@Deprecated
public CommandContext(final @NonNull C commandSender, final @NonNull CaptionRegistry<C> captionRegistry) { public CommandContext(final @NonNull C commandSender, final @NonNull CaptionRegistry<C> captionRegistry) {
this(false, commandSender, 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<C> commandManager) {
this(false, commandSender, commandManager);
}
/** /**
* Create a new command context instance * Create a new command context instance
* *
* @param suggestions Whether or not the context is created for command suggestions * @param suggestions Whether or not the context is created for command suggestions
* @param commandSender Sender of the command * @param commandSender Sender of the command
* @param captionRegistry Caption registry * @param captionRegistry Caption registry
* @deprecated Provide a command manager instead of a caption registry
*/ */
@Deprecated
public CommandContext( public CommandContext(
final boolean suggestions, final boolean suggestions,
final @NonNull C commandSender, final @NonNull C commandSender,
@ -83,6 +102,26 @@ public final class CommandContext<C> {
this.commandSender = commandSender; this.commandSender = commandSender;
this.suggestions = suggestions; this.suggestions = suggestions;
this.captionRegistry = captionRegistry; 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<C> commandManager
) {
this.commandSender = commandSender;
this.suggestions = suggestions;
this.commandManager = commandManager;
this.captionRegistry = commandManager.getCaptionRegistry();
} }
/** /**
@ -328,6 +367,31 @@ public final class CommandContext<C> {
this.currentArgument = argument; 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 <T> Type to inject
* @return Optional that may contain the created value
* @since 1.3.0
*/
@SuppressWarnings("unchecked")
public <@NonNull T> @NonNull Optional<T> inject(final @NonNull Class<T> 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<C, ?> 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 * Used to track performance metrics related to command parsing. This is attached

View file

@ -23,6 +23,7 @@
// //
package cloud.commandframework.context; package cloud.commandframework.context;
import cloud.commandframework.CommandManager;
import cloud.commandframework.captions.CaptionRegistry; import cloud.commandframework.captions.CaptionRegistry;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
@ -40,11 +41,28 @@ public interface CommandContextFactory<C> {
* @param sender Command sender * @param sender Command sender
* @param captionRegistry Caption registry * @param captionRegistry Caption registry
* @return Command context * @return Command context
* @deprecated Provide a command manager instead of a caption registry
*/ */
@Deprecated
@NonNull CommandContext<C> create( @NonNull CommandContext<C> create(
boolean suggestions, boolean suggestions,
@NonNull C sender, @NonNull C sender,
@NonNull CaptionRegistry<C> captionRegistry @NonNull CaptionRegistry<C> 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<C> create(
boolean suggestions,
@NonNull C sender,
@NonNull CommandManager<C> commandManager
);
} }

View file

@ -23,21 +23,15 @@
// //
package cloud.commandframework.context; package cloud.commandframework.context;
import cloud.commandframework.CommandManager;
import cloud.commandframework.captions.CaptionRegistry; import cloud.commandframework.captions.CaptionRegistry;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
@SuppressWarnings("deprecation")
public final class StandardCommandContextFactory<C> implements CommandContextFactory<C> { public final class StandardCommandContextFactory<C> implements CommandContextFactory<C> {
/**
* 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 @Override
public CommandContext<C> create( public @NonNull CommandContext<C> create(
final boolean suggestions, final boolean suggestions,
final @NonNull C sender, final @NonNull C sender,
final @NonNull CaptionRegistry<C> captionRegistry final @NonNull CaptionRegistry<C> captionRegistry
@ -49,4 +43,17 @@ public final class StandardCommandContextFactory<C> implements CommandContextFac
); );
} }
@Override
public @NonNull CommandContext<C> create(
final boolean suggestions,
final @NonNull C sender,
final @NonNull CommandManager<C> commandManager
) {
return new CommandContext<C>(
suggestions,
sender,
commandManager
);
}
} }

View file

@ -161,7 +161,7 @@ class CommandTreeTest {
.parse( .parse(
new CommandContext<>( new CommandContext<>(
new TestCommandSender(), new TestCommandSender(),
manager.getCaptionRegistry() manager
), ),
new LinkedList<>( new LinkedList<>(
Arrays.asList( Arrays.asList(
@ -174,7 +174,7 @@ class CommandTreeTest {
.parse( .parse(
new CommandContext<>( new CommandContext<>(
new TestCommandSender(), new TestCommandSender(),
manager.getCaptionRegistry() manager
), ),
new LinkedList<>( new LinkedList<>(
Arrays.asList("test", "two")) Arrays.asList("test", "two"))
@ -182,21 +182,21 @@ class CommandTreeTest {
.getSecond().getClass()); .getSecond().getClass());
manager.getCommandTree() manager.getCommandTree()
.parse( .parse(
new CommandContext<>(new TestCommandSender(), manager.getCaptionRegistry()), new CommandContext<>(new TestCommandSender(), manager),
new LinkedList<>(Arrays.asList("test", "opt")) new LinkedList<>(Arrays.asList("test", "opt"))
) )
.getFirst().getCommandExecutionHandler().execute(new CommandContext<>( .getFirst().getCommandExecutionHandler().execute(new CommandContext<>(
new TestCommandSender(), new TestCommandSender(),
manager.getCaptionRegistry() manager
)); ));
manager.getCommandTree() manager.getCommandTree()
.parse( .parse(
new CommandContext<>(new TestCommandSender(), manager.getCaptionRegistry()), new CommandContext<>(new TestCommandSender(), manager),
new LinkedList<>(Arrays.asList("test", "opt", "12")) new LinkedList<>(Arrays.asList("test", "opt", "12"))
) )
.getFirst().getCommandExecutionHandler().execute(new CommandContext<>( .getFirst().getCommandExecutionHandler().execute(new CommandContext<>(
new TestCommandSender(), new TestCommandSender(),
manager.getCaptionRegistry() manager
)); ));
} }
@ -206,7 +206,7 @@ class CommandTreeTest {
.parse( .parse(
new CommandContext<>( new CommandContext<>(
new TestCommandSender(), new TestCommandSender(),
manager.getCaptionRegistry() manager
), ),
new LinkedList<>(Arrays.asList( new LinkedList<>(Arrays.asList(
"other", "other",
@ -216,7 +216,7 @@ class CommandTreeTest {
) )
.getFirst().getCommandExecutionHandler().execute(new CommandContext<>( .getFirst().getCommandExecutionHandler().execute(new CommandContext<>(
new TestCommandSender(), new TestCommandSender(),
manager.getCaptionRegistry() manager
)); ));
} }
@ -224,7 +224,7 @@ class CommandTreeTest {
void getSuggestions() { void getSuggestions() {
Assertions.assertFalse( Assertions.assertFalse(
manager.getCommandTree().getSuggestions( manager.getCommandTree().getSuggestions(
new CommandContext<>(new TestCommandSender(), manager.getCaptionRegistry()), new CommandContext<>(new TestCommandSender(), manager),
new LinkedList<>(Collections.singletonList("test ")) new LinkedList<>(Collections.singletonList("test "))
).isEmpty()); ).isEmpty());
} }

View file

@ -546,7 +546,7 @@ public final class CloudBrigadierManager<C, S> {
commandContext = new CommandContext<>( commandContext = new CommandContext<>(
true, true,
cloudSender, cloudSender,
this.commandManager.getCaptionRegistry() this.commandManager
); );
} }

View file

@ -56,7 +56,7 @@ class CloudCommodoreManager<C> extends BukkitPluginRegistrationHandler<C> {
this.brigadierManager = new CloudBrigadierManager<>(commandManager, () -> this.brigadierManager = new CloudBrigadierManager<>(commandManager, () ->
new CommandContext<>( new CommandContext<>(
commandManager.getCommandSenderMapper().apply(Bukkit.getConsoleSender()), commandManager.getCommandSenderMapper().apply(Bukkit.getConsoleSender()),
commandManager.getCaptionRegistry() commandManager
)); ));
this.brigadierManager.brigadierSenderMapper( this.brigadierManager.brigadierSenderMapper(
sender -> this.commandManager.getCommandSenderMapper().apply( sender -> this.commandManager.getCommandSenderMapper().apply(

View file

@ -51,7 +51,7 @@ class PaperBrigadierListener<C> implements Listener {
() -> new CommandContext<>( () -> new CommandContext<>(
this.paperCommandManager.getCommandSenderMapper() this.paperCommandManager.getCommandSenderMapper()
.apply(Bukkit.getConsoleSender()), .apply(Bukkit.getConsoleSender()),
this.paperCommandManager.getCaptionRegistry() this.paperCommandManager
) )
); );
this.brigadierManager.brigadierSenderMapper( this.brigadierManager.brigadierSenderMapper(

View file

@ -49,7 +49,7 @@ final class VelocityPluginRegistrationHandler<C> implements CommandRegistrationH
velocityCommandManager.getCommandSenderMapper() velocityCommandManager.getCommandSenderMapper()
.apply(velocityCommandManager.getProxyServer() .apply(velocityCommandManager.getProxyServer()
.getConsoleCommandSource()), .getConsoleCommandSource()),
velocityCommandManager.getCaptionRegistry() velocityCommandManager
) )
); );
this.brigadierManager.brigadierSenderMapper( this.brigadierManager.brigadierSenderMapper(