More progress on command method parsing
This commit is contained in:
parent
da68a6bc87
commit
1e58ca3f13
4 changed files with 153 additions and 30 deletions
|
|
@ -23,15 +23,20 @@
|
|||
//
|
||||
package com.intellectualsites.commands.annotations;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.reflect.TypeToken;
|
||||
import com.intellectualsites.commands.Command;
|
||||
import com.intellectualsites.commands.CommandManager;
|
||||
import com.intellectualsites.commands.arguments.CommandArgument;
|
||||
import com.intellectualsites.commands.arguments.parser.ArgumentParser;
|
||||
import com.intellectualsites.commands.arguments.parser.ParserParameters;
|
||||
import com.intellectualsites.commands.execution.CommandExecutionHandler;
|
||||
import com.intellectualsites.commands.meta.CommandMeta;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Parameter;
|
||||
import java.util.ArrayList;
|
||||
|
|
@ -39,6 +44,7 @@ import java.util.Arrays;
|
|||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.function.Predicate;
|
||||
|
|
@ -55,7 +61,7 @@ public final class AnnotationParser<C, M extends CommandMeta> {
|
|||
private static final Predicate<String> PATTERN_ARGUMENT_LITERAL = Pattern.compile("([A-Za-z0-9]+)(|([A-Za-z0-9]+))*")
|
||||
.asPredicate();
|
||||
private static final Predicate<String> PATTERN_ARGUMENT_REQUIRED = Pattern.compile("<([A-Za-z0-9]+)>").asPredicate();
|
||||
private static final Predicate<String> PATTERN_ARGUMENT_OPTIONAL = Pattern.compile("\\[([A-Za-z0-9]+)\\]").asPredicate();
|
||||
private static final Predicate<String> PATTERN_ARGUMENT_OPTIONAL = Pattern.compile("\\[([A-Za-z0-9]+)]").asPredicate();
|
||||
|
||||
private final CommandManager<C, M> manager;
|
||||
|
||||
|
|
@ -91,40 +97,134 @@ public final class AnnotationParser<C, M extends CommandMeta> {
|
|||
}
|
||||
commandMethodPairs.add(new CommandMethodPair(method, commandMethod));
|
||||
}
|
||||
return this.construct(commandMethodPairs);
|
||||
final Collection<Command<C, M>> commands = this.construct(instance, commandMethodPairs);
|
||||
for (final Command<C, M> command : commands) {
|
||||
this.manager.command(command);
|
||||
}
|
||||
return commands;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private Collection<Command<C, M>> construct(@Nonnull final Collection<CommandMethodPair> methodPairs) {
|
||||
@SuppressWarnings("unchecked")
|
||||
private Collection<Command<C, M>> construct(@Nonnull final Object instance,
|
||||
@Nonnull final Collection<CommandMethodPair> methodPairs) {
|
||||
final Collection<Command<C, M>> commands = new ArrayList<>();
|
||||
for (final CommandMethodPair commandMethodPair : methodPairs) {
|
||||
final CommandMethod commandMethod = commandMethodPair.getCommandMethod();
|
||||
final Method method = commandMethodPair.getMethod();
|
||||
final Map<String, ArgumentMode> tokens = this.parseSyntax(commandMethod.value());
|
||||
Command.Builder<C, M> builder = this.manager.commandBuilder(commandMethod.value(),
|
||||
Collections.emptyList(),
|
||||
manager.createDefaultCommandMeta());
|
||||
final LinkedHashMap<String, ArgumentMode> tokens = this.parseSyntax(commandMethod.value());
|
||||
/* Determine command name */
|
||||
final String commandToken = commandMethod.value().split(" ")[0].split("\\|")[0];
|
||||
@SuppressWarnings("ALL")
|
||||
Command.Builder builder = this.manager.commandBuilder(commandToken,
|
||||
Collections.emptyList(),
|
||||
manager.createDefaultCommandMeta());
|
||||
final Collection<ArgumentParameterPair> arguments = this.getArguments(method);
|
||||
final Map<String, CommandArgument<C, ?>> commandArguments = Maps.newHashMap();
|
||||
/* Go through all annotated parameters and build up the argument tree */
|
||||
for (final ArgumentParameterPair argumentPair : arguments) {
|
||||
final Parameter parameter = argumentPair.getParameter();
|
||||
final Collection<Annotation> annotations = Arrays.asList(parameter.getAnnotations());
|
||||
final TypeToken<?> token = TypeToken.of(parameter.getParameterizedType());
|
||||
final ParserParameters parameters = this.manager.getParserRegistry()
|
||||
.parseAnnotations(token, annotations);
|
||||
final ArgumentParser<C, ?> parser = this.manager.getParserRegistry()
|
||||
.createParser(token, parameters)
|
||||
.orElseThrow(() -> new IllegalArgumentException(
|
||||
String.format("Parameter '%s' in method '%s' "
|
||||
+ "has parser '%s' but no parser exists "
|
||||
+ "for that type",
|
||||
parameter.getName(), method.getName(),
|
||||
token.toString())));
|
||||
final CommandArgument<C, ?> argument = this.buildArgument(method,
|
||||
tokens.get(argumentPair.getArgument().value()),
|
||||
argumentPair);
|
||||
commandArguments.put(argument.getName(), argument);
|
||||
}
|
||||
|
||||
boolean commandNameFound = false;
|
||||
/* Build the command tree */
|
||||
for (final Map.Entry<String, ArgumentMode> entry : tokens.entrySet()) {
|
||||
if (!commandNameFound) {
|
||||
commandNameFound = true;
|
||||
continue;
|
||||
}
|
||||
if (entry.getValue() == ArgumentMode.LITERAL) {
|
||||
builder = builder.literal(entry.getKey());
|
||||
} else {
|
||||
final CommandArgument<C, ?> argument = commandArguments.get(entry.getKey());
|
||||
if (argument == null) {
|
||||
throw new IllegalArgumentException(String.format(
|
||||
"Found no mapping for argument '%s' in method '%s'",
|
||||
entry.getKey(), method.getName()
|
||||
));
|
||||
}
|
||||
builder = builder.argument(argument);
|
||||
}
|
||||
}
|
||||
/* Decorate command data */
|
||||
builder = builder.withPermission(commandMethod.permission()).withSenderType(commandMethod.requiredSender());
|
||||
/* Construct the handler */
|
||||
final CommandExecutionHandler<C> commandExecutionHandler = commandContext -> {
|
||||
final List<Object> parameters = new ArrayList<>(method.getParameterCount());
|
||||
for (final Parameter parameter : method.getParameters()) {
|
||||
if (parameter.isAnnotationPresent(Argument.class)) {
|
||||
final Argument argument = parameter.getAnnotation(Argument.class);
|
||||
final CommandArgument<C, ?> commandArgument = commandArguments.get(argument.value());
|
||||
if (commandArgument.isRequired()) {
|
||||
parameters.add(commandContext.getRequired(argument.value()));
|
||||
} else {
|
||||
final Object optional = commandContext.get(argument.value()).orElse(null);
|
||||
parameters.add(optional);
|
||||
}
|
||||
} else {
|
||||
if (parameter.getType().isAssignableFrom(commandContext.getSender().getClass())) {
|
||||
parameters.add(commandContext.getSender());
|
||||
} else {
|
||||
throw new IllegalArgumentException(String.format(
|
||||
"Unknown command parameter '%s' in method '%s'",
|
||||
parameter.getName(), method.getName()
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
try {
|
||||
method.invoke(instance, parameters.toArray());
|
||||
} catch (final IllegalAccessException | InvocationTargetException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
};
|
||||
builder = builder.handler(commandExecutionHandler);
|
||||
commands.add(builder.build());
|
||||
}
|
||||
return commands;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@SuppressWarnings("unchecked")
|
||||
private CommandArgument<C, ?> buildArgument(@Nonnull final Method method,
|
||||
@Nullable final ArgumentMode argumentMode,
|
||||
@Nonnull final ArgumentParameterPair argumentPair) {
|
||||
final Parameter parameter = argumentPair.getParameter();
|
||||
final Collection<Annotation> annotations = Arrays.asList(parameter.getAnnotations());
|
||||
final TypeToken<?> token = TypeToken.of(parameter.getParameterizedType());
|
||||
final ParserParameters parameters = this.manager.getParserRegistry()
|
||||
.parseAnnotations(token, annotations);
|
||||
final ArgumentParser<C, ?> parser = this.manager.getParserRegistry()
|
||||
.createParser(token, parameters)
|
||||
.orElseThrow(() -> new IllegalArgumentException(
|
||||
String.format("Parameter '%s' in method '%s' "
|
||||
+ "has parser '%s' but no parser exists "
|
||||
+ "for that type",
|
||||
parameter.getName(), method.getName(),
|
||||
token.toString())));
|
||||
if (argumentMode == null || argumentMode == ArgumentMode.LITERAL) {
|
||||
throw new IllegalArgumentException(String.format(
|
||||
"Invalid command argument '%s' in method '%s': "
|
||||
+ "Missing syntax mapping", argumentPair.getArgument().value(), method.getName()));
|
||||
}
|
||||
final Argument argument = argumentPair.getArgument();
|
||||
@SuppressWarnings("ALL")
|
||||
final CommandArgument.Builder argumentBuilder = CommandArgument.ofType(parameter.getType(),
|
||||
argument.value());
|
||||
if (argumentMode == ArgumentMode.OPTIONAL) {
|
||||
if (argument.defaultValue().isEmpty()) {
|
||||
argumentBuilder.asOptional();
|
||||
} else {
|
||||
argumentBuilder.asOptionalWithDefault(argument.defaultValue());
|
||||
}
|
||||
} else {
|
||||
argumentBuilder.asRequired();
|
||||
}
|
||||
return argumentBuilder.manager(this.manager).withParser(parser).build();
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
LinkedHashMap<String, ArgumentMode> parseSyntax(@Nonnull final String syntax) {
|
||||
final StringTokenizer stringTokenizer = new StringTokenizer(syntax, " ");
|
||||
|
|
@ -135,10 +235,12 @@ public final class AnnotationParser<C, M extends CommandMeta> {
|
|||
map.put(token.substring(1, token.length() - 1), ArgumentMode.REQUIRED);
|
||||
} else if (PATTERN_ARGUMENT_OPTIONAL.test(token)) {
|
||||
map.put(token.substring(1, token.length() - 1), ArgumentMode.OPTIONAL);
|
||||
} else {
|
||||
} else if (PATTERN_ARGUMENT_LITERAL.test(token)) {
|
||||
final String[] literals = token.split("\\|");
|
||||
/* Actually use the other literals as well */
|
||||
map.put(literals[0], ArgumentMode.LITERAL);
|
||||
} else {
|
||||
throw new IllegalArgumentException(String.format("Unrecognizable syntax token '%s'", syntax));
|
||||
}
|
||||
}
|
||||
return map;
|
||||
|
|
|
|||
|
|
@ -42,13 +42,6 @@ public @interface Argument {
|
|||
*/
|
||||
String value();
|
||||
|
||||
/**
|
||||
* Whether or not the argument is required
|
||||
*
|
||||
* @return {@code true} if the argument is required, else {@code false}
|
||||
*/
|
||||
boolean required() default true;
|
||||
|
||||
/**
|
||||
* Name of the argument parser
|
||||
*
|
||||
|
|
@ -56,4 +49,11 @@ public @interface Argument {
|
|||
*/
|
||||
String parserName() default "";
|
||||
|
||||
/**
|
||||
* Get the default value
|
||||
*
|
||||
* @return Default value
|
||||
*/
|
||||
String defaultValue() default "";
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,13 +24,18 @@
|
|||
package com.intellectualsites.commands.annotations;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import com.intellectualsites.commands.Command;
|
||||
import com.intellectualsites.commands.CommandManager;
|
||||
import com.intellectualsites.commands.annotations.specifier.Range;
|
||||
import com.intellectualsites.commands.meta.SimpleCommandMeta;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletionException;
|
||||
|
||||
class AnnotationParserTest {
|
||||
|
||||
|
|
@ -54,4 +59,20 @@ class AnnotationParserTest {
|
|||
Assertions.assertEquals(map, arguments);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMethodConstruction() {
|
||||
final Collection<Command<TestCommandSender, SimpleCommandMeta>> commands = annotationParser.parse(this);
|
||||
Assertions.assertFalse(commands.isEmpty());
|
||||
manager.executeCommand(new TestCommandSender(), "test 10").join();
|
||||
Assertions.assertThrows(CompletionException.class, () ->
|
||||
manager.executeCommand(new TestCommandSender(), "test 101").join());
|
||||
}
|
||||
|
||||
@CommandMethod("test <int> [string]")
|
||||
public void testCommand(@Nonnull final TestCommandSender sender,
|
||||
@Argument("int") @Range(max = "100") final int argument,
|
||||
@Nonnull @Argument(value = "string", defaultValue = "potato") final String string) {
|
||||
System.out.printf("Received int: %d and string '%s'\n", argument, string);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -128,7 +128,7 @@ public class CommandArgument<C, T> implements Comparable<CommandArgument<?, ?>>
|
|||
*/
|
||||
@Nonnull
|
||||
public static <C, T> CommandArgument.Builder<C, T> ofType(@Nonnull final Class<T> clazz,
|
||||
@Nonnull final String name) {
|
||||
@Nonnull final String name) {
|
||||
return new Builder<>(clazz, name);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue