Allow interception of command builders based on annotations in AnnotationParser

Fixes #179
This commit is contained in:
Alexander Söderberg 2020-12-18 18:59:59 +01:00 committed by Alexander Söderberg
parent c684c6607f
commit 9ed40a698a
3 changed files with 66 additions and 5 deletions

View file

@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added `@Suggestions` annotated methods - Added `@Suggestions` annotated methods
- Added `@Parser` annotated methods - Added `@Parser` annotated methods
- Type safe meta system - Type safe meta system
- Allow interception of command builders based on annotations in AnnotationParser
### Changed ### Changed
- Moved the parser injector registry into CommandManager and added injection to CommandContext - Moved the parser injector registry into CommandManager and added injection to CommandContext

View file

@ -83,6 +83,8 @@ public final class AnnotationParser<C> {
private final Map<Class<? extends Annotation>, Function<? extends Annotation, ParserParameters>> annotationMappers; private final Map<Class<? extends Annotation>, Function<? extends Annotation, ParserParameters>> annotationMappers;
private final Map<Class<? extends Annotation>, Function<? extends Annotation, BiFunction<@NonNull CommandContext<C>, private final Map<Class<? extends Annotation>, Function<? extends Annotation, BiFunction<@NonNull CommandContext<C>,
@NonNull Queue<@NonNull String>, @NonNull ArgumentParseResult<Boolean>>>> preprocessorMappers; @NonNull Queue<@NonNull String>, @NonNull ArgumentParseResult<Boolean>>>> preprocessorMappers;
private final Map<Class<? extends Annotation>, BiFunction<? extends Annotation, Command.Builder<C>, Command.Builder<C>>>
builderModifiers;
private final Class<C> commandSenderClass; private final Class<C> commandSenderClass;
private final MetaFactory metaFactory; private final MetaFactory metaFactory;
private final FlagExtractor flagExtractor; private final FlagExtractor flagExtractor;
@ -107,6 +109,7 @@ public final class AnnotationParser<C> {
this.metaFactory = new MetaFactory(this, metaMapper); this.metaFactory = new MetaFactory(this, metaMapper);
this.annotationMappers = new HashMap<>(); this.annotationMappers = new HashMap<>();
this.preprocessorMappers = new HashMap<>(); this.preprocessorMappers = new HashMap<>();
this.builderModifiers = new HashMap<>();
this.flagExtractor = new FlagExtractor(manager); this.flagExtractor = new FlagExtractor(manager);
this.registerAnnotationMapper(CommandDescription.class, d -> this.registerAnnotationMapper(CommandDescription.class, d ->
ParserParameters.single(StandardParameters.DESCRIPTION, d.value())); ParserParameters.single(StandardParameters.DESCRIPTION, d.value()));
@ -177,6 +180,25 @@ public final class AnnotationParser<C> {
return getMethodOrClassAnnotation(method, clazz) != null; return getMethodOrClassAnnotation(method, clazz) != null;
} }
/**
* Register a builder modifier for a specific annotation. The builder modifiers are
* allowed to act on a {@link Command.Builder} after all arguments have been added
* to the builder. This allows for modifications of the builder instance before
* the command is registered to the command manager.
*
* @param annotation Annotation (class) that the builder modifier reacts to
* @param builderModifier Modifier that acts on the given annotation and the incoming builder. Command builders
* are immutable, so the modifier should return the instance of the command builder that is
* returned as a result of any operation on the builder
* @param <A> Annotation type
*/
public <A extends Annotation> void registerBuilderModifier(
final @NonNull Class<A> annotation,
final @NonNull BiFunction<A, Command.Builder<C>, Command.Builder<C>> builderModifier
) {
this.builderModifiers.put(annotation, builderModifier);
}
/** /**
* Register an annotation mapper * Register an annotation mapper
* *
@ -457,6 +479,14 @@ public final class AnnotationParser<C> {
for (final CommandFlag<?> flag : flags) { for (final CommandFlag<?> flag : flags) {
builder = builder.flag(flag); builder = builder.flag(flag);
} }
for (final Annotation annotation : method.getDeclaredAnnotations()) {
@SuppressWarnings("ALL")
final BiFunction builderModifier = this.builderModifiers.get(annotation.annotationType());
if (builderModifier == null) {
continue;
}
builder = (Command.Builder<C>) builderModifier.apply(annotation, builder);
}
/* Construct and register the command */ /* Construct and register the command */
final Command<C> builtCommand = builder.build(); final Command<C> builtCommand = builder.build();
commands.add(builtCommand); commands.add(builtCommand);

View file

@ -30,6 +30,7 @@ import cloud.commandframework.annotations.specifier.Range;
import cloud.commandframework.annotations.suggestions.Suggestions; import cloud.commandframework.annotations.suggestions.Suggestions;
import cloud.commandframework.arguments.parser.ArgumentParser; import cloud.commandframework.arguments.parser.ArgumentParser;
import cloud.commandframework.arguments.parser.ParserParameters; import cloud.commandframework.arguments.parser.ParserParameters;
import cloud.commandframework.arguments.standard.IntegerArgument;
import cloud.commandframework.arguments.standard.StringArgument; import cloud.commandframework.arguments.standard.StringArgument;
import cloud.commandframework.context.CommandContext; import cloud.commandframework.context.CommandContext;
import cloud.commandframework.meta.SimpleCommandMeta; import cloud.commandframework.meta.SimpleCommandMeta;
@ -78,7 +79,11 @@ class AnnotationParserTest {
InjectableValue.class, InjectableValue.class,
(context, annotations) -> new InjectableValue("Hello World!") (context, annotations) -> new InjectableValue("Hello World!")
); );
/* Register a builder modifier */
annotationParser.registerBuilderModifier(
IntegerArgumentInjector.class,
(injector, builder) -> builder.argument(IntegerArgument.of(injector.value()))
);
/* Parse the class. Required for both testMethodConstruction() and testNamedSuggestionProvider() */ /* Parse the class. Required for both testMethodConstruction() and testNamedSuggestionProvider() */
commands = annotationParser.parse(this); commands = annotationParser.parse(this);
} }
@ -135,7 +140,7 @@ class AnnotationParserTest {
@Test @Test
void testParameterInjection() { void testParameterInjection() {
manager.executeCommand(new TestCommandSender(), "inject").join(); manager.executeCommand(new TestCommandSender(), "injected 10").join();
} }
@Test @Test
@ -167,6 +172,11 @@ class AnnotationParserTest {
).contains("Stella")); ).contains("Stella"));
} }
@Test
void testInjectedCommand() {
manager.executeCommand(new TestCommandSender(), "injected 10").join();
}
@Suggestions("cows") @Suggestions("cows")
public List<String> cowSuggestions(final CommandContext<TestCommandSender> context, final String input) { public List<String> cowSuggestions(final CommandContext<TestCommandSender> context, final String input) {
return Arrays.asList("Stella", "Bella", "Agda"); return Arrays.asList("Stella", "Bella", "Agda");
@ -177,6 +187,12 @@ class AnnotationParserTest {
return new CustomType("yay"); return new CustomType("yay");
} }
@IntegerArgumentInjector
@CommandMethod("injected")
public void injectedCommand(final CommandContext<TestCommandSender> context) {
System.out.printf("Got an integer: %d\n", context.<Integer>get("number"));
}
@ProxiedBy("proxycommand") @ProxiedBy("proxycommand")
@CommandMethod("test|t literal <int> [string]") @CommandMethod("test|t literal <int> [string]")
public void testCommand( public void testCommand(
@ -278,4 +294,18 @@ class AnnotationParserTest {
} }
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
private @interface IntegerArgumentInjector {
/**
* The name of the integer argument to insert
*
* @return Integer argument name
*/
String value() default "number";
}
} }