✨ Add flag support to the annotation system.
This commit is contained in:
parent
782f3023fc
commit
c67619e5da
9 changed files with 214 additions and 2 deletions
|
|
@ -27,6 +27,7 @@ import cloud.commandframework.Command;
|
||||||
import cloud.commandframework.CommandManager;
|
import cloud.commandframework.CommandManager;
|
||||||
import cloud.commandframework.Description;
|
import cloud.commandframework.Description;
|
||||||
import cloud.commandframework.arguments.CommandArgument;
|
import cloud.commandframework.arguments.CommandArgument;
|
||||||
|
import cloud.commandframework.arguments.flags.CommandFlag;
|
||||||
import cloud.commandframework.arguments.parser.ArgumentParser;
|
import cloud.commandframework.arguments.parser.ArgumentParser;
|
||||||
import cloud.commandframework.arguments.parser.ParserParameter;
|
import cloud.commandframework.arguments.parser.ParserParameter;
|
||||||
import cloud.commandframework.arguments.parser.StandardParameters;
|
import cloud.commandframework.arguments.parser.StandardParameters;
|
||||||
|
|
@ -64,6 +65,7 @@ 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 Class<C> commandSenderClass;
|
private final Class<C> commandSenderClass;
|
||||||
private final MetaFactory metaFactory;
|
private final MetaFactory metaFactory;
|
||||||
|
private final FlagExtractor flagExtractor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a new annotation parser
|
* Construct a new annotation parser
|
||||||
|
|
@ -82,6 +84,7 @@ public final class AnnotationParser<C> {
|
||||||
this.manager = manager;
|
this.manager = manager;
|
||||||
this.metaFactory = new MetaFactory(this, metaMapper);
|
this.metaFactory = new MetaFactory(this, metaMapper);
|
||||||
this.annotationMappers = new HashMap<>();
|
this.annotationMappers = new HashMap<>();
|
||||||
|
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()));
|
||||||
}
|
}
|
||||||
|
|
@ -156,6 +159,7 @@ public final class AnnotationParser<C> {
|
||||||
tokens.get(commandToken).getMinor(),
|
tokens.get(commandToken).getMinor(),
|
||||||
metaBuilder.build());
|
metaBuilder.build());
|
||||||
final Collection<ArgumentParameterPair> arguments = this.argumentExtractor.apply(method);
|
final Collection<ArgumentParameterPair> arguments = this.argumentExtractor.apply(method);
|
||||||
|
final Collection<CommandFlag<?>> flags = this.flagExtractor.apply(method);
|
||||||
final Map<String, CommandArgument<C, ?>> commandArguments = new HashMap<>();
|
final Map<String, CommandArgument<C, ?>> commandArguments = new HashMap<>();
|
||||||
final Map<CommandArgument<C, ?>, String> argumentDescriptions = new HashMap<>();
|
final Map<CommandArgument<C, ?>, String> argumentDescriptions = new HashMap<>();
|
||||||
/* Go through all annotated parameters and build up the argument tree */
|
/* Go through all annotated parameters and build up the argument tree */
|
||||||
|
|
@ -218,6 +222,10 @@ public final class AnnotationParser<C> {
|
||||||
if (method.isAnnotationPresent(Hidden.class)) {
|
if (method.isAnnotationPresent(Hidden.class)) {
|
||||||
builder = builder.hidden();
|
builder = builder.hidden();
|
||||||
}
|
}
|
||||||
|
/* Apply flags */
|
||||||
|
for (final CommandFlag<?> flag : flags) {
|
||||||
|
builder = builder.flag(flag);
|
||||||
|
}
|
||||||
/* 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);
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ import java.util.function.Function;
|
||||||
* Utility that extract {@link Argument arguments} from
|
* Utility that extract {@link Argument arguments} from
|
||||||
* {@link java.lang.reflect.Method method} {@link java.lang.reflect.Parameter parameters}
|
* {@link java.lang.reflect.Method method} {@link java.lang.reflect.Parameter parameters}
|
||||||
*/
|
*/
|
||||||
class ArgumentExtractor implements Function<@NonNull Method, Collection<@NonNull ArgumentParameterPair>> {
|
class ArgumentExtractor implements Function<@NonNull Method, @NonNull Collection<@NonNull ArgumentParameterPair>> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NonNull Collection<@NonNull ArgumentParameterPair> apply(@NonNull final Method method) {
|
public @NonNull Collection<@NonNull ArgumentParameterPair> apply(@NonNull final Method method) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,69 @@
|
||||||
|
//
|
||||||
|
// 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 java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates that the parameter should be treated like a {@link cloud.commandframework.arguments.flags.CommandFlag}.
|
||||||
|
* If the parameter is a {@code boolean} then a presence flag will be created, else a value flag will be created
|
||||||
|
* and the parser will be resolved the same way as it would for a {@link Argument}
|
||||||
|
*/
|
||||||
|
@Target(ElementType.PARAMETER)
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface Flag {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The flag name
|
||||||
|
*
|
||||||
|
* @return Flag name
|
||||||
|
*/
|
||||||
|
String value();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flag aliases
|
||||||
|
*
|
||||||
|
* @return Aliases
|
||||||
|
*/
|
||||||
|
String[] aliases() default "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Name of the parser. Leave empty to use
|
||||||
|
* the default parser for the parameter type
|
||||||
|
*
|
||||||
|
* @return Parser name
|
||||||
|
*/
|
||||||
|
String parserName() default "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The argument description
|
||||||
|
*
|
||||||
|
* @return Argument description
|
||||||
|
*/
|
||||||
|
String description() default "";
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,91 @@
|
||||||
|
//
|
||||||
|
// 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 cloud.commandframework.CommandManager;
|
||||||
|
import cloud.commandframework.Description;
|
||||||
|
import cloud.commandframework.arguments.CommandArgument;
|
||||||
|
import cloud.commandframework.arguments.flags.CommandFlag;
|
||||||
|
import cloud.commandframework.arguments.parser.ArgumentParser;
|
||||||
|
import cloud.commandframework.arguments.parser.ParserParameters;
|
||||||
|
import cloud.commandframework.arguments.parser.ParserRegistry;
|
||||||
|
import io.leangen.geantyref.TypeToken;
|
||||||
|
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.lang.reflect.Parameter;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
final class FlagExtractor implements Function<@NonNull Method, Collection<@NonNull CommandFlag<?>>> {
|
||||||
|
|
||||||
|
private final CommandManager<?> commandManager;
|
||||||
|
|
||||||
|
FlagExtractor(@NonNull final CommandManager<?> commandManager) {
|
||||||
|
this.commandManager = commandManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NonNull Collection<@NonNull CommandFlag<?>> apply(@NonNull final Method method) {
|
||||||
|
final Collection<CommandFlag<?>> flags = new LinkedList<>();
|
||||||
|
for (final Parameter parameter : method.getParameters()) {
|
||||||
|
if (!parameter.isAnnotationPresent(Flag.class)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final Flag flag = parameter.getAnnotation(Flag.class);
|
||||||
|
final CommandFlag.Builder<Void> builder = this.commandManager.flagBuilder(flag.value())
|
||||||
|
.withDescription(Description.of(flag.description())).withAliases(flag.aliases());
|
||||||
|
if (parameter.getType().equals(boolean.class)) {
|
||||||
|
flags.add(builder.build());
|
||||||
|
} else {
|
||||||
|
final ParserRegistry<?> registry = this.commandManager.getParserRegistry();
|
||||||
|
final ArgumentParser<?, ?> parser;
|
||||||
|
if (flag.parserName().isEmpty()) {
|
||||||
|
parser = registry.createParser(TypeToken.get(parameter.getType()), ParserParameters.empty())
|
||||||
|
.orElse(null);
|
||||||
|
} else {
|
||||||
|
parser = registry.createParser(flag.parserName(), ParserParameters.empty())
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
if (parser == null) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
String.format("Cannot find parser for type '%s' for flag '%s' in method '%s'",
|
||||||
|
parameter.getType().getCanonicalName(), flag.value(), method.getName()));
|
||||||
|
}
|
||||||
|
@SuppressWarnings("ALL")
|
||||||
|
final CommandArgument.Builder argumentBuilder = CommandArgument.ofType(parameter.getType(), flag.value());
|
||||||
|
@SuppressWarnings("ALL")
|
||||||
|
final CommandArgument argument = argumentBuilder.asRequired()
|
||||||
|
.manager(this.commandManager)
|
||||||
|
.withParser(parser)
|
||||||
|
.build();
|
||||||
|
// noinspection unchecked
|
||||||
|
flags.add(builder.withArgument(argument).build());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -24,6 +24,7 @@
|
||||||
package cloud.commandframework.annotations;
|
package cloud.commandframework.annotations;
|
||||||
|
|
||||||
import cloud.commandframework.arguments.CommandArgument;
|
import cloud.commandframework.arguments.CommandArgument;
|
||||||
|
import cloud.commandframework.arguments.flags.FlagContext;
|
||||||
import cloud.commandframework.context.CommandContext;
|
import cloud.commandframework.context.CommandContext;
|
||||||
import cloud.commandframework.execution.CommandExecutionHandler;
|
import cloud.commandframework.execution.CommandExecutionHandler;
|
||||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||||
|
|
@ -55,6 +56,7 @@ class MethodCommandExecutionHandler<C> implements CommandExecutionHandler<C> {
|
||||||
@Override
|
@Override
|
||||||
public void execute(@NonNull final CommandContext<C> commandContext) {
|
public void execute(@NonNull final CommandContext<C> commandContext) {
|
||||||
final List<Object> arguments = new ArrayList<>(this.parameters.length);
|
final List<Object> arguments = new ArrayList<>(this.parameters.length);
|
||||||
|
final FlagContext flagContext = commandContext.flags();
|
||||||
|
|
||||||
/* Bind parameters to context */
|
/* Bind parameters to context */
|
||||||
for (final Parameter parameter : this.parameters) {
|
for (final Parameter parameter : this.parameters) {
|
||||||
|
|
@ -67,6 +69,13 @@ class MethodCommandExecutionHandler<C> implements CommandExecutionHandler<C> {
|
||||||
final Object optional = commandContext.getOptional(argument.value()).orElse(null);
|
final Object optional = commandContext.getOptional(argument.value()).orElse(null);
|
||||||
arguments.add(optional);
|
arguments.add(optional);
|
||||||
}
|
}
|
||||||
|
} else if (parameter.isAnnotationPresent(Flag.class)) {
|
||||||
|
final Flag flag = parameter.getAnnotation(Flag.class);
|
||||||
|
if (parameter.getType() == boolean.class) {
|
||||||
|
arguments.add(flagContext.isPresent(flag.value()));
|
||||||
|
} else {
|
||||||
|
arguments.add(flagContext.getValue(flag.value(), null));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (parameter.getType().isAssignableFrom(commandContext.getSender().getClass())) {
|
if (parameter.getType().isAssignableFrom(commandContext.getSender().getClass())) {
|
||||||
arguments.add(commandContext.getSender());
|
arguments.add(commandContext.getSender());
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,8 @@ class AnnotationParserTest {
|
||||||
manager.executeCommand(new TestCommandSender(), "proxycommand 10").join();
|
manager.executeCommand(new TestCommandSender(), "proxycommand 10").join();
|
||||||
Assertions.assertThrows(CompletionException.class, () ->
|
Assertions.assertThrows(CompletionException.class, () ->
|
||||||
manager.executeCommand(new TestCommandSender(), "test 101").join());
|
manager.executeCommand(new TestCommandSender(), "test 101").join());
|
||||||
|
manager.executeCommand(new TestCommandSender(), "flagcommand -p").join();
|
||||||
|
manager.executeCommand(new TestCommandSender(), "flagcommand --print --word peanut").join();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ProxiedBy("proxycommand")
|
@ProxiedBy("proxycommand")
|
||||||
|
|
@ -69,4 +71,13 @@ class AnnotationParserTest {
|
||||||
System.out.printf("Received int: %d and string '%s'\n", argument, string);
|
System.out.printf("Received int: %d and string '%s'\n", argument, string);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@CommandMethod("flagcommand")
|
||||||
|
public void testFlags(final TestCommandSender sender,
|
||||||
|
@Flag(value = "print", aliases = "p") boolean print,
|
||||||
|
@Flag(value = "word", aliases = "w") String word) {
|
||||||
|
if (print) {
|
||||||
|
System.out.println(word);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -680,6 +680,17 @@ public class Command<C> {
|
||||||
Collections.unmodifiableList(flags));
|
Collections.unmodifiableList(flags));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a new command flag
|
||||||
|
*
|
||||||
|
* @param builder Flag builder. {@link CommandFlag.Builder#build()} will be invoked.
|
||||||
|
* @param <T> Flag value type
|
||||||
|
* @return New builder instance that uses the provided flag
|
||||||
|
*/
|
||||||
|
public @NonNull <T> Builder<C> flag(final CommandFlag.@NonNull Builder<T> builder) {
|
||||||
|
return this.flag(builder.build());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build a command using the builder instance
|
* Build a command using the builder instance
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -89,6 +89,7 @@ public final class CommandFlag<T> {
|
||||||
/**
|
/**
|
||||||
* Get the flag description
|
* Get the flag description
|
||||||
* <p>
|
* <p>
|
||||||
|
*
|
||||||
* @return Flag description
|
* @return Flag description
|
||||||
*/
|
*/
|
||||||
public @NonNull Description getDescription() {
|
public @NonNull Description getDescription() {
|
||||||
|
|
@ -179,6 +180,17 @@ public final class CommandFlag<T> {
|
||||||
return new Builder<>(this.name, this.aliases, this.description, argument);
|
return new Builder<>(this.name, this.aliases, this.description, argument);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new builder instance using the given command argument
|
||||||
|
*
|
||||||
|
* @param builder Command argument builder. {@link CommandArgument.Builder#build()} will be invoked.
|
||||||
|
* @param <N> New argument type
|
||||||
|
* @return New builder instance
|
||||||
|
*/
|
||||||
|
public <N> Builder<N> withArgument(final CommandArgument.@NonNull Builder<?, N> builder) {
|
||||||
|
return this.withArgument(builder.build());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build a new command flag instance
|
* Build a new command flag instance
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@
|
||||||
package cloud.commandframework.arguments.flags;
|
package cloud.commandframework.arguments.flags;
|
||||||
|
|
||||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||||
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
@ -94,7 +95,7 @@ public final class FlagContext {
|
||||||
* @param <T> Value type
|
* @param <T> Value type
|
||||||
* @return Stored value, or the supplied default value
|
* @return Stored value, or the supplied default value
|
||||||
*/
|
*/
|
||||||
public <T> T getValue(@NonNull final String name, @NonNull final T defaultValue) {
|
public <T> @Nullable T getValue(@NonNull final String name, @Nullable final T defaultValue) {
|
||||||
final Object value = this.flagValues.get(name);
|
final Object value = this.flagValues.get(name);
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
return defaultValue;
|
return defaultValue;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue