✨ 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.Description;
|
||||
import cloud.commandframework.arguments.CommandArgument;
|
||||
import cloud.commandframework.arguments.flags.CommandFlag;
|
||||
import cloud.commandframework.arguments.parser.ArgumentParser;
|
||||
import cloud.commandframework.arguments.parser.ParserParameter;
|
||||
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 Class<C> commandSenderClass;
|
||||
private final MetaFactory metaFactory;
|
||||
private final FlagExtractor flagExtractor;
|
||||
|
||||
/**
|
||||
* Construct a new annotation parser
|
||||
|
|
@ -82,6 +84,7 @@ public final class AnnotationParser<C> {
|
|||
this.manager = manager;
|
||||
this.metaFactory = new MetaFactory(this, metaMapper);
|
||||
this.annotationMappers = new HashMap<>();
|
||||
this.flagExtractor = new FlagExtractor(manager);
|
||||
this.registerAnnotationMapper(CommandDescription.class, d ->
|
||||
ParserParameters.single(StandardParameters.DESCRIPTION, d.value()));
|
||||
}
|
||||
|
|
@ -156,6 +159,7 @@ public final class AnnotationParser<C> {
|
|||
tokens.get(commandToken).getMinor(),
|
||||
metaBuilder.build());
|
||||
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<CommandArgument<C, ?>, String> argumentDescriptions = new HashMap<>();
|
||||
/* 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)) {
|
||||
builder = builder.hidden();
|
||||
}
|
||||
/* Apply flags */
|
||||
for (final CommandFlag<?> flag : flags) {
|
||||
builder = builder.flag(flag);
|
||||
}
|
||||
/* Construct and register the command */
|
||||
final Command<C> builtCommand = builder.build();
|
||||
commands.add(builtCommand);
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ import java.util.function.Function;
|
|||
* Utility that extract {@link Argument arguments} from
|
||||
* {@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
|
||||
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;
|
||||
|
||||
import cloud.commandframework.arguments.CommandArgument;
|
||||
import cloud.commandframework.arguments.flags.FlagContext;
|
||||
import cloud.commandframework.context.CommandContext;
|
||||
import cloud.commandframework.execution.CommandExecutionHandler;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
|
|
@ -55,6 +56,7 @@ class MethodCommandExecutionHandler<C> implements CommandExecutionHandler<C> {
|
|||
@Override
|
||||
public void execute(@NonNull final CommandContext<C> commandContext) {
|
||||
final List<Object> arguments = new ArrayList<>(this.parameters.length);
|
||||
final FlagContext flagContext = commandContext.flags();
|
||||
|
||||
/* Bind parameters to context */
|
||||
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);
|
||||
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 {
|
||||
if (parameter.getType().isAssignableFrom(commandContext.getSender().getClass())) {
|
||||
arguments.add(commandContext.getSender());
|
||||
|
|
|
|||
|
|
@ -58,6 +58,8 @@ class AnnotationParserTest {
|
|||
manager.executeCommand(new TestCommandSender(), "proxycommand 10").join();
|
||||
Assertions.assertThrows(CompletionException.class, () ->
|
||||
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")
|
||||
|
|
@ -69,4 +71,13 @@ class AnnotationParserTest {
|
|||
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));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
|
|
|
|||
|
|
@ -89,6 +89,7 @@ public final class CommandFlag<T> {
|
|||
/**
|
||||
* Get the flag description
|
||||
* <p>
|
||||
*
|
||||
* @return Flag description
|
||||
*/
|
||||
public @NonNull Description getDescription() {
|
||||
|
|
@ -179,6 +180,17 @@ public final class CommandFlag<T> {
|
|||
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
|
||||
*
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@
|
|||
package cloud.commandframework.arguments.flags;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
|
@ -94,7 +95,7 @@ public final class FlagContext {
|
|||
* @param <T> Value type
|
||||
* @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);
|
||||
if (value == null) {
|
||||
return defaultValue;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue