From b0f69ebc47d6b60a41f4f62ae180a6c8cb6bd73f Mon Sep 17 00:00:00 2001 From: Nathan Adams Date: Thu, 10 Aug 2017 14:32:01 +0200 Subject: [PATCH] Allow redirects to modify context, supporting forks in the command chain --- .../mojang/brigadier/CommandDispatcher.java | 55 ++++++++++++++---- .../brigadier/builder/ArgumentBuilder.java | 10 +++- .../builder/LiteralArgumentBuilder.java | 2 +- .../builder/RequiredArgumentBuilder.java | 2 +- .../brigadier/context/CommandContext.java | 14 ++--- .../context/CommandContextBuilder.java | 22 +++---- .../brigadier/tree/ArgumentCommandNode.java | 8 ++- .../mojang/brigadier/tree/CommandNode.java | 9 ++- .../brigadier/tree/LiteralCommandNode.java | 8 ++- .../brigadier/tree/RootCommandNode.java | 4 +- .../CommandDispatcherCompletionsTest.java | 4 +- .../brigadier/CommandDispatcherTest.java | 57 +++++++++++++++---- .../CommandDispatcherUsagesTest.java | 5 +- .../builder/ArgumentBuilderTest.java | 8 ++- 14 files changed, 151 insertions(+), 57 deletions(-) diff --git a/src/main/java/com/mojang/brigadier/CommandDispatcher.java b/src/main/java/com/mojang/brigadier/CommandDispatcher.java index 22b5699..7286063 100644 --- a/src/main/java/com/mojang/brigadier/CommandDispatcher.java +++ b/src/main/java/com/mojang/brigadier/CommandDispatcher.java @@ -4,7 +4,6 @@ import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import com.mojang.brigadier.builder.LiteralArgumentBuilder; -import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.context.CommandContextBuilder; import com.mojang.brigadier.exceptions.CommandException; import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; @@ -12,10 +11,13 @@ import com.mojang.brigadier.tree.CommandNode; import com.mojang.brigadier.tree.LiteralCommandNode; import com.mojang.brigadier.tree.RootCommandNode; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collection; +import java.util.Deque; import java.util.Map; import java.util.Set; +import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -69,20 +71,41 @@ public class CommandDispatcher { throw ERROR_UNKNOWN_ARGUMENT.createWithContext(parse.getReader()); } } - final CommandContext context = parse.getContext().build(); - final Command command = context.getCommand(); - if (command == null) { + + int result = 0; + boolean foundCommand = false; + final Deque> contexts = new ArrayDeque<>(); + contexts.add(parse.getContext()); + + while (!contexts.isEmpty()) { + final CommandContextBuilder context = contexts.removeLast(); + if (context.getChild() != null) { + if (!context.getNodes().isEmpty()) { + final Function> modifier = context.getNodes().keySet().iterator().next().getRedirectModifier(); + for (final S source : modifier.apply(context.getSource())) { + contexts.add(context.getChild().copy().withSource(source)); + } + } + } else if (context.getCommand() != null) { + foundCommand = true; + result += context.getCommand().run(context.build()); + } + } + + if (!foundCommand) { throw ERROR_UNKNOWN_COMMAND.createWithContext(parse.getReader()); } - return command.run(context); + + return result; } public ParseResults parse(final String command, final S source) throws CommandException { final StringReader reader = new StringReader(command); - return parseNodes(root, reader, new CommandContextBuilder<>(this, source)); + final CommandContextBuilder context = new CommandContextBuilder<>(this, source); + return parseNodes(root, reader, context, context, null); } - private ParseResults parseNodes(final CommandNode node, final StringReader reader, final CommandContextBuilder contextBuilder) throws CommandException { + private ParseResults parseNodes(final CommandNode node, final StringReader reader, final CommandContextBuilder contextBuilder, CommandContextBuilder rootContext, final CommandContextBuilder parentContext) throws CommandException { final S source = contextBuilder.getSource(); final Map, CommandException> errors = Maps.newHashMap(); @@ -105,20 +128,30 @@ public class CommandDispatcher { continue; } + if (rootContext == contextBuilder) { + rootContext = context; + } + + if (parentContext != null) { + parentContext.withChild(context); + } + context.withCommand(child.getCommand()); if (reader.canRead()) { reader.skip(); if (child.getRedirect() != null) { - return parseNodes(child.getRedirect(), reader, context.redirect(child.getRedirect())); + final CommandContextBuilder childContext = new CommandContextBuilder<>(this, source); + childContext.withNode(child.getRedirect(), ""); + return parseNodes(child.getRedirect(), reader, childContext, rootContext, context); } else { - return parseNodes(child, reader, context); + return parseNodes(child, reader, context, rootContext, parentContext); } } else { - return new ParseResults<>(context); + return new ParseResults<>(rootContext); } } - return new ParseResults<>(contextBuilder, reader, errors); + return new ParseResults<>(rootContext, reader, errors); } public String[] getAllUsage(final CommandNode node, final S source) { diff --git a/src/main/java/com/mojang/brigadier/builder/ArgumentBuilder.java b/src/main/java/com/mojang/brigadier/builder/ArgumentBuilder.java index 0c67737..799c2c3 100644 --- a/src/main/java/com/mojang/brigadier/builder/ArgumentBuilder.java +++ b/src/main/java/com/mojang/brigadier/builder/ArgumentBuilder.java @@ -5,6 +5,8 @@ import com.mojang.brigadier.tree.CommandNode; import com.mojang.brigadier.tree.RootCommandNode; import java.util.Collection; +import java.util.Collections; +import java.util.function.Function; import java.util.function.Predicate; public abstract class ArgumentBuilder> { @@ -12,6 +14,7 @@ public abstract class ArgumentBuilder> { private Command command; private Predicate requirement = s -> true; private CommandNode target; + private Function> modifier = Collections::singleton; protected abstract T getThis(); @@ -45,11 +48,12 @@ public abstract class ArgumentBuilder> { return requirement; } - public T redirect(final CommandNode target) { + public T redirect(final CommandNode target, final Function> modifier) { if (!arguments.getChildren().isEmpty()) { throw new IllegalStateException("Cannot redirect a node with children"); } this.target = target; + this.modifier = modifier; return getThis(); } @@ -57,5 +61,9 @@ public abstract class ArgumentBuilder> { return target; } + public Function> getRedirectModifier() { + return modifier; + } + public abstract CommandNode build(); } diff --git a/src/main/java/com/mojang/brigadier/builder/LiteralArgumentBuilder.java b/src/main/java/com/mojang/brigadier/builder/LiteralArgumentBuilder.java index 8d14d46..ef47f8c 100644 --- a/src/main/java/com/mojang/brigadier/builder/LiteralArgumentBuilder.java +++ b/src/main/java/com/mojang/brigadier/builder/LiteralArgumentBuilder.java @@ -25,7 +25,7 @@ public class LiteralArgumentBuilder extends ArgumentBuilder build() { - final LiteralCommandNode result = new LiteralCommandNode<>(getLiteral(), getCommand(), getRequirement(), getRedirect()); + final LiteralCommandNode result = new LiteralCommandNode<>(getLiteral(), getCommand(), getRequirement(), getRedirect(), getRedirectModifier()); for (final CommandNode argument : getArguments()) { result.addChild(argument); diff --git a/src/main/java/com/mojang/brigadier/builder/RequiredArgumentBuilder.java b/src/main/java/com/mojang/brigadier/builder/RequiredArgumentBuilder.java index c9cb585..493c26e 100644 --- a/src/main/java/com/mojang/brigadier/builder/RequiredArgumentBuilder.java +++ b/src/main/java/com/mojang/brigadier/builder/RequiredArgumentBuilder.java @@ -31,7 +31,7 @@ public class RequiredArgumentBuilder extends ArgumentBuilder build() { - final ArgumentCommandNode result = new ArgumentCommandNode<>(getName(), getType(), getCommand(), getRequirement(), getRedirect()); + final ArgumentCommandNode result = new ArgumentCommandNode<>(getName(), getType(), getCommand(), getRequirement(), getRedirect(), getRedirectModifier()); for (final CommandNode argument : getArguments()) { result.addChild(argument); diff --git a/src/main/java/com/mojang/brigadier/context/CommandContext.java b/src/main/java/com/mojang/brigadier/context/CommandContext.java index c91dc46..f17b29a 100644 --- a/src/main/java/com/mojang/brigadier/context/CommandContext.java +++ b/src/main/java/com/mojang/brigadier/context/CommandContext.java @@ -13,19 +13,19 @@ public class CommandContext { private final Map> arguments; private final Map, String> nodes; private final String input; - private final CommandContext parent; + private final CommandContext child; - public CommandContext(final S source, final Map> arguments, final Command command, final Map, String> nodes, final String input, final CommandContext parent) { + public CommandContext(final S source, final Map> arguments, final Command command, final Map, String> nodes, final String input, final CommandContext child) { this.source = source; this.arguments = arguments; this.command = command; this.nodes = nodes; this.input = input; - this.parent = parent; + this.child = child; } - public CommandContext getParent() { - return parent; + public CommandContext getChild() { + return child; } public Command getCommand() { @@ -63,7 +63,7 @@ public class CommandContext { if (!Iterables.elementsEqual(nodes.entrySet(), that.nodes.entrySet())) return false; if (command != null ? !command.equals(that.command) : that.command != null) return false; if (!source.equals(that.source)) return false; - if (parent != null ? !parent.equals(that.parent) : that.parent != null) return false; + if (child != null ? !child.equals(that.child) : that.child != null) return false; return true; } @@ -74,7 +74,7 @@ public class CommandContext { result = 31 * result + arguments.hashCode(); result = 31 * result + (command != null ? command.hashCode() : 0); result = 31 * result + nodes.hashCode(); - result = 31 * result + (parent != null ? parent.hashCode() : 0); + result = 31 * result + (child != null ? child.hashCode() : 0); return result; } diff --git a/src/main/java/com/mojang/brigadier/context/CommandContextBuilder.java b/src/main/java/com/mojang/brigadier/context/CommandContextBuilder.java index 19e0ab6..a27c69b 100644 --- a/src/main/java/com/mojang/brigadier/context/CommandContextBuilder.java +++ b/src/main/java/com/mojang/brigadier/context/CommandContextBuilder.java @@ -13,7 +13,7 @@ public class CommandContextBuilder { private final CommandDispatcher dispatcher; private S source; private Command command; - private CommandContext parent; + private CommandContextBuilder child; public CommandContextBuilder(final CommandDispatcher dispatcher, final S source) { this.dispatcher = dispatcher; @@ -53,19 +53,21 @@ public class CommandContextBuilder { copy.command = command; copy.arguments.putAll(arguments); copy.nodes.putAll(nodes); - copy.parent = parent; + copy.child = child; return copy; } - public CommandContextBuilder redirect(final CommandNode newRoot) { - final CommandContextBuilder result = new CommandContextBuilder<>(dispatcher, source); - result.withNode(newRoot, ""); - result.parent = build(); - return result; + public CommandContextBuilder withChild(final CommandContextBuilder child) { + this.child = child; + return this; } - public CommandContext getParent() { - return parent; + public CommandContextBuilder getChild() { + return child; + } + + public Command getCommand() { + return command; } public String getInput() { @@ -89,7 +91,7 @@ public class CommandContextBuilder { } public CommandContext build() { - return new CommandContext<>(source, arguments, command, nodes, getInput(), parent); + return new CommandContext<>(source, arguments, command, nodes, getInput(), child == null ? null : child.build()); } public CommandDispatcher getDispatcher() { diff --git a/src/main/java/com/mojang/brigadier/tree/ArgumentCommandNode.java b/src/main/java/com/mojang/brigadier/tree/ArgumentCommandNode.java index 4d7c3c2..9574f0b 100644 --- a/src/main/java/com/mojang/brigadier/tree/ArgumentCommandNode.java +++ b/src/main/java/com/mojang/brigadier/tree/ArgumentCommandNode.java @@ -8,7 +8,9 @@ import com.mojang.brigadier.context.CommandContextBuilder; import com.mojang.brigadier.context.ParsedArgument; import com.mojang.brigadier.exceptions.CommandException; +import java.util.Collection; import java.util.Set; +import java.util.function.Function; import java.util.function.Predicate; public class ArgumentCommandNode extends CommandNode { @@ -18,8 +20,8 @@ public class ArgumentCommandNode extends CommandNode { private final String name; private final ArgumentType type; - public ArgumentCommandNode(final String name, final ArgumentType type, final Command command, final Predicate requirement, final CommandNode redirect) { - super(command, requirement, redirect); + public ArgumentCommandNode(final String name, final ArgumentType type, final Command command, final Predicate requirement, final CommandNode redirect, final Function> modifier) { + super(command, requirement, redirect, modifier); this.name = name; this.type = type; } @@ -69,7 +71,7 @@ public class ArgumentCommandNode extends CommandNode { public RequiredArgumentBuilder createBuilder() { final RequiredArgumentBuilder builder = RequiredArgumentBuilder.argument(name, type); builder.requires(getRequirement()); - builder.redirect(getRedirect()); + builder.redirect(getRedirect(), getRedirectModifier()); if (getCommand() != null) { builder.executes(getCommand()); } diff --git a/src/main/java/com/mojang/brigadier/tree/CommandNode.java b/src/main/java/com/mojang/brigadier/tree/CommandNode.java index db4f34c..dab7240 100644 --- a/src/main/java/com/mojang/brigadier/tree/CommandNode.java +++ b/src/main/java/com/mojang/brigadier/tree/CommandNode.java @@ -12,6 +12,7 @@ import java.util.Collection; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; +import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -19,12 +20,14 @@ public abstract class CommandNode implements Comparable> { private Map> children = Maps.newLinkedHashMap(); private final Predicate requirement; private final CommandNode redirect; + private final Function> modifier; private Command command; - protected CommandNode(final Command command, final Predicate requirement, final CommandNode redirect) { + protected CommandNode(final Command command, final Predicate requirement, final CommandNode redirect, final Function> modifier) { this.command = command; this.requirement = requirement; this.redirect = redirect; + this.modifier = modifier; } public Command getCommand() { @@ -39,6 +42,10 @@ public abstract class CommandNode implements Comparable> { return redirect; } + public Function> getRedirectModifier() { + return modifier; + } + public boolean canUse(final S source) { return requirement.test(source); } diff --git a/src/main/java/com/mojang/brigadier/tree/LiteralCommandNode.java b/src/main/java/com/mojang/brigadier/tree/LiteralCommandNode.java index 1bddb83..28e55e8 100644 --- a/src/main/java/com/mojang/brigadier/tree/LiteralCommandNode.java +++ b/src/main/java/com/mojang/brigadier/tree/LiteralCommandNode.java @@ -7,7 +7,9 @@ import com.mojang.brigadier.context.CommandContextBuilder; import com.mojang.brigadier.exceptions.CommandException; import com.mojang.brigadier.exceptions.ParameterizedCommandExceptionType; +import java.util.Collection; import java.util.Set; +import java.util.function.Function; import java.util.function.Predicate; public class LiteralCommandNode extends CommandNode { @@ -15,8 +17,8 @@ public class LiteralCommandNode extends CommandNode { private final String literal; - public LiteralCommandNode(final String literal, final Command command, final Predicate requirement, final CommandNode redirect) { - super(command, requirement, redirect); + public LiteralCommandNode(final String literal, final Command command, final Predicate requirement, final CommandNode redirect, final Function> modifier) { + super(command, requirement, redirect, modifier); this.literal = literal; } @@ -78,7 +80,7 @@ public class LiteralCommandNode extends CommandNode { public LiteralArgumentBuilder createBuilder() { final LiteralArgumentBuilder builder = LiteralArgumentBuilder.literal(this.literal); builder.requires(getRequirement()); - builder.redirect(getRedirect()); + builder.redirect(getRedirect(), getRedirectModifier()); if (getCommand() != null) { builder.executes(getCommand()); } diff --git a/src/main/java/com/mojang/brigadier/tree/RootCommandNode.java b/src/main/java/com/mojang/brigadier/tree/RootCommandNode.java index d90e4e3..cf43586 100644 --- a/src/main/java/com/mojang/brigadier/tree/RootCommandNode.java +++ b/src/main/java/com/mojang/brigadier/tree/RootCommandNode.java @@ -5,11 +5,13 @@ import com.mojang.brigadier.builder.ArgumentBuilder; import com.mojang.brigadier.context.CommandContextBuilder; import com.mojang.brigadier.exceptions.CommandException; +import java.util.Collection; +import java.util.Collections; import java.util.Set; public class RootCommandNode extends CommandNode { public RootCommandNode() { - super(null, c -> true, null); + super(null, c -> true, null, Collections::singleton); } @Override diff --git a/src/test/java/com/mojang/brigadier/CommandDispatcherCompletionsTest.java b/src/test/java/com/mojang/brigadier/CommandDispatcherCompletionsTest.java index a69d5bb..640a6e4 100644 --- a/src/test/java/com/mojang/brigadier/CommandDispatcherCompletionsTest.java +++ b/src/test/java/com/mojang/brigadier/CommandDispatcherCompletionsTest.java @@ -6,6 +6,8 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; +import java.util.Collections; + import static com.mojang.brigadier.builder.LiteralArgumentBuilder.literal; import static org.hamcrest.Matchers.emptyArray; import static org.hamcrest.Matchers.equalTo; @@ -43,7 +45,7 @@ public class CommandDispatcherCompletionsTest { public void testCommand_redirect() throws Exception { subject.register(literal("foo")); subject.register(literal("bar")); - subject.register(literal("redirect").redirect(subject.getRoot())); + subject.register(literal("redirect").redirect(subject.getRoot(), Collections::singleton)); assertThat(subject.getCompletionSuggestions("redirect ", source), equalTo(new String[]{"bar", "foo", "redirect"})); } diff --git a/src/test/java/com/mojang/brigadier/CommandDispatcherTest.java b/src/test/java/com/mojang/brigadier/CommandDispatcherTest.java index ec1eb9d..21f013d 100644 --- a/src/test/java/com/mojang/brigadier/CommandDispatcherTest.java +++ b/src/test/java/com/mojang/brigadier/CommandDispatcherTest.java @@ -1,6 +1,8 @@ package com.mojang.brigadier; +import com.google.common.collect.Lists; import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.context.CommandContextBuilder; import com.mojang.brigadier.exceptions.CommandException; import com.mojang.brigadier.tree.LiteralCommandNode; import org.junit.Before; @@ -9,17 +11,20 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; +import java.util.Collection; import java.util.Collections; +import java.util.function.Function; import static com.mojang.brigadier.arguments.IntegerArgumentType.integer; import static com.mojang.brigadier.builder.LiteralArgumentBuilder.literal; import static com.mojang.brigadier.builder.RequiredArgumentBuilder.argument; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasProperty; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; -import static org.mockito.Matchers.notNull; +import static org.mockito.Matchers.argThat; import static org.mockito.Mockito.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -172,26 +177,54 @@ public class CommandDispatcherTest { @Test public void testExecuteRedirected() throws Exception { subject.register(literal("actual").executes(command)); - subject.register(literal("redirected").redirect(subject.getRoot())); + subject.register(literal("redirected").redirect(subject.getRoot(), Collections::singleton)); final ParseResults parse = subject.parse("redirected redirected actual", source); - assertThat(parse.getContext().getInput(), equalTo("actual")); - assertThat(parse.getContext().getNodes().size(), is(2)); + assertThat(parse.getContext().getInput(), equalTo("redirected")); + assertThat(parse.getContext().getNodes().size(), is(1)); - final CommandContext parent1 = parse.getContext().getParent(); - assertThat(parent1, is(notNullValue())); - assertThat(parent1.getInput(), equalTo("redirected")); - assertThat(parent1.getNodes().size(), is(2)); + final CommandContextBuilder child1 = parse.getContext().getChild(); + assertThat(child1, is(notNullValue())); + assertThat(child1.getInput(), equalTo("redirected")); + assertThat(child1.getNodes().size(), is(2)); - final CommandContext parent2 = parent1.getParent(); - assertThat(parent2, is(notNullValue())); - assertThat(parent2.getInput(), equalTo("redirected")); - assertThat(parent2.getNodes().size(), is(1)); + final CommandContextBuilder child2 = child1.getChild(); + assertThat(child2, is(notNullValue())); + assertThat(child2.getInput(), equalTo("actual")); + assertThat(child2.getNodes().size(), is(2)); assertThat(subject.execute(parse), is(42)); verify(command).run(any(CommandContext.class)); } + @SuppressWarnings("unchecked") + @Test + public void testExecuteRedirectedMultipleTimes() throws Exception { + final Function> modifier = mock(Function.class); + final Object source1 = new Object(); + final Object source2 = new Object(); + + when(modifier.apply(source)).thenReturn(Lists.newArrayList(source1, source2)); + + subject.register(literal("actual").executes(command)); + subject.register(literal("redirected").redirect(subject.getRoot(), modifier)); + + final ParseResults parse = subject.parse("redirected actual", source); + assertThat(parse.getContext().getInput(), equalTo("redirected")); + assertThat(parse.getContext().getNodes().size(), is(1)); + assertThat(parse.getContext().getSource(), is(source)); + + final CommandContextBuilder parent = parse.getContext().getChild(); + assertThat(parent, is(notNullValue())); + assertThat(parent.getInput(), equalTo("actual")); + assertThat(parent.getNodes().size(), is(2)); + assertThat(parent.getSource(), is(source)); + + assertThat(subject.execute(parse), is(84)); + verify(command).run(argThat(hasProperty("source", is(source1)))); + verify(command).run(argThat(hasProperty("source", is(source2)))); + } + @Test public void testExecuteOrphanedSubcommand() throws Exception { subject.register(literal("foo").then( diff --git a/src/test/java/com/mojang/brigadier/CommandDispatcherUsagesTest.java b/src/test/java/com/mojang/brigadier/CommandDispatcherUsagesTest.java index 2a76d02..7a5cbf7 100644 --- a/src/test/java/com/mojang/brigadier/CommandDispatcherUsagesTest.java +++ b/src/test/java/com/mojang/brigadier/CommandDispatcherUsagesTest.java @@ -10,6 +10,7 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; +import java.util.Collections; import java.util.Map; import static com.mojang.brigadier.builder.LiteralArgumentBuilder.literal; @@ -89,11 +90,11 @@ public class CommandDispatcherUsagesTest { ); subject.register( literal("j") - .redirect(subject.getRoot()) + .redirect(subject.getRoot(), Collections::singleton) ); subject.register( literal("k") - .redirect(get("h")) + .redirect(get("h"), Collections::singleton) ); } diff --git a/src/test/java/com/mojang/brigadier/builder/ArgumentBuilderTest.java b/src/test/java/com/mojang/brigadier/builder/ArgumentBuilderTest.java index 779a56c..e7501d5 100644 --- a/src/test/java/com/mojang/brigadier/builder/ArgumentBuilderTest.java +++ b/src/test/java/com/mojang/brigadier/builder/ArgumentBuilderTest.java @@ -5,6 +5,8 @@ import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; +import java.util.Collections; + import static com.mojang.brigadier.arguments.IntegerArgumentType.integer; import static com.mojang.brigadier.builder.LiteralArgumentBuilder.literal; import static com.mojang.brigadier.builder.RequiredArgumentBuilder.argument; @@ -35,7 +37,7 @@ public class ArgumentBuilderTest { @Test public void testRedirect() throws Exception { final CommandNode target = mock(CommandNode.class); - builder.redirect(target); + builder.redirect(target, Collections::singleton); assertThat(builder.getRedirect(), is(target)); } @@ -43,13 +45,13 @@ public class ArgumentBuilderTest { public void testRedirect_withChild() throws Exception { final CommandNode target = mock(CommandNode.class); builder.then(literal("foo")); - builder.redirect(target); + builder.redirect(target, Collections::singleton); } @Test(expected = IllegalStateException.class) public void testThen_withRedirect() throws Exception { final CommandNode target = mock(CommandNode.class); - builder.redirect(target); + builder.redirect(target, Collections::singleton); builder.then(literal("foo")); }