From 18c9e1e7ad8cc70562f497cd07b5c511c99e3ee4 Mon Sep 17 00:00:00 2001 From: tsunko <22252467+tsunko@users.noreply.github.com> Date: Tue, 2 Oct 2018 22:04:08 -0700 Subject: [PATCH] Made channels optional (by default, they are not required); cleaned up some documentation, --- .../java/academy/hekiyou/flow/Faucet.java | 25 ++++++++++++++++- .../academy/hekiyou/flow/FlowCommand.java | 9 ++++++- .../academy/hekiyou/flow/env/NullChannel.java | 5 ++++ .../hekiyou/flow/gunvarrel/Command.java | 27 ++++++++++++------- .../hekiyou/flow/gunvarrel/Gunvarrel.java | 18 ++++++++----- .../flow/gunvarrel/SimpleRegisterer.java | 2 +- .../hekiyou/flow/gunvarrel/SplitCommand.java | 14 +++++++--- test/academy/hekiyou/flow/test/FlowTest.java | 2 +- .../flow/test/mock/TestCommandClass.java | 14 ++++++++-- .../mock/TestCommandClassWithSubcommands.java | 9 +++---- 10 files changed, 95 insertions(+), 30 deletions(-) diff --git a/src/main/java/academy/hekiyou/flow/Faucet.java b/src/main/java/academy/hekiyou/flow/Faucet.java index ecb8b7e..63f1ce2 100644 --- a/src/main/java/academy/hekiyou/flow/Faucet.java +++ b/src/main/java/academy/hekiyou/flow/Faucet.java @@ -2,6 +2,7 @@ import academy.hekiyou.flow.env.Channel; import academy.hekiyou.flow.env.Invoker; +import academy.hekiyou.flow.env.NullChannel; import academy.hekiyou.flow.gunvarrel.Gunvarrel; import academy.hekiyou.flow.gunvarrel.Registerer; import academy.hekiyou.flow.gunvarrel.SimpleRegisterer; @@ -32,6 +33,13 @@ public static void initialize(Registerer registerer){ instance.loader = new Gunvarrel(registerer); } + /** + * Initializes the Flow library, passing null as the registerer. + */ + public static void initialize(){ + initialize(null); + } + /** * Uninitializes the current setup, if any. */ @@ -75,6 +83,19 @@ public static boolean process(String command, Invoker invoker, Channel chan, Flo return instance.loader.findAndExecute(command, invoker, chan, flow); } + /** + * A variant of the process(String command, Invoker invoker, Channel chan, Flow flow) that does + * not require a channel to be supplied. This is the same as calling: + * process(command, invoker, NullChannel.NULL, flow) + * @param command The command name to invoke + * @param invoker An instance/implementation of Invoker + * @param flow An instance/implementation of Flow + * @return true if the command was found and processed successfully, false otherwise + */ + public static boolean process(String command, Invoker invoker, Flow flow){ + return process(command, invoker, NullChannel.NULL, flow); + } + public static Settings getSettings(){ checkForInitialization(); return instance.settings; @@ -104,7 +125,7 @@ public class Settings { /** * Represents the message that is sent when a command errors during argument processing by bad usage. */ - public String usageError = "Error in executing command."; + public String usageError = "Usage error."; /** * Represents the message that is sent in case of a permission error. @@ -116,6 +137,8 @@ public class Settings { */ public String invalidSubcommandError = "Invalid subcommand. Subcommands are: "; + private Settings(){} + } } diff --git a/src/main/java/academy/hekiyou/flow/FlowCommand.java b/src/main/java/academy/hekiyou/flow/FlowCommand.java index 233f799..518e3ca 100644 --- a/src/main/java/academy/hekiyou/flow/FlowCommand.java +++ b/src/main/java/academy/hekiyou/flow/FlowCommand.java @@ -7,7 +7,7 @@ /** * Tags a method as being a "command method". Methods tagged as "FlowCommand" are loaded - * via Gunvarrel.load(TenkorePlugin plugin, Class klass). + * via Faucet.loadClass(Class klass). */ @Retention(value = RetentionPolicy.RUNTIME) @Target(value = ElementType.METHOD) @@ -33,4 +33,11 @@ */ String[] alias() default {}; + /** + * If set to true, the command function is expected to contain a Channel (or subclass of) parameter. + * If false (default), the command is expected to not contain a Channel (or subclass of) parameter. + * @return Weather or not the command requires command/chat channels support. + */ + boolean requiresChannelSupport() default false; + } diff --git a/src/main/java/academy/hekiyou/flow/env/NullChannel.java b/src/main/java/academy/hekiyou/flow/env/NullChannel.java index 7218b27..a957310 100644 --- a/src/main/java/academy/hekiyou/flow/env/NullChannel.java +++ b/src/main/java/academy/hekiyou/flow/env/NullChannel.java @@ -3,8 +3,13 @@ import java.util.stream.Stream; +/** + * Represents a Channel instance for any command that does not utilize channels. + */ public class NullChannel implements Channel { + public static final NullChannel NULL = new NullChannel(); + @Override public String getName() { return ""; diff --git a/src/main/java/academy/hekiyou/flow/gunvarrel/Command.java b/src/main/java/academy/hekiyou/flow/gunvarrel/Command.java index b8329d4..767d152 100644 --- a/src/main/java/academy/hekiyou/flow/gunvarrel/Command.java +++ b/src/main/java/academy/hekiyou/flow/gunvarrel/Command.java @@ -27,17 +27,23 @@ public Command(String name, Object ref, Method method, FlowCommand metadata){ } public void execute(Invoker invoker, Channel chan, Flow flow){ - if(!checkPermission(invoker)) return; + if(!invoker.hasPermission(metadata.permission())){ + invoker.sendMessage(Faucet.getSettings().permissionError); + return; + } try { - method.invoke(ref, invoker, chan, flow); + // check if the function actually needs channels to be passed to it; drop it here if it doesn't + // (to match function parameters) + invokeMethodWithArguments(method, invoker, chan, flow); } catch (IllegalAccessException | InvocationTargetException e){ if(e.getCause() instanceof FlowException || e.getCause() instanceof FlowEmptyException){ invoker.sendMessage(Faucet.getSettings().usageError); invoker.sendMessage(formatError(metadata.usage(), flow.index())); - return; + } else { + // non-expected exception occurred; print it out! + e.printStackTrace(); } - e.printStackTrace(); } } @@ -49,7 +55,7 @@ public FlowCommand getMetadata(){ return metadata; } - String formatError(String[] usage, int errorIndex){ + private String formatError(String[] usage, int errorIndex){ StringBuilder builder = new StringBuilder(); Faucet.Settings settings = Faucet.getSettings(); @@ -68,12 +74,13 @@ String formatError(String[] usage, int errorIndex){ return builder.toString(); } - boolean checkPermission(Invoker invoker){ - if(!invoker.hasPermission(metadata.permission())){ - invoker.sendMessage(Faucet.getSettings().permissionError); - return false; + void invokeMethodWithArguments(Method method, Invoker invoker, Channel chan, Flow flow) + throws IllegalAccessException, InvocationTargetException { + if(metadata.requiresChannelSupport()){ + method.invoke(ref, invoker, chan, flow); + } else { + method.invoke(ref, invoker, flow); } - return true; } } diff --git a/src/main/java/academy/hekiyou/flow/gunvarrel/Gunvarrel.java b/src/main/java/academy/hekiyou/flow/gunvarrel/Gunvarrel.java index dbe817c..ae9cd5f 100644 --- a/src/main/java/academy/hekiyou/flow/gunvarrel/Gunvarrel.java +++ b/src/main/java/academy/hekiyou/flow/gunvarrel/Gunvarrel.java @@ -44,7 +44,7 @@ public Optional loadClass(Class klass) { FlowCommand meta = method.getAnnotation(FlowCommand.class); String cmdName = method.getName(); - if(!isValidMethodParams(method.getParameterTypes())){ + if(!isValidMethodParams(method.getParameterTypes(), meta.requiresChannelSupport())){ throw new IllegalArgumentException("Badly formatted method: " + cmdName + " (" + method.getDeclaringClass().getName() + ")"); } @@ -95,11 +95,17 @@ public List unloadClass(Class klass) { return Stream.of(toUnreg).map(Command::getName).collect(Collectors.toList()); } - private final boolean isValidMethodParams(Class[] params){ - return params.length == 3 && - Invoker.class.isAssignableFrom(params[0]) && - Channel.class.isAssignableFrom(params[1]) && - Flow.class.isAssignableFrom(params[2]) ; + private boolean isValidMethodParams(Class[] params, boolean needsChannelArg){ + if(needsChannelArg){ + return (params.length == 3 && + Invoker.class.isAssignableFrom(params[0]) && + Channel.class.isAssignableFrom(params[1]) && + Flow.class.isAssignableFrom(params[2])); + } else { + return (params.length == 2 && + Invoker.class.isAssignableFrom(params[0]) && + Flow.class.isAssignableFrom(params[1])); + } } } diff --git a/src/main/java/academy/hekiyou/flow/gunvarrel/SimpleRegisterer.java b/src/main/java/academy/hekiyou/flow/gunvarrel/SimpleRegisterer.java index 8fb8674..6b24322 100644 --- a/src/main/java/academy/hekiyou/flow/gunvarrel/SimpleRegisterer.java +++ b/src/main/java/academy/hekiyou/flow/gunvarrel/SimpleRegisterer.java @@ -15,7 +15,7 @@ public class SimpleRegisterer implements Registerer { @Override public void register(Command command) { if(isRegistered(command.getName())){ - throw new IllegalStateException("already registered"); + throw new IllegalStateException("already registered " + command.getName()); } registered.put(command.getName(), command); diff --git a/src/main/java/academy/hekiyou/flow/gunvarrel/SplitCommand.java b/src/main/java/academy/hekiyou/flow/gunvarrel/SplitCommand.java index 9f303a1..e245451 100644 --- a/src/main/java/academy/hekiyou/flow/gunvarrel/SplitCommand.java +++ b/src/main/java/academy/hekiyou/flow/gunvarrel/SplitCommand.java @@ -25,10 +25,17 @@ public SplitCommand(String name, Object ref, Method parent, Map @Override public void execute(Invoker invoker, Channel channel, Flow flow){ - if(!checkPermission(invoker)) return; + if(!invoker.hasPermission(getMetadata().permission())){ + invoker.sendMessage(Faucet.getSettings().permissionError); + return; + } try { - method.invoke(ref, invoker, channel, flow); // apply any changes the parent method to the invoker and flow (bad) + // apply any changes the parent method to the invoker and flow + // technically, this is probably bad design as the root command probably shouldn't modify the invoker + // however, in practice, it feels like the root command may need to perform prep with the invoker first + // and then its subcommand can take over + invokeMethodWithArguments(method, invoker, channel, flow); } catch (IllegalAccessException | InvocationTargetException e){ e.printStackTrace(); } @@ -41,7 +48,8 @@ public void execute(Invoker invoker, Channel channel, Flow flow){ } try { - subMethod.invoke(ref, invoker, channel, flow); // now pass the maybe-modified invoker/flow to the sub-command method + // now pass the maybe-modified invoker/flow to the sub-command method + invokeMethodWithArguments(subMethod, invoker, channel, flow); } catch (IllegalAccessException | InvocationTargetException e){ e.printStackTrace(); } diff --git a/test/academy/hekiyou/flow/test/FlowTest.java b/test/academy/hekiyou/flow/test/FlowTest.java index c2236af..3110c92 100644 --- a/test/academy/hekiyou/flow/test/FlowTest.java +++ b/test/academy/hekiyou/flow/test/FlowTest.java @@ -108,7 +108,7 @@ public void testBadFlowArguments(){ Faucet.process("testWithParams", mockInvokers[0], mockChannel, new StringFlow(new String[]{"notAnInt longerThan1Character"})); String failMessageRecv = ((TestInvoker)mockInvokers[0]).consumeMessage(); Assert.assertNotNull(failMessageRecv); - Assert.assertEquals("Oops! You've made a mistake here:", failMessageRecv); + Assert.assertEquals(Faucet.getSettings().usageError, failMessageRecv); } @Test diff --git a/test/academy/hekiyou/flow/test/mock/TestCommandClass.java b/test/academy/hekiyou/flow/test/mock/TestCommandClass.java index 4d390f2..321c68c 100644 --- a/test/academy/hekiyou/flow/test/mock/TestCommandClass.java +++ b/test/academy/hekiyou/flow/test/mock/TestCommandClass.java @@ -16,7 +16,7 @@ public class TestCommandClass { usage = "/test", alias = "tset" // just "test" backwards ) - public void test(Invoker invoker, Channel channel, Flow flow){ + public void test(Invoker invoker, Flow flow){ invoker.sendMessage("%s", TEST_MESSAGE); } @@ -25,11 +25,21 @@ public void test(Invoker invoker, Channel channel, Flow flow){ description = "A test command", usage = {"/testWithParams", "", ""} ) - public void testWithParams(Invoker invoker, Channel channel, Flow flow){ + public void testWithParams(Invoker invoker, Flow flow){ int arg1 = flow.next(int.class); char arg2 = flow.next(char.class); invoker.sendMessage(arg1 + "" + arg2); } + @FlowCommand( + permission = "test-pass-permission", + description = "A test command", + usage = {"/testWithParams", "", ""}, + requiresChannelSupport = true + ) + public void testWithChannel(Invoker invoker, Channel channel, Flow flow){ + invoker.sendMessage("%s", TEST_MESSAGE); + } + } diff --git a/test/academy/hekiyou/flow/test/mock/TestCommandClassWithSubcommands.java b/test/academy/hekiyou/flow/test/mock/TestCommandClassWithSubcommands.java index d16e34e..e2f4e91 100644 --- a/test/academy/hekiyou/flow/test/mock/TestCommandClassWithSubcommands.java +++ b/test/academy/hekiyou/flow/test/mock/TestCommandClassWithSubcommands.java @@ -3,7 +3,6 @@ import academy.hekiyou.flow.Flow; import academy.hekiyou.flow.FlowCommand; import academy.hekiyou.flow.FlowSplitCommand; -import academy.hekiyou.flow.env.Channel; import academy.hekiyou.flow.env.Invoker; import academy.hekiyou.flow.test.FlowTest; @@ -19,17 +18,17 @@ public class TestCommandClassWithSubcommands { usage = {"root", ""} ) @FlowSplitCommand - public void root(Invoker invoker, Channel channel, Flow flow){} + public void root(Invoker invoker, Flow flow){} - public void root$child1(Invoker invoker, Channel channel, Flow flow){ + public void root$child1(Invoker invoker, Flow flow){ invoker.sendMessage(TEST_MESSAGE_1); } - public void root$child2(Invoker invoker, Channel channel, Flow flow){ + public void root$child2(Invoker invoker, Flow flow){ invoker.sendMessage(TEST_MESSAGE_2); } - public void root$child3(Invoker invoker, Channel channel, Flow flow){ + public void root$child3(Invoker invoker, Flow flow){ invoker.sendMessage(TEST_MESSAGE_3); }