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]
### Changed
- Moved the parser injector registry into CommandManager and added injection to CommandContext
## [1.2.0] - 2020-12-07
### Added

View file

@ -73,7 +73,6 @@ public final class AnnotationParser<C> {
private final SyntaxParser syntaxParser = new SyntaxParser();
private final ArgumentExtractor argumentExtractor = new ArgumentExtractor();
private final ParameterInjectorRegistry<C> parameterInjectorRegistry = new ParameterInjectorRegistry<>();
private final CommandManager<C> manager;
private final Map<Class<? extends Annotation>, Function<? extends Annotation, ParserParameters>> annotationMappers;
@ -110,10 +109,6 @@ public final class AnnotationParser<C> {
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<C> {
* @since 1.2.0
*/
public @NonNull ParameterInjectorRegistry<C> getParameterInjectorRegistry() {
return this.parameterInjectorRegistry;
return this.manager.parameterInjectorRegistry();
}
/**
@ -344,8 +339,12 @@ public final class AnnotationParser<C> {
}
try {
/* Construct the handler */
final CommandExecutionHandler<C> commandExecutionHandler
= new MethodCommandExecutionHandler<>(instance, commandArguments, method, this.parameterInjectorRegistry);
final CommandExecutionHandler<C> 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);

View file

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

View file

@ -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<C> {
private final ServicePipeline servicePipeline = ServicePipeline.builder().build();
private final ParserRegistry<C> parserRegistry = new StandardParserRegistry<>();
private final Collection<Command<C>> commands = new LinkedList<>();
private final ParameterInjectorRegistry<C> parameterInjectorRegistry = new ParameterInjectorRegistry<>();
private final CommandExecutionCoordinator<C> commandExecutionCoordinator;
private final CommandTree<C> commandTree;
private final CommandSuggestionEngine<C> commandSuggestionEngine;
@ -128,6 +130,11 @@ public abstract class CommandManager<C> {
}, new AcceptingCommandPostprocessor<>());
/* Create the caption registry */
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(
false,
commandSender,
this.captionRegistry
this
);
final LinkedList<String> inputQueue = new CommandInputTokenizer(input).tokenize();
try {
@ -191,7 +198,7 @@ public abstract class CommandManager<C> {
final CommandContext<C> context = this.commandContextFactory.create(
true,
commandSender,
this.captionRegistry
this
);
return this.commandSuggestionEngine.getSuggestions(context, input);
}
@ -670,6 +677,17 @@ public abstract class CommandManager<C> {
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
*

View file

@ -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 <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;
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<C> {
private final C commandSender;
private final boolean suggestions;
private final CaptionRegistry<C> captionRegistry;
private final CommandManager<C> commandManager;
private CommandArgument<C, ?> currentArgument = null;
@ -63,18 +67,33 @@ public final class CommandContext<C> {
*
* @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<C> 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
*
* @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<C> {
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<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;
}
/**
* 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

View file

@ -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<C> {
* @param sender Command sender
* @param captionRegistry Caption registry
* @return Command context
* @deprecated Provide a command manager instead of a caption registry
*/
@Deprecated
@NonNull CommandContext<C> create(
boolean suggestions,
@NonNull C sender,
@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;
import cloud.commandframework.CommandManager;
import cloud.commandframework.captions.CaptionRegistry;
import org.checkerframework.checker.nullness.qual.NonNull;
@SuppressWarnings("deprecation")
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
public CommandContext<C> create(
public @NonNull CommandContext<C> create(
final boolean suggestions,
final @NonNull C sender,
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(
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());
}

View file

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

View file

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

View file

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

View file

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