Skip to content

Commit

Permalink
Made channels optional (by default, they are not required); cleaned up
Browse files Browse the repository at this point in the history
some documentation,
  • Loading branch information
tsunko committed Oct 3, 2018
1 parent 2bdc431 commit 18c9e1e
Show file tree
Hide file tree
Showing 10 changed files with 95 additions and 30 deletions.
25 changes: 24 additions & 1 deletion src/main/java/academy/hekiyou/flow/Faucet.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
*/
Expand Down Expand Up @@ -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 <code>process(String command, Invoker invoker, Channel chan, Flow flow)</code> that does
* not require a channel to be supplied. This is the same as calling:
* <code>process(command, invoker, NullChannel.NULL, flow)</code>
* @param command The command name to invoke
* @param invoker An instance/implementation of Invoker
* @param flow An instance/implementation of Flow
* @return <code>true</code> if the command was found and processed successfully, <code>false</code> 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;
Expand Down Expand Up @@ -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.
Expand All @@ -116,6 +137,8 @@ public class Settings {
*/
public String invalidSubcommandError = "Invalid subcommand. Subcommands are: ";

private Settings(){}

}

}
9 changes: 8 additions & 1 deletion src/main/java/academy/hekiyou/flow/FlowCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

/**
* Tags a method as being a "command method". Methods tagged as "FlowCommand" are loaded
* via <code>Gunvarrel.load(TenkorePlugin plugin, Class klass)</code>.
* via <code>Faucet.loadClass(Class klass)</code>.
*/
@Retention(value = RetentionPolicy.RUNTIME)
@Target(value = ElementType.METHOD)
Expand All @@ -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 <b>not</b> contain a Channel (or subclass of) parameter.
* @return Weather or not the command requires command/chat channels support.
*/
boolean requiresChannelSupport() default false;

}
5 changes: 5 additions & 0 deletions src/main/java/academy/hekiyou/flow/env/NullChannel.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<Void> {

public static final NullChannel NULL = new NullChannel();

@Override
public String getName() {
return "";
Expand Down
27 changes: 17 additions & 10 deletions src/main/java/academy/hekiyou/flow/gunvarrel/Command.java
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}

Expand All @@ -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();

Expand All @@ -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;
}

}
18 changes: 12 additions & 6 deletions src/main/java/academy/hekiyou/flow/gunvarrel/Gunvarrel.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public <T> Optional<T> loadClass(Class<T> 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() + ")");
}
Expand Down Expand Up @@ -95,11 +95,17 @@ public List<String> 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]));
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
14 changes: 11 additions & 3 deletions src/main/java/academy/hekiyou/flow/gunvarrel/SplitCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,17 @@ public SplitCommand(String name, Object ref, Method parent, Map<String, Method>

@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();
}
Expand All @@ -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();
}
Expand Down
2 changes: 1 addition & 1 deletion test/academy/hekiyou/flow/test/FlowTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
14 changes: 12 additions & 2 deletions test/academy/hekiyou/flow/test/mock/TestCommandClass.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand All @@ -25,11 +25,21 @@ public void test(Invoker invoker, Channel channel, Flow flow){
description = "A test command",
usage = {"/testWithParams", "<int>", "<char>"}
)
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", "<int>", "<char>"},
requiresChannelSupport = true
)
public void testWithChannel(Invoker invoker, Channel<Invoker> channel, Flow flow){
invoker.sendMessage("%s", TEST_MESSAGE);
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -19,17 +18,17 @@ public class TestCommandClassWithSubcommands {
usage = {"root", "<child1,child2,child3>"}
)
@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);
}

Expand Down

0 comments on commit 18c9e1e

Please sign in to comment.