Added javadoc to CommandDispatcher

This commit is contained in:
Nathan Adams 2018-09-25 17:38:04 +02:00
parent e54a50f566
commit ff0da75678

View file

@ -29,9 +29,27 @@ import java.util.concurrent.CompletableFuture;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/**
* The core command dispatcher, for registering, parsing, and executing commands.
*
* @param <S> a custom "source" type, such as a user or originator of a command
*/
public class CommandDispatcher<S> { public class CommandDispatcher<S> {
/**
* The string required to separate individual arguments in an input string
*
* @see #ARGUMENT_SEPARATOR_CHAR
*/
public static final String ARGUMENT_SEPARATOR = " "; public static final String ARGUMENT_SEPARATOR = " ";
/**
* The char required to separate individual arguments in an input string
*
* @see #ARGUMENT_SEPARATOR
*/
public static final char ARGUMENT_SEPARATOR_CHAR = ' '; public static final char ARGUMENT_SEPARATOR_CHAR = ' ';
private static final String USAGE_OPTIONAL_OPEN = "["; private static final String USAGE_OPTIONAL_OPEN = "[";
private static final String USAGE_OPTIONAL_CLOSE = "]"; private static final String USAGE_OPTIONAL_CLOSE = "]";
private static final String USAGE_REQUIRED_OPEN = "("; private static final String USAGE_REQUIRED_OPEN = "(";
@ -48,33 +66,144 @@ public class CommandDispatcher<S> {
private ResultConsumer<S> consumer = (c, s, r) -> { private ResultConsumer<S> consumer = (c, s, r) -> {
}; };
/**
* Create a new {@link CommandDispatcher} with the specified root node.
*
* <p>This is often useful to copy existing or pre-defined command trees.</p>
*
* @param root the existing {@link RootCommandNode} to use as the basis for this tree
*/
public CommandDispatcher(final RootCommandNode<S> root) { public CommandDispatcher(final RootCommandNode<S> root) {
this.root = root; this.root = root;
} }
/**
* Creates a new {@link CommandDispatcher} with an empty command tree.
*/
public CommandDispatcher() { public CommandDispatcher() {
this(new RootCommandNode<>()); this(new RootCommandNode<>());
} }
/**
* Utility method for registering new commands.
*
* <p>This is a shortcut for calling {@link RootCommandNode#addChild(CommandNode)} after building the provided {@code command}.</p>
*
* <p>As {@link RootCommandNode} can only hold literals, this method will only allow literal arguments.</p>
*
* @param command a literal argument builder to add to this command tree
* @return the node added to this tree
*/
public LiteralCommandNode<S> register(final LiteralArgumentBuilder<S> command) { public LiteralCommandNode<S> register(final LiteralArgumentBuilder<S> command) {
final LiteralCommandNode<S> build = command.build(); final LiteralCommandNode<S> build = command.build();
root.addChild(build); root.addChild(build);
return build; return build;
} }
/**
* Sets a callback to be informed of the result of every command.
*
* @param consumer the new result consumer to be called
*/
public void setConsumer(final ResultConsumer<S> consumer) { public void setConsumer(final ResultConsumer<S> consumer) {
this.consumer = consumer; this.consumer = consumer;
} }
/**
* Parses and executes a given command.
*
* <p>This is a shortcut to first {@link #parse(StringReader, Object)} and then {@link #execute(ParseResults)}.</p>
*
* <p>It is recommended to parse and execute as separate steps, as parsing is often the most expensive step, and easiest to cache.</p>
*
* <p>If this command returns a value, then it successfully executed something. If it could not parse the command, or the execution was a failure,
* then an exception will be thrown. Most exceptions will be of type {@link CommandSyntaxException}, but it is possible that a {@link RuntimeException}
* may bubble up from the result of a command. The meaning behind the returned result is arbitrary, and will depend
* entirely on what command was performed.</p>
*
* <p>If the command passes through a node that is {@link CommandNode#isFork()} then it will be 'forked'.
* A forked command will not bubble up any {@link CommandSyntaxException}s, and the 'result' returned will turn into
* 'amount of successful commands executes'.</p>
*
* <p>After each and any command is ran, a registered callback given to {@link #setConsumer(ResultConsumer)}
* will be notified of the result and success of the command. You can use that method to gather more meaningful
* results than this method will return, especially when a command forks.</p>
*
* @param input a command string to parse &amp; execute
* @param source a custom "source" object, usually representing the originator of this command
* @return a numeric result from a "command" that was performed
* @throws CommandSyntaxException if the command failed to parse or execute
* @throws RuntimeException if the command failed to execute and was not handled gracefully
* @see #parse(String, Object)
* @see #parse(StringReader, Object)
* @see #execute(ParseResults)
* @see #execute(StringReader, Object)
*/
public int execute(final String input, final S source) throws CommandSyntaxException { public int execute(final String input, final S source) throws CommandSyntaxException {
return execute(new StringReader(input), source); return execute(new StringReader(input), source);
} }
/**
* Parses and executes a given command.
*
* <p>This is a shortcut to first {@link #parse(StringReader, Object)} and then {@link #execute(ParseResults)}.</p>
*
* <p>It is recommended to parse and execute as separate steps, as parsing is often the most expensive step, and easiest to cache.</p>
*
* <p>If this command returns a value, then it successfully executed something. If it could not parse the command, or the execution was a failure,
* then an exception will be thrown. Most exceptions will be of type {@link CommandSyntaxException}, but it is possible that a {@link RuntimeException}
* may bubble up from the result of a command. The meaning behind the returned result is arbitrary, and will depend
* entirely on what command was performed.</p>
*
* <p>If the command passes through a node that is {@link CommandNode#isFork()} then it will be 'forked'.
* A forked command will not bubble up any {@link CommandSyntaxException}s, and the 'result' returned will turn into
* 'amount of successful commands executes'.</p>
*
* <p>After each and any command is ran, a registered callback given to {@link #setConsumer(ResultConsumer)}
* will be notified of the result and success of the command. You can use that method to gather more meaningful
* results than this method will return, especially when a command forks.</p>
*
* @param input a command string to parse &amp; execute
* @param source a custom "source" object, usually representing the originator of this command
* @return a numeric result from a "command" that was performed
* @throws CommandSyntaxException if the command failed to parse or execute
* @throws RuntimeException if the command failed to execute and was not handled gracefully
* @see #parse(String, Object)
* @see #parse(StringReader, Object)
* @see #execute(ParseResults)
* @see #execute(String, Object)
*/
public int execute(final StringReader input, final S source) throws CommandSyntaxException { public int execute(final StringReader input, final S source) throws CommandSyntaxException {
final ParseResults<S> parse = parse(input, source); final ParseResults<S> parse = parse(input, source);
return execute(parse); return execute(parse);
} }
/**
* Executes a given pre-parsed command.
*
* <p>If this command returns a value, then it successfully executed something. If the execution was a failure,
* then an exception will be thrown.
* Most exceptions will be of type {@link CommandSyntaxException}, but it is possible that a {@link RuntimeException}
* may bubble up from the result of a command. The meaning behind the returned result is arbitrary, and will depend
* entirely on what command was performed.</p>
*
* <p>If the command passes through a node that is {@link CommandNode#isFork()} then it will be 'forked'.
* A forked command will not bubble up any {@link CommandSyntaxException}s, and the 'result' returned will turn into
* 'amount of successful commands executes'.</p>
*
* <p>After each and any command is ran, a registered callback given to {@link #setConsumer(ResultConsumer)}
* will be notified of the result and success of the command. You can use that method to gather more meaningful
* results than this method will return, especially when a command forks.</p>
*
* @param parse the result of a successful {@link #parse(StringReader, Object)}
* @return a numeric result from a "command" that was performed.
* @throws CommandSyntaxException if the command failed to parse or execute
* @throws RuntimeException if the command failed to execute and was not handled gracefully
* @see #parse(String, Object)
* @see #parse(StringReader, Object)
* @see #execute(String, Object)
* @see #execute(StringReader, Object)
*/
public int execute(final ParseResults<S> parse) throws CommandSyntaxException { public int execute(final ParseResults<S> parse) throws CommandSyntaxException {
if (parse.getReader().canRead()) { if (parse.getReader().canRead()) {
if (parse.getExceptions().size() == 1) { if (parse.getExceptions().size() == 1) {
@ -157,10 +286,64 @@ public class CommandDispatcher<S> {
return forked ? successfulForks : result; return forked ? successfulForks : result;
} }
/**
* Parses a given command.
*
* <p>The result of this method can be cached, and it is advised to do so where appropriate. Parsing is often the
* most expensive step, and this allows you to essentially "precompile" a command if it will be ran often.</p>
*
* <p>If the command passes through a node that is {@link CommandNode#isFork()} then the resulting context will be marked as 'forked'.
* Forked contexts may contain child contexts, which may be modified by the {@link RedirectModifier} attached to the fork.</p>
*
* <p>Parsing a command can never fail, you will always be provided with a new {@link ParseResults}.
* However, that does not mean that it will always parse into a valid command. You should inspect the returned results
* to check for validity. If its {@link ParseResults#getReader()} {@link StringReader#canRead()} then it did not finish
* parsing successfully. You can use that position as an indicator to the user where the command stopped being valid.
* You may inspect {@link ParseResults#getExceptions()} if you know the parse failed, as it will explain why it could
* not find any valid commands. It may contain multiple exceptions, one for each "potential node" that it could have visited,
* explaining why it did not go down that node.</p>
*
* <p>When you eventually call {@link #execute(ParseResults)} with the result of this method, the above error checking
* will occur. You only need to inspect it yourself if you wish to handle that yourself.</p>
*
* @param command a command string to parse
* @param source a custom "source" object, usually representing the originator of this command
* @return the result of parsing this command
* @see #parse(StringReader, Object)
* @see #execute(ParseResults)
* @see #execute(String, Object)
*/
public ParseResults<S> parse(final String command, final S source) { public ParseResults<S> parse(final String command, final S source) {
return parse(new StringReader(command), source); return parse(new StringReader(command), source);
} }
/**
* Parses a given command.
*
* <p>The result of this method can be cached, and it is advised to do so where appropriate. Parsing is often the
* most expensive step, and this allows you to essentially "precompile" a command if it will be ran often.</p>
*
* <p>If the command passes through a node that is {@link CommandNode#isFork()} then the resulting context will be marked as 'forked'.
* Forked contexts may contain child contexts, which may be modified by the {@link RedirectModifier} attached to the fork.</p>
*
* <p>Parsing a command can never fail, you will always be provided with a new {@link ParseResults}.
* However, that does not mean that it will always parse into a valid command. You should inspect the returned results
* to check for validity. If its {@link ParseResults#getReader()} {@link StringReader#canRead()} then it did not finish
* parsing successfully. You can use that position as an indicator to the user where the command stopped being valid.
* You may inspect {@link ParseResults#getExceptions()} if you know the parse failed, as it will explain why it could
* not find any valid commands. It may contain multiple exceptions, one for each "potential node" that it could have visited,
* explaining why it did not go down that node.</p>
*
* <p>When you eventually call {@link #execute(ParseResults)} with the result of this method, the above error checking
* will occur. You only need to inspect it yourself if you wish to handle that yourself.</p>
*
* @param command a command string to parse
* @param source a custom "source" object, usually representing the originator of this command
* @return the result of parsing this command
* @see #parse(String, Object)
* @see #execute(ParseResults)
* @see #execute(String, Object)
*/
public ParseResults<S> parse(final StringReader command, final S source) { public ParseResults<S> parse(final StringReader command, final S source) {
final CommandContextBuilder<S> context = new CommandContextBuilder<>(this, source, 0); final CommandContextBuilder<S> context = new CommandContextBuilder<>(this, source, 0);
return parseNodes(root, command, context); return parseNodes(root, command, context);
@ -256,6 +439,27 @@ public class CommandDispatcher<S> {
return new ParseResults<>(contextSoFar, originalReader.getCursor(), originalReader, errors == null ? Collections.emptyMap() : errors); return new ParseResults<>(contextSoFar, originalReader.getCursor(), originalReader, errors == null ? Collections.emptyMap() : errors);
} }
/**
* Gets all possible executable commands following the given node.
*
* <p>You may use {@link #getRoot()} as a target to get all usage data for the entire command tree.</p>
*
* <p>The returned syntax will be in "simple" form: {@code <param>} and {@code literal}. "Optional" nodes will be
* listed as multiple entries: the parent node, and the child nodes.
* For example, a required literal "foo" followed by an optional param "int" will be two nodes:</p>
* <ul>
* <li>{@code foo}</li>
* <li>{@code foo <int>}</li>
* </ul>
*
* <p>The path to the specified node will <b>not</b> be prepended to the output, as there can theoretically be many
* ways to reach a given node. It will only give you paths relative to the specified node, not absolute from root.</p>
*
* @param node target node to get child usage strings for
* @param source a custom "source" object, usually representing the originator of this command
* @param restricted if true, commands that the {@code source} cannot access will not be mentioned
* @return array of full usage strings under the target node
*/
public String[] getAllUsage(final CommandNode<S> node, final S source, final boolean restricted) { public String[] getAllUsage(final CommandNode<S> node, final S source, final boolean restricted) {
final ArrayList<String> result = Lists.newArrayList(); final ArrayList<String> result = Lists.newArrayList();
getAllUsage(node, source, result, "", restricted); getAllUsage(node, source, result, "", restricted);
@ -281,6 +485,27 @@ public class CommandDispatcher<S> {
} }
} }
/**
* Gets the possible executable commands from a specified node.
*
* <p>You may use {@link #getRoot()} as a target to get usage data for the entire command tree.</p>
*
* <p>The returned syntax will be in "smart" form: {@code <param>}, {@code literal}, {@code [optional]} and {@code (either|or)}.
* These forms may be mixed and matched to provide as much information about the child nodes as it can, without being too verbose.
* For example, a required literal "foo" followed by an optional param "int" can be compressed into one string:</p>
* <ul>
* <li>{@code foo [<int>]}</li>
* </ul>
*
* <p>The path to the specified node will <b>not</b> be prepended to the output, as there can theoretically be many
* ways to reach a given node. It will only give you paths relative to the specified node, not absolute from root.</p>
*
* <p>The returned usage will be restricted to only commands that the provided {@code source} can use.</p>
*
* @param node target node to get child usage strings for
* @param source a custom "source" object, usually representing the originator of this command
* @return array of full usage strings under the target node
*/
public Map<CommandNode<S>, String> getSmartUsage(final CommandNode<S> node, final S source) { public Map<CommandNode<S>, String> getSmartUsage(final CommandNode<S> node, final S source) {
final Map<CommandNode<S>, String> result = Maps.newLinkedHashMap(); final Map<CommandNode<S>, String> result = Maps.newLinkedHashMap();
@ -348,6 +573,21 @@ public class CommandDispatcher<S> {
return self; return self;
} }
/**
* Gets suggestions for a parsed input string on what comes next.
*
* <p>As it is ultimately up to custom argument types to provide suggestions, it may be an asynchronous operation,
* for example getting in-game data or player names etc. As such, this method returns a future and no guarantees
* are made to when or how the future completes.</p>
*
* <p>The suggestions provided will be in the context of the end of the parsed input string, but may suggest
* new or replacement strings for earlier in the input string. For example, if the end of the string was
* {@code foobar} but an argument preferred it to be {@code minecraft:foobar}, it will suggest a replacement for that
* whole segment of the input.</p>
*
* @param parse the result of a {@link #parse(StringReader, Object)}
* @return a future that will eventually resolve into a {@link Suggestions} object
*/
public CompletableFuture<Suggestions> getCompletionSuggestions(final ParseResults<S> parse) { public CompletableFuture<Suggestions> getCompletionSuggestions(final ParseResults<S> parse) {
final CommandContextBuilder<S> rootContext = parse.getContext(); final CommandContextBuilder<S> rootContext = parse.getContext();
final CommandContextBuilder<S> context = rootContext.getLastChild(); final CommandContextBuilder<S> context = rootContext.getLastChild();
@ -397,10 +637,33 @@ public class CommandDispatcher<S> {
return result; return result;
} }
/**
* Gets the root of this command tree.
*
* <p>This is often useful as a target of a {@link com.mojang.brigadier.builder.ArgumentBuilder#redirect(CommandNode)},
* {@link #getAllUsage(CommandNode, Object, boolean)} or {@link #getSmartUsage(CommandNode, Object)}.
* You may also use it to clone the command tree via {@link #CommandDispatcher(RootCommandNode)}.</p>
*
* @return root of the command tree
*/
public RootCommandNode<S> getRoot() { public RootCommandNode<S> getRoot() {
return root; return root;
} }
/**
* Finds a valid path to a given node on the command tree.
*
* <p>There may theoretically be multiple paths to a node on the tree, especially with the use of forking or redirecting.
* As such, this method makes no guarantees about which path it finds. It will not look at forks or redirects,
* and find the first instance of the target node on the tree.</p>
*
* <p>The only guarantee made is that for the same command tree and the same version of this library, the result of
* this method will <b>always</b> be a valid input for {@link #findNode(Collection)}, which should return the same node
* as provided to this method.</p>
*
* @param target the target node you are finding a path for
* @return a path to the resulting node, or an empty list if it was not found
*/
public Collection<String> getPath(final CommandNode<S> target) { public Collection<String> getPath(final CommandNode<S> target) {
final List<List<CommandNode<S>>> nodes = new ArrayList<>(); final List<List<CommandNode<S>>> nodes = new ArrayList<>();
addPaths(root, nodes, new ArrayList<>()); addPaths(root, nodes, new ArrayList<>());
@ -420,6 +683,17 @@ public class CommandDispatcher<S> {
return Collections.emptyList(); return Collections.emptyList();
} }
/**
* Finds a node by its path
*
* <p>Paths may be generated with {@link #getPath(CommandNode)}, and are guaranteed (for the same tree, and the
* same version of this library) to always produce the same valid node by this method.</p>
*
* <p>If a node could not be found at the specified path, then {@code null} will be returned.</p>
*
* @param path a generated path to a node
* @return the node at the given path, or null if not found
*/
public CommandNode<S> findNode(final Collection<String> path) { public CommandNode<S> findNode(final Collection<String> path) {
CommandNode<S> node = root; CommandNode<S> node = root;
for (final String name : path) { for (final String name : path) {
@ -431,6 +705,16 @@ public class CommandDispatcher<S> {
return node; return node;
} }
/**
* Scans the command tree for potential ambiguous commands.
*
* <p>This is a shortcut for {@link CommandNode#findAmbiguities(AmbiguityConsumer)} on {@link #getRoot()}.</p>
*
* <p>Ambiguities are detected by testing every {@link CommandNode#getExamples()} on one node verses every sibling
* node. This is not fool proof, and relies a lot on the providers of the used argument types to give good examples.</p>
*
* @param consumer a callback to be notified of potential ambiguities
*/
public void findAmbiguities(final AmbiguityConsumer<S> consumer) { public void findAmbiguities(final AmbiguityConsumer<S> consumer) {
root.findAmbiguities(consumer); root.findAmbiguities(consumer);
} }