From 6282592971ebfa04910e71309c92f2676659dfa3 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz Date: Wed, 18 Nov 2020 19:48:50 +0100 Subject: [PATCH] Can now parse basic parameter types --- .../components/commands/ArgumentType.java | 6 +++ .../commands/ArgumentTypeHandler.java | 21 +++++++++ .../ArgumentTypeHandlerCollection.java | 30 ++++++++++++ .../{internal => }/CommandEndpoint.java | 4 +- .../commands/{internal => }/CommandGroup.java | 10 ++-- .../components/commands/CommandManager.java | 19 ++++++++ .../components/commands/CommandMethod.java | 47 +++++++++++++++++++ .../commands/CommandMethodArgument.java | 17 +++++++ .../commands/{internal => }/CommandNode.java | 2 +- .../{internal => }/DiscoveryUtils.java | 7 +-- .../primitive/IntegerTypeHandler.java | 10 ++++ .../commands/internal/CommandMethod.java | 26 ---------- .../{internal => }/CommandGraphTests.java | 43 ++++++++++++++--- 13 files changed, 198 insertions(+), 44 deletions(-) create mode 100644 src/main/java/fr/zcraft/quartzlib/components/commands/ArgumentType.java create mode 100644 src/main/java/fr/zcraft/quartzlib/components/commands/ArgumentTypeHandler.java create mode 100644 src/main/java/fr/zcraft/quartzlib/components/commands/ArgumentTypeHandlerCollection.java rename src/main/java/fr/zcraft/quartzlib/components/commands/{internal => }/CommandEndpoint.java (76%) rename src/main/java/fr/zcraft/quartzlib/components/commands/{internal => }/CommandGroup.java (76%) create mode 100644 src/main/java/fr/zcraft/quartzlib/components/commands/CommandManager.java create mode 100644 src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethod.java create mode 100644 src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethodArgument.java rename src/main/java/fr/zcraft/quartzlib/components/commands/{internal => }/CommandNode.java (87%) rename src/main/java/fr/zcraft/quartzlib/components/commands/{internal => }/DiscoveryUtils.java (79%) create mode 100644 src/main/java/fr/zcraft/quartzlib/components/commands/arguments/primitive/IntegerTypeHandler.java delete mode 100644 src/main/java/fr/zcraft/quartzlib/components/commands/internal/CommandMethod.java rename src/test/java/fr/zcraft/quartzlib/components/commands/{internal => }/CommandGraphTests.java (56%) diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/ArgumentType.java b/src/main/java/fr/zcraft/quartzlib/components/commands/ArgumentType.java new file mode 100644 index 00000000..9cff7a1b --- /dev/null +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/ArgumentType.java @@ -0,0 +1,6 @@ +package fr.zcraft.quartzlib.components.commands; + +@FunctionalInterface +public interface ArgumentType { + T parse(String raw); +} diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/ArgumentTypeHandler.java b/src/main/java/fr/zcraft/quartzlib/components/commands/ArgumentTypeHandler.java new file mode 100644 index 00000000..6d693652 --- /dev/null +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/ArgumentTypeHandler.java @@ -0,0 +1,21 @@ +package fr.zcraft.quartzlib.components.commands; + +import fr.zcraft.quartzlib.components.commands.ArgumentType; + +public class ArgumentTypeHandler { + private final Class resultType; + private final ArgumentType typeHandler; + + public ArgumentTypeHandler(Class resultType, ArgumentType typeHandler) { + this.resultType = resultType; + this.typeHandler = typeHandler; + } + + public ArgumentType getTypeHandler() { + return typeHandler; + } + + public Class getResultType() { + return resultType; + } +} diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/ArgumentTypeHandlerCollection.java b/src/main/java/fr/zcraft/quartzlib/components/commands/ArgumentTypeHandlerCollection.java new file mode 100644 index 00000000..ff33a655 --- /dev/null +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/ArgumentTypeHandlerCollection.java @@ -0,0 +1,30 @@ +package fr.zcraft.quartzlib.components.commands; + +import fr.zcraft.quartzlib.components.commands.arguments.primitive.IntegerTypeHandler; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +class ArgumentTypeHandlerCollection { + private final Map, ArgumentTypeHandler> argumentTypeHandlerMap = new HashMap<>(); + + public ArgumentTypeHandlerCollection () { + this.registerNativeTypes(); + } + + public void register(ArgumentTypeHandler typeHandler) + { + argumentTypeHandlerMap.put(typeHandler.getResultType(), typeHandler); + } + + public Optional> findTypeHandler(Class resultType) { + return Optional.ofNullable(argumentTypeHandlerMap.get(resultType)); + } + + private void registerNativeTypes () { + register(new ArgumentTypeHandler<>(Integer.class, new IntegerTypeHandler())); + + register(new ArgumentTypeHandler<>(String.class, s -> s)); + } +} diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/internal/CommandEndpoint.java b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandEndpoint.java similarity index 76% rename from src/main/java/fr/zcraft/quartzlib/components/commands/internal/CommandEndpoint.java rename to src/main/java/fr/zcraft/quartzlib/components/commands/CommandEndpoint.java index 8e48fffe..aa3961bc 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/internal/CommandEndpoint.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandEndpoint.java @@ -1,6 +1,4 @@ -package fr.zcraft.quartzlib.components.commands.internal; - -import sun.reflect.generics.reflectiveObjects.NotImplementedException; +package fr.zcraft.quartzlib.components.commands; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/internal/CommandGroup.java b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandGroup.java similarity index 76% rename from src/main/java/fr/zcraft/quartzlib/components/commands/internal/CommandGroup.java rename to src/main/java/fr/zcraft/quartzlib/components/commands/CommandGroup.java index d6260695..c0c6eed5 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/internal/CommandGroup.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandGroup.java @@ -1,4 +1,4 @@ -package fr.zcraft.quartzlib.components.commands.internal; +package fr.zcraft.quartzlib.components.commands; import java.util.Arrays; import java.util.HashMap; @@ -11,15 +11,15 @@ class CommandGroup extends CommandNode { private final Map subCommands = new HashMap<>(); - public CommandGroup(Class commandGroupClass, Supplier classInstanceSupplier, String name) { - this(commandGroupClass, classInstanceSupplier, name, null); + public CommandGroup(Class commandGroupClass, Supplier classInstanceSupplier, String name, ArgumentTypeHandlerCollection typeHandlerCollection) { + this(commandGroupClass, classInstanceSupplier, name, typeHandlerCollection, null); } - public CommandGroup(Class commandGroupClass, Supplier classInstanceSupplier, String name, CommandGroup parent) { + public CommandGroup(Class commandGroupClass, Supplier classInstanceSupplier, String name, ArgumentTypeHandlerCollection typeHandlerCollection, CommandGroup parent) { super(name, parent); this.commandGroupClass = commandGroupClass; this.classInstanceSupplier = classInstanceSupplier; - DiscoveryUtils.getCommandMethods(commandGroupClass).forEach(this::addMethod); + DiscoveryUtils.getCommandMethods(commandGroupClass, typeHandlerCollection).forEach(this::addMethod); } public Iterable getSubCommands () { diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandManager.java b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandManager.java new file mode 100644 index 00000000..a24c44e4 --- /dev/null +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandManager.java @@ -0,0 +1,19 @@ +package fr.zcraft.quartzlib.components.commands; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Supplier; + +public class CommandManager { + private final Map rootCommands = new HashMap<>(); + private final ArgumentTypeHandlerCollection typeHandlerCollection = new ArgumentTypeHandlerCollection(); + + public void registerCommand(String name, Class commandType, Supplier commandClassSupplier) { + CommandGroup group = new CommandGroup(commandType, commandClassSupplier, name, typeHandlerCollection); + rootCommands.put(name, group); + } + + public void run(String commandName, String... args) { + ((CommandGroup) rootCommands.get(commandName)).run(args); // TODO + } +} diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethod.java b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethod.java new file mode 100644 index 00000000..1ecbaf14 --- /dev/null +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethod.java @@ -0,0 +1,47 @@ +package fr.zcraft.quartzlib.components.commands; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Arrays; + +class CommandMethod { + private final Method method; + private final String name; + private final CommandMethodArgument[] arguments; + + CommandMethod(Method method, ArgumentTypeHandlerCollection typeHandlerCollection) { + this.method = method; + this.name = method.getName(); + + arguments = Arrays.stream(method.getParameters()) + .map(p -> new CommandMethodArgument(p, typeHandlerCollection)) + .toArray(CommandMethodArgument[]::new); + } + + public String getName() { + return name; + } + + public void run(Object target, String[] args) { + Object[] parsedArgs = parseArguments(args); + try { + this.method.invoke(target, parsedArgs); + } catch (IllegalAccessException | InvocationTargetException e) { + e.printStackTrace(); // TODO + } + } + + private Object[] parseArguments(String[] args) { + Object[] parsed = new Object[args.length]; + + for (int i = 0; i < args.length; i++) { + parsed[i] = arguments[i].parse(args[i]); + } + + return parsed; + } + + public CommandMethodArgument[] getArguments() { + return arguments; + } +} diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethodArgument.java b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethodArgument.java new file mode 100644 index 00000000..0ea633bb --- /dev/null +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethodArgument.java @@ -0,0 +1,17 @@ +package fr.zcraft.quartzlib.components.commands; + +import java.lang.reflect.Parameter; + +public class CommandMethodArgument { + private final Parameter parameter; + private final ArgumentTypeHandler typeHandler; + + public CommandMethodArgument(Parameter parameter, ArgumentTypeHandlerCollection typeHandlerCollection) { + this.parameter = parameter; + this.typeHandler = typeHandlerCollection.findTypeHandler(parameter.getType()).get(); // FIXME: handle unknown types + } + + public Object parse(String raw) { + return this.typeHandler.getTypeHandler().parse(raw); + } +} diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/internal/CommandNode.java b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandNode.java similarity index 87% rename from src/main/java/fr/zcraft/quartzlib/components/commands/internal/CommandNode.java rename to src/main/java/fr/zcraft/quartzlib/components/commands/CommandNode.java index 75b69d13..58a350a9 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/internal/CommandNode.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandNode.java @@ -1,4 +1,4 @@ -package fr.zcraft.quartzlib.components.commands.internal; +package fr.zcraft.quartzlib.components.commands; abstract class CommandNode { private final String name; diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/internal/DiscoveryUtils.java b/src/main/java/fr/zcraft/quartzlib/components/commands/DiscoveryUtils.java similarity index 79% rename from src/main/java/fr/zcraft/quartzlib/components/commands/internal/DiscoveryUtils.java rename to src/main/java/fr/zcraft/quartzlib/components/commands/DiscoveryUtils.java index 09a30f1e..023b23da 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/internal/DiscoveryUtils.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/DiscoveryUtils.java @@ -1,17 +1,18 @@ -package fr.zcraft.quartzlib.components.commands.internal; +package fr.zcraft.quartzlib.components.commands; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Arrays; import java.util.function.Supplier; import java.util.stream.Stream; abstract class DiscoveryUtils { - public static Stream getCommandMethods(Class commandGroupClass) { + public static Stream getCommandMethods(Class commandGroupClass, ArgumentTypeHandlerCollection typeHandlerCollection) { return Arrays.stream(commandGroupClass.getDeclaredMethods()) .filter(m -> Modifier.isPublic(m.getModifiers()) && !Modifier.isStatic(m.getModifiers())) - .map(CommandMethod::new); + .map((Method method) -> new CommandMethod(method, typeHandlerCollection)); } public static Supplier getClassConstructorSupplier (Class commandGroupClass) { diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/arguments/primitive/IntegerTypeHandler.java b/src/main/java/fr/zcraft/quartzlib/components/commands/arguments/primitive/IntegerTypeHandler.java new file mode 100644 index 00000000..84cba16e --- /dev/null +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/arguments/primitive/IntegerTypeHandler.java @@ -0,0 +1,10 @@ +package fr.zcraft.quartzlib.components.commands.arguments.primitive; + +import fr.zcraft.quartzlib.components.commands.ArgumentType; + +public class IntegerTypeHandler implements ArgumentType { + @Override + public Integer parse(String raw) { + return Integer.parseInt(raw, 10); // TODO: handle exceptions + } +} diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/internal/CommandMethod.java b/src/main/java/fr/zcraft/quartzlib/components/commands/internal/CommandMethod.java deleted file mode 100644 index d97e03bc..00000000 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/internal/CommandMethod.java +++ /dev/null @@ -1,26 +0,0 @@ -package fr.zcraft.quartzlib.components.commands.internal; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; - -class CommandMethod { - private final Method method; - private final String name; - - CommandMethod(Method method) { - this.method = method; - this.name = method.getName(); - } - - public String getName() { - return name; - } - - public void run(Object target, String[] args) { - try { - this.method.invoke(target, (Object[]) args); - } catch (IllegalAccessException | InvocationTargetException e) { - e.printStackTrace(); // TODO - } - } -} diff --git a/src/test/java/fr/zcraft/quartzlib/components/commands/internal/CommandGraphTests.java b/src/test/java/fr/zcraft/quartzlib/components/commands/CommandGraphTests.java similarity index 56% rename from src/test/java/fr/zcraft/quartzlib/components/commands/internal/CommandGraphTests.java rename to src/test/java/fr/zcraft/quartzlib/components/commands/CommandGraphTests.java index fa0210b4..921fa8ca 100644 --- a/src/test/java/fr/zcraft/quartzlib/components/commands/internal/CommandGraphTests.java +++ b/src/test/java/fr/zcraft/quartzlib/components/commands/CommandGraphTests.java @@ -1,6 +1,7 @@ -package fr.zcraft.quartzlib.components.commands.internal; +package fr.zcraft.quartzlib.components.commands; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import java.util.stream.StreamSupport; @@ -16,6 +17,13 @@ static public void staticMethod () {} } public class CommandGraphTests { + private CommandManager commands; + + @BeforeEach + public void beforeEach () { + commands = new CommandManager(); + } + @Test public void canDiscoverBasicSubcommands() { class FooCommand { public void add () {} @@ -23,13 +31,13 @@ public void get () {} public void list () {} } - CommandGroup commandGroup = new CommandGroup(FooCommand.class, () -> new FooCommand(), "foo"); + CommandGroup commandGroup = new CommandGroup(FooCommand.class, () -> new FooCommand(), "foo", new ArgumentTypeHandlerCollection()); String[] commandNames = StreamSupport.stream(commandGroup.getSubCommands().spliterator(), false).map(CommandNode::getName).toArray(String[]::new); Assertions.assertArrayEquals(new String[] {"add", "get", "list"}, commandNames); } @Test public void onlyDiscoversPublicMethods() { - CommandGroup commandGroup = new CommandGroup(CommandWithStatics.class, () -> new CommandWithStatics(), "foo"); + CommandGroup commandGroup = new CommandGroup(CommandWithStatics.class, () -> new CommandWithStatics(), "foo", new ArgumentTypeHandlerCollection()); String[] commandNames = StreamSupport.stream(commandGroup.getSubCommands().spliterator(), false).map(CommandNode::getName).toArray(String[]::new); Assertions.assertArrayEquals(new String[] {"add", "delete"}, commandNames); } @@ -43,9 +51,32 @@ class FooCommand { public void list () { ran[2] = true; } } - FooCommand f = new FooCommand(); - CommandGroup commandGroup = new CommandGroup(FooCommand.class, () -> new FooCommand(),"foo"); - commandGroup.run("get"); + commands.registerCommand("foo", FooCommand.class, () -> new FooCommand()); + commands.run("foo", "get"); Assertions.assertArrayEquals(new boolean[] { false, true, false }, ran); } + + @Test public void canReceiveStringArguments() { + final String[] argValue = {""}; + + class FooCommand { + public void add (String arg) { argValue[0] = arg; } + } + + commands.registerCommand("foo", FooCommand.class, () -> new FooCommand()); + commands.run("foo", "add", "pomf"); + Assertions.assertArrayEquals(new String[] { "pomf" }, argValue); + } + + @Test public void canReceiveParsedArguments() { + final int[] argValue = {0}; + + class FooCommand { + public void add (Integer arg) { argValue[0] = arg; } + } + + commands.registerCommand("foo", FooCommand.class, () -> new FooCommand()); + commands.run("foo", "add", "42"); + Assertions.assertArrayEquals(new int[] { 42 }, argValue); + } }