diff --git a/Essentials/src/main/java/com/earth2me/essentials/commands/EssentialsCommandNode.java b/Essentials/src/main/java/com/earth2me/essentials/commands/EssentialsCommandNode.java index bad7e4ad5..be838704a 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/commands/EssentialsCommandNode.java +++ b/Essentials/src/main/java/com/earth2me/essentials/commands/EssentialsCommandNode.java @@ -5,7 +5,10 @@ import org.bukkit.Server; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; import java.util.List; +import java.util.Locale; public abstract class EssentialsCommandNode { private final ArrayList> childNodes = new ArrayList<>(); @@ -37,6 +40,10 @@ public abstract class EssentialsCommandNode { throw new NoChargeException(); } + protected List> getChildNodes() { + return Collections.unmodifiableList(childNodes); + } + public abstract boolean matches(final WalkContext context); public static Root root(final Initializer initializer) { @@ -54,8 +61,8 @@ public abstract class EssentialsCommandNode { this.node = node; } - public void literal(final String name, final Initializer initializer) { - node.childNodes.add(new Literal<>(name, initializer)); + public void literal(final String name, final Initializer initializer, final String... aliases) { + node.childNodes.add(new Literal<>(name, aliases, initializer)); } public void execute(final RunHandler runHandler) { @@ -112,6 +119,9 @@ public abstract class EssentialsCommandNode { public static class Root extends EssentialsCommandNode { protected Root(Initializer initializer) { super(initializer); + if (getChildNodes().isEmpty()) { + throw new RuntimeException("Root nodes must be initialised with at least one child"); + } } @Override @@ -130,14 +140,24 @@ public abstract class EssentialsCommandNode { public static class Literal extends EssentialsCommandNode { private final String name; + private final HashSet aliases; - protected Literal(String name, Initializer initializer) { + protected Literal(String name, String[] aliases, Initializer initializer) { super(initializer); + if (getChildNodes().isEmpty()) { + throw new RuntimeException("Literal nodes must be initialised with at least one child (node name: " + name + ")"); + } + this.name = name; + this.aliases = new HashSet<>(); + this.aliases.add(name.toLowerCase(Locale.ROOT)); + for (final String alias : aliases) { + this.aliases.add(alias.toLowerCase(Locale.ROOT)); + } } public boolean matches(WalkContext context) { - return context.args.length > 0 && context.args[0].equalsIgnoreCase(name); + return context.args.length > 0 && aliases.contains(context.args[0].toLowerCase(Locale.ROOT)); } @Override diff --git a/Essentials/src/test/java/com/earth2me/essentials/commands/EssentialsCommandNodeTest.java b/Essentials/src/test/java/com/earth2me/essentials/commands/EssentialsCommandNodeTest.java index 392340c09..38d01ae52 100644 --- a/Essentials/src/test/java/com/earth2me/essentials/commands/EssentialsCommandNodeTest.java +++ b/Essentials/src/test/java/com/earth2me/essentials/commands/EssentialsCommandNodeTest.java @@ -6,13 +6,13 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.function.Executable; import org.mockito.InOrder; -import org.mockito.Mockito; import java.util.Arrays; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; class EssentialsCommandNodeTest { private FakeServer fakeServer; @@ -27,21 +27,18 @@ class EssentialsCommandNodeTest { consoleSource = mock(CommandSource.class); } - @Test - void testNonTerminateThrow() { - final EssentialsCommandNode.Root rootNode = EssentialsCommandNode.root(root -> { - root.literal("hello", hello -> { - hello.execute(ctx -> { - if (ctx.args().length < 1) { - ctx.sender().sendMessage("hello to who?"); - } else if (ctx.args().length < 2) { - ctx.sender().sendMessage("hi there " + ctx.args()[0]); - } else { - ctx.sender().sendMessage("woah hi " + String.join(" and ", ctx.args())); - } - System.out.println(Arrays.toString(ctx.args())); - }); - }); + EssentialsCommandNode.Root buildCommonTree() { + return EssentialsCommandNode.root(root -> { + root.literal("hello", hello -> hello.execute(ctx -> { + if (ctx.args().length < 1) { + ctx.sender().sendMessage("hello to who?"); + } else if (ctx.args().length < 2) { + ctx.sender().sendMessage("hi there " + ctx.args()[0]); + } else { + ctx.sender().sendMessage("woah hi " + String.join(" and ", ctx.args())); + } + System.out.println(Arrays.toString(ctx.args())); + })); root.literal("bye", bye -> { bye.literal("forever just kidding", bye1 -> { bye1.execute(ctx -> { throw new RuntimeException("this shouldn't happen"); }); }); bye.literal("forever", bye2 -> bye2.execute(ctx -> ctx.sender().sendMessage(":(("))); @@ -53,8 +50,23 @@ class EssentialsCommandNodeTest { ctx.sender().sendMessage("wait you can't leave"); } }); - }); + }, "farewell", "tschuss"); }); + } + + @Test + void testBuild() { + assertThrows(RuntimeException.class, () -> EssentialsCommandNode.root(root -> {}), "empty root"); + assertThrows(RuntimeException.class, () -> EssentialsCommandNode.root(root -> { + root.literal("potato", potato -> {}); + }), "empty literal"); + + assertDoesNotThrow(this::buildCommonTree, "build complete tree"); + } + + @Test + void testEval() { + final EssentialsCommandNode.Root rootNode = buildCommonTree(); assertThrows(NoChargeException.class, () -> rootNode.run(fakeServer, playerSource, "test", new String[]{""}), "wrongly parsed empty arg"); assertThrows(NoChargeException.class, () -> rootNode.run(fakeServer, playerSource, "test", new String[]{"wilkommen"}), "wrongly parsed unknown literal"); // wrongly parsed German @@ -63,17 +75,17 @@ class EssentialsCommandNodeTest { Executable playerHelloOneArg = () -> rootNode.run(fakeServer, playerSource, "test", new String[]{"hello", "world"}); Executable playerHelloManyArgs = () -> rootNode.run(fakeServer, playerSource, "test", new String[]{"hello", "jroy", "pop", "lax", "evident"}); Executable playerBye = () -> rootNode.run(fakeServer, playerSource, "test", new String[]{"bye", "legacy", "code"}); - Executable consoleBye = () -> rootNode.run(fakeServer, consoleSource, "test", new String[]{"bye", "player", "data"}); + Executable consoleFarewell = () -> rootNode.run(fakeServer, consoleSource, "test", new String[]{"fAREWELL", "player", "data"}); Executable consoleByeForeverJk = () -> rootNode.run(fakeServer, consoleSource, "test", new String[]{"bye", "forever", "just", "kidding"}); assertDoesNotThrow(playerHelloNoArgs, "parsing first level no-arg command"); assertDoesNotThrow(playerHelloOneArg, "parsing first level 1 arg command"); assertDoesNotThrow(playerHelloManyArgs, "parsing first level multi-arg command"); assertDoesNotThrow(playerBye); - assertDoesNotThrow(consoleBye); + assertDoesNotThrow(consoleFarewell, "parsing with literal alias"); assertDoesNotThrow(consoleByeForeverJk); - InOrder ordered = Mockito.inOrder(playerSource, consoleSource); + InOrder ordered = inOrder(playerSource, consoleSource); ordered.verify(playerSource).sendMessage("hello to who?"); ordered.verify(playerSource).sendMessage("hi there world"); ordered.verify(playerSource).sendMessage("woah hi jroy and pop and lax and evident");