From a14bc55a8fbd89dbd7ad12b71bdc45dea12bf626 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz Date: Tue, 24 Nov 2020 10:22:43 +0100 Subject: [PATCH] Can now parse all enums by default, and add infrastructure to parse any generic type --- .../components/commands/ArgumentType.java | 4 +- .../commands/ArgumentTypeHandler.java | 4 +- .../ArgumentTypeHandlerCollection.java | 36 ++++++++--- .../components/commands/CommandEndpoint.java | 4 +- .../components/commands/CommandGroup.java | 6 +- .../components/commands/CommandManager.java | 4 +- .../components/commands/CommandMethod.java | 7 ++- .../commands/CommandMethodArgument.java | 4 +- .../components/commands/CommandNode.java | 4 +- .../commands/GenericArgumentType.java | 7 +++ .../arguments/generic/EnumArgumentType.java | 59 +++++++++++++++++++ ...eHandler.java => IntegerArgumentType.java} | 2 +- .../exceptions/ArgumentParseException.java | 4 ++ .../commands/exceptions/CommandException.java | 4 ++ .../commands/CommandGraphTests.java | 22 ++++++- .../generic/EnumArgumentTypeTests.java | 22 +++++++ 16 files changed, 170 insertions(+), 23 deletions(-) create mode 100644 src/main/java/fr/zcraft/quartzlib/components/commands/GenericArgumentType.java create mode 100644 src/main/java/fr/zcraft/quartzlib/components/commands/arguments/generic/EnumArgumentType.java rename src/main/java/fr/zcraft/quartzlib/components/commands/arguments/primitive/{IntegerTypeHandler.java => IntegerArgumentType.java} (79%) create mode 100644 src/main/java/fr/zcraft/quartzlib/components/commands/exceptions/ArgumentParseException.java create mode 100644 src/main/java/fr/zcraft/quartzlib/components/commands/exceptions/CommandException.java create mode 100644 src/test/java/fr/zcraft/quartzlib/components/commands/arguments/generic/EnumArgumentTypeTests.java diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/ArgumentType.java b/src/main/java/fr/zcraft/quartzlib/components/commands/ArgumentType.java index 9cff7a1b..f97c2c0c 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/ArgumentType.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/ArgumentType.java @@ -1,6 +1,8 @@ package fr.zcraft.quartzlib.components.commands; +import fr.zcraft.quartzlib.components.commands.exceptions.ArgumentParseException; + @FunctionalInterface public interface ArgumentType { - T parse(String raw); + T parse(String raw) throws ArgumentParseException; } diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/ArgumentTypeHandler.java b/src/main/java/fr/zcraft/quartzlib/components/commands/ArgumentTypeHandler.java index 6d693652..d79503d4 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/ArgumentTypeHandler.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/ArgumentTypeHandler.java @@ -1,8 +1,6 @@ package fr.zcraft.quartzlib.components.commands; -import fr.zcraft.quartzlib.components.commands.ArgumentType; - -public class ArgumentTypeHandler { +class ArgumentTypeHandler { private final Class resultType; private final ArgumentType typeHandler; diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/ArgumentTypeHandlerCollection.java b/src/main/java/fr/zcraft/quartzlib/components/commands/ArgumentTypeHandlerCollection.java index ff33a655..b1e3ca66 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/ArgumentTypeHandlerCollection.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/ArgumentTypeHandlerCollection.java @@ -1,13 +1,14 @@ package fr.zcraft.quartzlib.components.commands; -import fr.zcraft.quartzlib.components.commands.arguments.primitive.IntegerTypeHandler; +import fr.zcraft.quartzlib.components.commands.arguments.generic.EnumArgumentType; +import fr.zcraft.quartzlib.components.commands.arguments.primitive.IntegerArgumentType; +import org.jetbrains.annotations.Nullable; -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; +import java.util.*; class ArgumentTypeHandlerCollection { private final Map, ArgumentTypeHandler> argumentTypeHandlerMap = new HashMap<>(); + private final List> genericArgumentTypes = new ArrayList<>(); public ArgumentTypeHandlerCollection () { this.registerNativeTypes(); @@ -18,13 +19,34 @@ public void register(ArgumentTypeHandler typeHandler) argumentTypeHandlerMap.put(typeHandler.getResultType(), typeHandler); } + public void register(GenericArgumentType genericArgumentType) { + genericArgumentTypes.add(genericArgumentType); + } + public Optional> findTypeHandler(Class resultType) { - return Optional.ofNullable(argumentTypeHandlerMap.get(resultType)); + ArgumentTypeHandler typeHandler = argumentTypeHandlerMap.get(resultType); + if (typeHandler != null) return Optional.of(typeHandler); + return this.findGenericTypeHandler(resultType); } - private void registerNativeTypes () { - register(new ArgumentTypeHandler<>(Integer.class, new IntegerTypeHandler())); + private Optional> findGenericTypeHandler(Class resultType) { + for (GenericArgumentType t : genericArgumentTypes) { + Optional> matchingArgumentType = t.getMatchingArgumentType(resultType); + if (matchingArgumentType.isPresent()) { + ArgumentTypeHandler typeHandler = new ArgumentTypeHandler<>(resultType, (ArgumentType) matchingArgumentType.get()); + return Optional.of(typeHandler); + } + } + return Optional.empty(); + } + + private void registerNativeTypes () { + // Primitive types + register(new ArgumentTypeHandler<>(Integer.class, new IntegerArgumentType())); register(new ArgumentTypeHandler<>(String.class, s -> s)); + + // Generic types + register(new EnumArgumentType()); } } diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandEndpoint.java b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandEndpoint.java index aa3961bc..061d5b49 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandEndpoint.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandEndpoint.java @@ -1,5 +1,7 @@ package fr.zcraft.quartzlib.components.commands; +import fr.zcraft.quartzlib.components.commands.exceptions.CommandException; + import java.util.ArrayList; import java.util.List; @@ -11,7 +13,7 @@ class CommandEndpoint extends CommandNode { } @Override - void run(Object instance, String[] args) { + void run(Object instance, String[] args) throws CommandException { this.methods.get(0).run(instance, args); } diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandGroup.java b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandGroup.java index c0c6eed5..1b87d313 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandGroup.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandGroup.java @@ -1,5 +1,7 @@ package fr.zcraft.quartzlib.components.commands; +import fr.zcraft.quartzlib.components.commands.exceptions.CommandException; + import java.util.Arrays; import java.util.HashMap; import java.util.Map; @@ -37,13 +39,13 @@ private void addMethod(CommandMethod method) { endpoint.addMethod(method); } - void run(String... args) { + void run(String... args) throws CommandException { Object commandObject = classInstanceSupplier.get(); run(commandObject, args); } @Override - void run(Object instance, String[] args) { + void run(Object instance, String[] args) throws CommandException { String commandName = args[0]; CommandNode subCommand = subCommands.get(commandName); subCommand.run(instance, Arrays.copyOfRange(args, 1, args.length)); diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandManager.java b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandManager.java index a24c44e4..8845ed31 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandManager.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandManager.java @@ -1,5 +1,7 @@ package fr.zcraft.quartzlib.components.commands; +import fr.zcraft.quartzlib.components.commands.exceptions.CommandException; + import java.util.HashMap; import java.util.Map; import java.util.function.Supplier; @@ -13,7 +15,7 @@ public void registerCommand(String name, Class commandType, Supplier c rootCommands.put(name, group); } - public void run(String commandName, String... args) { + public void run(String commandName, String... args) throws CommandException { ((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 index 1ecbaf14..5f878900 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethod.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethod.java @@ -1,5 +1,8 @@ package fr.zcraft.quartzlib.components.commands; +import fr.zcraft.quartzlib.components.commands.exceptions.ArgumentParseException; +import fr.zcraft.quartzlib.components.commands.exceptions.CommandException; + import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Arrays; @@ -22,7 +25,7 @@ public String getName() { return name; } - public void run(Object target, String[] args) { + public void run(Object target, String[] args) throws CommandException { Object[] parsedArgs = parseArguments(args); try { this.method.invoke(target, parsedArgs); @@ -31,7 +34,7 @@ public void run(Object target, String[] args) { } } - private Object[] parseArguments(String[] args) { + private Object[] parseArguments(String[] args) throws ArgumentParseException { Object[] parsed = new Object[args.length]; for (int i = 0; i < args.length; i++) { diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethodArgument.java b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethodArgument.java index 0ea633bb..06b06aad 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethodArgument.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethodArgument.java @@ -1,5 +1,7 @@ package fr.zcraft.quartzlib.components.commands; +import fr.zcraft.quartzlib.components.commands.exceptions.ArgumentParseException; + import java.lang.reflect.Parameter; public class CommandMethodArgument { @@ -11,7 +13,7 @@ public CommandMethodArgument(Parameter parameter, ArgumentTypeHandlerCollection this.typeHandler = typeHandlerCollection.findTypeHandler(parameter.getType()).get(); // FIXME: handle unknown types } - public Object parse(String raw) { + public Object parse(String raw) throws ArgumentParseException { return this.typeHandler.getTypeHandler().parse(raw); } } diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandNode.java b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandNode.java index 58a350a9..8fa1538d 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandNode.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandNode.java @@ -1,5 +1,7 @@ package fr.zcraft.quartzlib.components.commands; +import fr.zcraft.quartzlib.components.commands.exceptions.CommandException; + abstract class CommandNode { private final String name; private final CommandGroup parent; @@ -17,5 +19,5 @@ public CommandGroup getParent() { return parent; } - abstract void run(Object instance, String[] args); + abstract void run(Object instance, String[] args) throws CommandException; } diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/GenericArgumentType.java b/src/main/java/fr/zcraft/quartzlib/components/commands/GenericArgumentType.java new file mode 100644 index 00000000..48dc320f --- /dev/null +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/GenericArgumentType.java @@ -0,0 +1,7 @@ +package fr.zcraft.quartzlib.components.commands; + +import java.util.Optional; + +public interface GenericArgumentType { + Optional> getMatchingArgumentType(Class type); +} diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/arguments/generic/EnumArgumentType.java b/src/main/java/fr/zcraft/quartzlib/components/commands/arguments/generic/EnumArgumentType.java new file mode 100644 index 00000000..3baeb45a --- /dev/null +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/arguments/generic/EnumArgumentType.java @@ -0,0 +1,59 @@ +package fr.zcraft.quartzlib.components.commands.arguments.generic; + +import fr.zcraft.quartzlib.components.commands.ArgumentType; +import fr.zcraft.quartzlib.components.commands.GenericArgumentType; +import fr.zcraft.quartzlib.components.commands.exceptions.ArgumentParseException; + +import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +public class EnumArgumentType implements GenericArgumentType> { + @Override + public Optional>> getMatchingArgumentType(Class type) { + if (type.isEnum()) { + return Optional.of(new DiscreteEnumArgumentType(type)); + } + return Optional.empty(); + } + + static private class DiscreteEnumArgumentType implements ArgumentType> { + private final Map> enumValues; + + public DiscreteEnumArgumentType(Class enumClass) { + enumValues = getEnumValues(enumClass); + } + + @Override + public Enum parse(String raw) throws ArgumentParseException { + Enum value = enumValues.get(raw); + if (value == null) throw new EnumParseException(); + return value; + } + } + + static private class EnumParseException extends ArgumentParseException { + + } + + static private Map> getEnumValues (Class enumClass) { + Map> enumValues = new HashMap<>(); + + Arrays.stream(enumClass.getDeclaredFields()) + .filter(f -> Modifier.isPublic(f.getModifiers()) + && Modifier.isStatic(f.getModifiers()) + && enumClass.isAssignableFrom(f.getType())) + .forEach(f -> { + try { + f.setAccessible(true); + enumValues.put(f.getName().toLowerCase(), (Enum)f.get(null)); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + }); + + return enumValues; + } +} 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/IntegerArgumentType.java similarity index 79% rename from src/main/java/fr/zcraft/quartzlib/components/commands/arguments/primitive/IntegerTypeHandler.java rename to src/main/java/fr/zcraft/quartzlib/components/commands/arguments/primitive/IntegerArgumentType.java index 84cba16e..8d2a2c57 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/arguments/primitive/IntegerTypeHandler.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/arguments/primitive/IntegerArgumentType.java @@ -2,7 +2,7 @@ import fr.zcraft.quartzlib.components.commands.ArgumentType; -public class IntegerTypeHandler implements ArgumentType { +public class IntegerArgumentType 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/exceptions/ArgumentParseException.java b/src/main/java/fr/zcraft/quartzlib/components/commands/exceptions/ArgumentParseException.java new file mode 100644 index 00000000..b6ff6416 --- /dev/null +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/exceptions/ArgumentParseException.java @@ -0,0 +1,4 @@ +package fr.zcraft.quartzlib.components.commands.exceptions; + +public class ArgumentParseException extends CommandException { +} diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/exceptions/CommandException.java b/src/main/java/fr/zcraft/quartzlib/components/commands/exceptions/CommandException.java new file mode 100644 index 00000000..ea6c97d8 --- /dev/null +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/exceptions/CommandException.java @@ -0,0 +1,4 @@ +package fr.zcraft.quartzlib.components.commands.exceptions; + +public abstract class CommandException extends Exception { +} diff --git a/src/test/java/fr/zcraft/quartzlib/components/commands/CommandGraphTests.java b/src/test/java/fr/zcraft/quartzlib/components/commands/CommandGraphTests.java index 921fa8ca..53e27dc3 100644 --- a/src/test/java/fr/zcraft/quartzlib/components/commands/CommandGraphTests.java +++ b/src/test/java/fr/zcraft/quartzlib/components/commands/CommandGraphTests.java @@ -1,5 +1,6 @@ package fr.zcraft.quartzlib.components.commands; +import fr.zcraft.quartzlib.components.commands.exceptions.CommandException; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -42,7 +43,7 @@ public void list () {} Assertions.assertArrayEquals(new String[] {"add", "delete"}, commandNames); } - @Test public void canRunBasicSubcommands() { + @Test public void canRunBasicSubcommands() throws CommandException { final boolean[] ran = {false, false, false}; class FooCommand { @@ -56,7 +57,7 @@ class FooCommand { Assertions.assertArrayEquals(new boolean[] { false, true, false }, ran); } - @Test public void canReceiveStringArguments() { + @Test public void canReceiveStringArguments() throws CommandException { final String[] argValue = {""}; class FooCommand { @@ -68,7 +69,7 @@ class FooCommand { Assertions.assertArrayEquals(new String[] { "pomf" }, argValue); } - @Test public void canReceiveParsedArguments() { + @Test public void canReceiveParsedArguments() throws CommandException { final int[] argValue = {0}; class FooCommand { @@ -79,4 +80,19 @@ class FooCommand { commands.run("foo", "add", "42"); Assertions.assertArrayEquals(new int[] { 42 }, argValue); } + + enum FooEnum { FOO, BAR } + @Test public void canReceiveEnumArguments() throws CommandException { + final FooEnum[] argValue = {null}; + + class FooCommand { + public void add (FooEnum arg) { argValue[0] = arg; } + } + + commands.registerCommand("foo", FooCommand.class, () -> new FooCommand()); + commands.run("foo", "add", "foo"); + Assertions.assertArrayEquals(new FooEnum[] { FooEnum.FOO }, argValue); + commands.run("foo", "add", "bar"); + Assertions.assertArrayEquals(new FooEnum[] { FooEnum.BAR }, argValue); + } } diff --git a/src/test/java/fr/zcraft/quartzlib/components/commands/arguments/generic/EnumArgumentTypeTests.java b/src/test/java/fr/zcraft/quartzlib/components/commands/arguments/generic/EnumArgumentTypeTests.java new file mode 100644 index 00000000..6041e0ce --- /dev/null +++ b/src/test/java/fr/zcraft/quartzlib/components/commands/arguments/generic/EnumArgumentTypeTests.java @@ -0,0 +1,22 @@ +package fr.zcraft.quartzlib.components.commands.arguments.generic; + +import fr.zcraft.quartzlib.components.commands.ArgumentType; +import fr.zcraft.quartzlib.components.commands.exceptions.ArgumentParseException; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class EnumArgumentTypeTests { + private final EnumArgumentType enumArgumentType = new EnumArgumentType(); + + private enum SimpleEnum { FOO, BAR } + + @Test + public void worksOnSimpleEnum() throws ArgumentParseException { + ArgumentType argumentType = enumArgumentType.getMatchingArgumentType(SimpleEnum.class).get(); + + Assertions.assertEquals(SimpleEnum.FOO, argumentType.parse("foo")); + Assertions.assertEquals(SimpleEnum.BAR, argumentType.parse("bar")); + + Assertions.assertThrows(ArgumentParseException.class, () -> argumentType.parse("blah")); + } +}