From c1b04d177235a20c2d0c2771383d3afb21077719 Mon Sep 17 00:00:00 2001 From: dfsek Date: Mon, 8 Mar 2021 00:14:19 -0700 Subject: [PATCH] argument parsing --- .../terra/api/command/CommandManager.java | 4 +- .../api/command/TerraCommandManager.java | 32 ++++++-- .../terra/api/command/annotation/Command.java | 2 + .../command/exception/CommandException.java | 13 +++ .../exception/InvalidArgumentsException.java | 13 +++ .../exception/MalformedCommandException.java | 16 ++++ .../structure/StructureExportCommand.java | 2 +- common/src/test/java/command/CommandTest.java | 79 +++++++++++++++++++ 8 files changed, 151 insertions(+), 10 deletions(-) create mode 100644 common/src/main/java/com/dfsek/terra/api/command/exception/CommandException.java create mode 100644 common/src/main/java/com/dfsek/terra/api/command/exception/InvalidArgumentsException.java create mode 100644 common/src/main/java/com/dfsek/terra/api/command/exception/MalformedCommandException.java create mode 100644 common/src/test/java/command/CommandTest.java diff --git a/common/src/main/java/com/dfsek/terra/api/command/CommandManager.java b/common/src/main/java/com/dfsek/terra/api/command/CommandManager.java index 3f5bae579..8caa03072 100644 --- a/common/src/main/java/com/dfsek/terra/api/command/CommandManager.java +++ b/common/src/main/java/com/dfsek/terra/api/command/CommandManager.java @@ -1,9 +1,11 @@ package com.dfsek.terra.api.command; +import com.dfsek.terra.api.command.exception.CommandException; + import java.util.List; public interface CommandManager { - void execute(String command, List args); + void execute(String command, List args) throws CommandException; void register(String name, Class clazz); } diff --git a/common/src/main/java/com/dfsek/terra/api/command/TerraCommandManager.java b/common/src/main/java/com/dfsek/terra/api/command/TerraCommandManager.java index 6c7ebfe77..18a13de68 100644 --- a/common/src/main/java/com/dfsek/terra/api/command/TerraCommandManager.java +++ b/common/src/main/java/com/dfsek/terra/api/command/TerraCommandManager.java @@ -3,19 +3,24 @@ package com.dfsek.terra.api.command; import com.dfsek.terra.api.command.annotation.Argument; import com.dfsek.terra.api.command.annotation.Command; import com.dfsek.terra.api.command.annotation.Subcommand; +import com.dfsek.terra.api.command.exception.CommandException; +import com.dfsek.terra.api.command.exception.InvalidArgumentsException; +import com.dfsek.terra.api.command.exception.MalformedCommandException; import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; public class TerraCommandManager implements CommandManager { - Map commands = new HashMap<>(); + private final Map commands = new HashMap<>(); @Override - public void execute(String commandName, List args) { + public void execute(String commandName, List argsIn) throws CommandException { + List args = new ArrayList<>(argsIn); ExecutionState state = new ExecutionState(); CommandHolder commandHolder = commands.get(commandName); @@ -29,34 +34,45 @@ public class TerraCommandManager implements CommandManager { if(command.arguments().length == 0 && command.subcommands().length == 0) { if(args.isEmpty()) invoke(commandClass, state); - else throw new IllegalArgumentException("Expected 0 arguments, found " + args.size()); + else throw new InvalidArgumentsException("Expected 0 arguments, found " + args.size()); } if(commandHolder.subcommands.containsKey(args.get(0))) { - invoke(commandClass, state); + invoke(commandHolder.subcommands.get(args.get(0)).clazz(), state); } boolean req = true; for(Argument argument : command.arguments()) { if(!req && argument.required()) - throw new IllegalStateException("Required arguments must come first! Arguments: " + Arrays.toString(command.arguments())); + throw new MalformedCommandException("Required arguments must come first! Arguments: " + Arrays.toString(command.arguments())); req = argument.required(); + if(args.isEmpty()) { + if(req) throw new InvalidArgumentsException("Invalid arguments: " + command.usage()); + break; + } + String arg = args.get(0); if(arg.startsWith("-")) { // flags have started. - + if(req) throw new InvalidArgumentsException("Flags must come after arguments."); + break; } + state.addArgument(argument.value(), args.remove(0)); } + + + invoke(commandClass, state); } - private void invoke(Class clazz, ExecutionState state) { + private void invoke(Class clazz, ExecutionState state) throws MalformedCommandException { try { + System.out.println("invocation"); clazz.getConstructor().newInstance().execute(state); } catch(InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { - e.printStackTrace(); + throw new MalformedCommandException("Unable to reflectively instantiate command: ", e); } } diff --git a/common/src/main/java/com/dfsek/terra/api/command/annotation/Command.java b/common/src/main/java/com/dfsek/terra/api/command/annotation/Command.java index 5eebc1ec3..01cd11e97 100644 --- a/common/src/main/java/com/dfsek/terra/api/command/annotation/Command.java +++ b/common/src/main/java/com/dfsek/terra/api/command/annotation/Command.java @@ -13,4 +13,6 @@ public @interface Command { Flag[] flags() default {}; Subcommand[] subcommands() default {}; + + String usage() default ""; } diff --git a/common/src/main/java/com/dfsek/terra/api/command/exception/CommandException.java b/common/src/main/java/com/dfsek/terra/api/command/exception/CommandException.java new file mode 100644 index 000000000..3fd6537ef --- /dev/null +++ b/common/src/main/java/com/dfsek/terra/api/command/exception/CommandException.java @@ -0,0 +1,13 @@ +package com.dfsek.terra.api.command.exception; + +public abstract class CommandException extends Exception { + private static final long serialVersionUID = -2955328495045879822L; + + public CommandException(String message) { + super(message); + } + + public CommandException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/common/src/main/java/com/dfsek/terra/api/command/exception/InvalidArgumentsException.java b/common/src/main/java/com/dfsek/terra/api/command/exception/InvalidArgumentsException.java new file mode 100644 index 000000000..5ad88d0b3 --- /dev/null +++ b/common/src/main/java/com/dfsek/terra/api/command/exception/InvalidArgumentsException.java @@ -0,0 +1,13 @@ +package com.dfsek.terra.api.command.exception; + +public class InvalidArgumentsException extends CommandException { + private static final long serialVersionUID = 7563619667472569824L; + + public InvalidArgumentsException(String message) { + super(message); + } + + public InvalidArgumentsException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/common/src/main/java/com/dfsek/terra/api/command/exception/MalformedCommandException.java b/common/src/main/java/com/dfsek/terra/api/command/exception/MalformedCommandException.java new file mode 100644 index 000000000..c124a8b85 --- /dev/null +++ b/common/src/main/java/com/dfsek/terra/api/command/exception/MalformedCommandException.java @@ -0,0 +1,16 @@ +package com.dfsek.terra.api.command.exception; + +/** + * Thrown when command is incorrectly defined. + */ +public class MalformedCommandException extends CommandException { + private static final long serialVersionUID = -5417760860407895496L; + + public MalformedCommandException(String message) { + super(message); + } + + public MalformedCommandException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/common/src/main/java/com/dfsek/terra/commands/structure/StructureExportCommand.java b/common/src/main/java/com/dfsek/terra/commands/structure/StructureExportCommand.java index 8d36c6c56..fa368668b 100644 --- a/common/src/main/java/com/dfsek/terra/commands/structure/StructureExportCommand.java +++ b/common/src/main/java/com/dfsek/terra/commands/structure/StructureExportCommand.java @@ -8,6 +8,6 @@ import com.dfsek.terra.api.command.annotation.Command; public class StructureExportCommand implements CommandTemplate { @Override public void execute(ExecutionState state) { - + System.out.println("export command"); } } diff --git a/common/src/test/java/command/CommandTest.java b/common/src/test/java/command/CommandTest.java new file mode 100644 index 000000000..41f37d22f --- /dev/null +++ b/common/src/test/java/command/CommandTest.java @@ -0,0 +1,79 @@ +package command; + +import com.dfsek.terra.api.command.CommandManager; +import com.dfsek.terra.api.command.CommandTemplate; +import com.dfsek.terra.api.command.ExecutionState; +import com.dfsek.terra.api.command.TerraCommandManager; +import com.dfsek.terra.api.command.annotation.Argument; +import com.dfsek.terra.api.command.annotation.Command; +import com.dfsek.terra.api.command.exception.CommandException; +import com.dfsek.terra.api.command.exception.MalformedCommandException; +import com.dfsek.terra.commands.StructureCommand; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; + +import static org.junit.jupiter.api.Assertions.fail; + +public class CommandTest { + @Test + public void subcommand() throws CommandException { + CommandManager manager = new TerraCommandManager(); + manager.register("structure", StructureCommand.class); + + manager.execute("structure", Arrays.asList("export")); + } + + @Test + public void args() throws CommandException { + CommandManager manager = new TerraCommandManager(); + manager.register("test", DemoCommand.class); + + manager.execute("test", Arrays.asList("first", "2")); + manager.execute("test", Arrays.asList("first", "2", "3.4")); + } + + @Test + public void requiredArgsFirst() throws CommandException { + CommandManager manager = new TerraCommandManager(); + manager.register("test", DemoInvalidCommand.class); + + try { + manager.execute("test", Arrays.asList("first", "2")); + fail(); + } catch(MalformedCommandException ignore) { + } + } + + @Command(arguments = { + @Argument(value = "arg0"), + @Argument(value = "arg1", type = int.class), + @Argument(value = "arg2", type = double.class, required = false) + }) + public static final class DemoCommand implements CommandTemplate { + + @Override + public void execute(ExecutionState state) { + System.out.println(state.getArgument("arg0", String.class)); + System.out.println(state.getArgument("arg1", String.class)); + try { + System.out.println(state.getArgument("arg2", String.class)); + } catch(IllegalArgumentException e) { + System.out.println("arg2 undefined."); + } + } + } + + @Command(arguments = { + @Argument(value = "arg0"), + @Argument(value = "arg2", type = double.class, required = false), // optional arguments must be last. this command is invalid. + @Argument(value = "arg1", type = int.class) + }) + public static final class DemoInvalidCommand implements CommandTemplate { + + @Override + public void execute(ExecutionState state) { + throw new Error("this should never be reached"); + } + } +}