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);
}