Move to an improved CommandRouter, improve routing logic

now supports splitting commands over multiple BaseCommands better
should now only match probable @Default handlers, so @CatchUnknown
can still work in obviously wrong scenarios.

@HelpCommand no longer implies @Default as @CatchUnknown will pick it up
This commit is contained in:
Aikar
2019-02-25 00:39:36 -05:00
parent 975eb22afd
commit 67d631d47c
14 changed files with 261 additions and 276 deletions
+15
View File
@@ -1,6 +1,9 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="Duplicates" enabled="false" level="WARNING" enabled_by_default="false">
<option name="minCloneLength" value="100" />
</inspection_tool>
<inspection_tool class="JavaDoc" enabled="false" level="WARNING" enabled_by_default="false">
<option name="TOP_LEVEL_CLASS_OPTIONS">
<value>
@@ -39,5 +42,17 @@
<option name="processComments" value="true" />
</inspection_tool>
<inspection_tool class="UnusedProperty" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="unused" enabled="true" level="WARNING" enabled_by_default="true" klass="packageLocal" inner_class="protected" field="packageLocal" method="packageLocal" parameter="packageLocal">
<option name="LOCAL_VARIABLE" value="true" />
<option name="FIELD" value="true" />
<option name="METHOD" value="true" />
<option name="CLASS" value="true" />
<option name="PARAMETER" value="true" />
<option name="REPORT_PARAMETER_FOR_PUBLIC_METHODS" value="false" />
<option name="ADD_MAINS_TO_ENTRIES" value="true" />
<option name="ADD_APPLET_TO_ENTRIES" value="true" />
<option name="ADD_SERVLET_TO_ENTRIES" value="true" />
<option name="ADD_NONJAVA_TO_ENTRIES" value="true" />
</inspection_tool>
</profile>
</component>
@@ -52,8 +52,9 @@ public class BukkitRootCommand extends Command implements RootCommand {
}
@Override
public List<String> tabComplete(CommandSender sender, String alias, String[] args) throws IllegalArgumentException {
return getTabCompletions(manager.getCommandIssuer(sender), alias, args);
public List<String> tabComplete(CommandSender sender, String commandLabel, String[] args) throws IllegalArgumentException {
if (commandLabel.contains(":")) commandLabel = ACFPatterns.COLON.split(commandLabel, 2)[1];
return getTabCompletions(manager.getCommandIssuer(sender), commandLabel, args);
}
@Override
@@ -71,8 +72,6 @@ public class BukkitRootCommand extends Command implements RootCommand {
public void addChild(BaseCommand command) {
if (this.defCommand == null || !command.subCommands.get(BaseCommand.DEFAULT).isEmpty()) {
this.defCommand = command;
//this.setDescription(command.getDescription());
//this.setUsage(command.getUsage());
}
addChildShared(this.children, this.subCommands, command);
setPermission(getUniquePermission());
@@ -23,6 +23,7 @@
package co.aikar.commands;
import co.aikar.commands.CommandRouter.RouteSearch;
import co.aikar.commands.annotation.CatchAll;
import co.aikar.commands.annotation.CatchUnknown;
import co.aikar.commands.annotation.CommandAlias;
@@ -50,7 +51,6 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.Stack;
import java.util.stream.Collectors;
@@ -67,7 +67,6 @@ import java.util.stream.Stream;
* then each actionable command is a sub command
*/
@SuppressWarnings("unused")
public abstract class BaseCommand {
/**
@@ -310,7 +309,6 @@ public abstract class BaseCommand {
*/
private void registerSubcommands() {
final Annotations annotations = manager.getAnnotations();
boolean foundDefault = false;
boolean foundCatchUnknown = false;
boolean isParentEmpty = parentSubcommand == null || parentSubcommand.isEmpty();
@@ -318,22 +316,14 @@ public abstract class BaseCommand {
method.setAccessible(true);
String sublist = null;
String sub = getSubcommandValue(method);
final boolean def = annotations.hasAnnotation(method, Default.class);
final String helpCommand = annotations.getAnnotationValue(method, HelpCommand.class, Annotations.NOTHING);
final String commandAliases = annotations.getAnnotationValue(method, CommandAlias.class, Annotations.NOTHING);
if (!isParentEmpty && def) {
sub = parentSubcommand;
}
if (isParentEmpty && (def || (!foundDefault && helpCommand != null))) {
if (!foundDefault) {
if (def) {
this.subCommands.get(DEFAULT).clear();
foundDefault = true;
}
registerSubcommand(method, DEFAULT);
if (annotations.hasAnnotation(method, Default.class)) {
if (!isParentEmpty) {
sub = parentSubcommand;
} else {
ACFUtil.sneaky(new IllegalStateException("Multiple @Default/@HelpCommand commands, duplicate on " + method.getDeclaringClass().getName() + "#" + method.getName()));
registerSubcommand(method, DEFAULT);
}
}
@@ -359,7 +349,7 @@ public abstract class BaseCommand {
}
registerSubcommand(method, CATCHUNKNOWN);
} else {
ACFUtil.sneaky(new IllegalStateException("Multiple @UnknownHandler/@HelpCommand commands, duplicate on " + method.getDeclaringClass().getName() + "#" + method.getName()));
ACFUtil.sneaky(new IllegalStateException("Multiple @CatchUnknown/@HelpCommand commands, duplicate on " + method.getDeclaringClass().getName() + "#" + method.getName()));
}
} else if (preCommand) {
if (this.preCommandHandler == null) {
@@ -506,48 +496,16 @@ public abstract class BaseCommand {
}
}
public void execute(CommandIssuer issuer, String commandLabel, String[] args) {
commandLabel = commandLabel.toLowerCase();
void execute(CommandIssuer issuer, CommandRouter.CommandRouteResult command) {
try {
CommandOperationContext commandContext = preCommandOperation(issuer, commandLabel, args, false);
if (args.length > 0) {
CommandSearch cmd = findSubCommand(args);
if (cmd != null) {
execSubcommand = cmd.getCheckSub();
final String[] execargs = Arrays.copyOfRange(args, cmd.argIndex, args.length);
executeCommand(commandContext, issuer, execargs, cmd.cmd);
return;
}
}
Set<RegisteredCommand> defaultCommands = subCommands.get(DEFAULT);
RegisteredCommand defCommand = ACFUtil.getFirstElement(defaultCommands);
if (defCommand != null && (args.length == 0 || defCommand.consumeInputResolvers > 0)) {
findAndExecuteCommand(commandContext, DEFAULT, issuer, args);
} else if (subCommands.get(CATCHUNKNOWN) != null) {
if (!findAndExecuteCommand(commandContext, CATCHUNKNOWN, issuer, args)) {
help(issuer, args);
}
}
CommandOperationContext commandContext = preCommandOperation(issuer, command.commandLabel, command.args, false);
execSubcommand = command.subcommand;
executeCommand(commandContext, issuer, command.args, command.cmd);
} finally {
postCommandOperation();
}
}
/**
* Gets the registered command of the given arguments.
*
* @param args The arguments given by the user.
* @return The subcommand or null if none were found.
* @see #findSubCommand(String[])
*/
RegisteredCommand<?> getRegisteredCommand(String[] args) {
final CommandSearch cmd = findSubCommand(args);
return cmd != null ? cmd.cmd : null;
}
/**
* This is ran after any command operation has been performed.
*/
@@ -598,60 +556,6 @@ public abstract class BaseCommand {
return CommandManager.getCurrentCommandManager();
}
/**
* Finds a subcommand of the given arguments.
*
* @param args The arguments the user input.
* @return The identified subcommand.
* @see #findSubCommand(String[], boolean)
*/
private CommandSearch findSubCommand(String[] args) {
return findSubCommand(args, false);
}
/**
* Finds a subcommand of the given arguments.
*
* @param args The arguments the user input.
* @param completion Whether or not completion of arguments should kick in. This may end up with worse than wanted results.
* @return The identified subcommand.
*/
private CommandSearch findSubCommand(String[] args, boolean completion) {
for (int i = args.length; i >= 0; i--) {
String checkSub = ApacheCommonsLangUtil.join(args, " ", 0, i).toLowerCase();
Set<RegisteredCommand> cmds = subCommands.get(checkSub);
final int extraArgs = args.length - i;
if (!cmds.isEmpty()) {
RegisteredCommand cmd = null;
if (cmds.size() == 1) {
cmd = ACFUtil.getFirstElement(cmds);
} else {
Optional<RegisteredCommand> optCmd = cmds.stream().filter(c -> {
int required = c.requiredResolvers;
int optional = c.optionalResolvers;
return extraArgs <= required + optional && (completion || extraArgs >= required);
}).min((c1, c2) -> {
int a = c1.consumeInputResolvers;
int b = c2.consumeInputResolvers;
if (a == b) {
return 0;
}
return a < b ? 1 : -1;
});
if (optCmd.isPresent()) {
cmd = optCmd.get();
}
}
if (cmd != null) {
return new CommandSearch(cmd, i, checkSub);
}
}
}
return null;
}
private void executeCommand(CommandOperationContext commandOperationContext,
CommandIssuer issuer, String[] args, RegisteredCommand cmd) {
if (cmd.hasPermission(issuer)) {
@@ -711,19 +615,17 @@ public abstract class BaseCommand {
args = new String[]{""};
}
try {
CommandOperationContext commandOperationContext = preCommandOperation(issuer, commandLabel, args, isAsync);
final CommandSearch search = findSubCommand(args, true);
CommandRouter router = manager.getRouter();
preCommandOperation(issuer, commandLabel, args, isAsync);
final RouteSearch search = router.routeCommand(commandLabel, args, true);
final List<String> cmds = new ArrayList<>();
if (search != null) {
cmds.addAll(completeCommand(issuer, search.cmd, Arrays.copyOfRange(args, search.argIndex, args.length), commandLabel, isAsync));
} else if (subCommands.get(CATCHUNKNOWN).size() == 1) {
cmds.addAll(completeCommand(issuer, ACFUtil.getFirstElement(subCommands.get(CATCHUNKNOWN)), args, commandLabel, isAsync));
} else if (subCommands.get(DEFAULT).size() == 1) {
cmds.addAll(completeCommand(issuer, ACFUtil.getFirstElement(subCommands.get(DEFAULT)), args, commandLabel, isAsync));
CommandRouter.CommandRouteResult result = router.matchCommand(search, true);
if (result != null) {
cmds.addAll(completeCommand(issuer, result.cmd, result.args, commandLabel, isAsync));
}
}
return filterTabComplete(args[args.length - 1], cmds);
@@ -792,55 +694,6 @@ public abstract class BaseCommand {
.collect(Collectors.toList());
}
/**
* Gets a registered command under the given subcommand name.
*
* @param subcommand The name of the subcommand requested.
* @return The subcommand found or null if none.
*/
private RegisteredCommand getCommandBySubcommand(String subcommand) {
return getCommandBySubcommand(subcommand, false);
}
/**
* Gets a registered command under the given name.
* If requireOne is true, it won't accept more than a single matching subcommand.
*
* @param subcommand Name of the subcommand wanted.
* @param requireOne Whether to only accept 1 result.
* @return The subcommand found, or null if none/too many.
*/
private RegisteredCommand getCommandBySubcommand(String subcommand, boolean requireOne) {
final Set<RegisteredCommand> commands = subCommands.get(subcommand);
if (!commands.isEmpty() && (!requireOne || commands.size() == 1)) {
return commands.iterator().next();
}
return null;
}
/**
* Internally calls {@link #executeCommand(CommandOperationContext, CommandIssuer, String[], RegisteredCommand)}
* and gets through {@link #getCommandBySubcommand(String)}.
*
* @param commandContext The command context to use.
* @param subcommand The subcommand to find the executor of.
* @param issuer The issuer who executed the subcommand.
* @param args All arguments given by the issuer.
* @return Whether it found a command or not.
* @see #executeCommand(CommandOperationContext, CommandIssuer, String[], RegisteredCommand)
* @see #getCommandBySubcommand(String)
* @see RegisteredCommand#invoke(CommandIssuer, List, CommandOperationContext)
*/
private boolean findAndExecuteCommand(CommandOperationContext commandContext, String subcommand, CommandIssuer issuer, String... args) {
final RegisteredCommand cmd = this.getCommandBySubcommand(subcommand);
if (cmd != null) {
executeCommand(commandContext, issuer, args, cmd);
return true;
}
return false;
}
/**
* Executes the precommand and sees whether something is wrong. Ideally, you get false from this.
*
@@ -951,7 +804,7 @@ public abstract class BaseCommand {
}
public RegisteredCommand getDefaultRegisteredCommand() {
return this.getCommandBySubcommand(DEFAULT);
return ACFUtil.getFirstElement(this.subCommands.get(DEFAULT));
}
public String setContextFlags(Class<?> cls, String flags) {
@@ -967,35 +820,4 @@ public abstract class BaseCommand {
registeredCommands.addAll(this.subCommands.values());
return registeredCommands;
}
private static class CommandSearch {
RegisteredCommand cmd;
int argIndex;
String checkSub;
CommandSearch(RegisteredCommand cmd, int argIndex, String checkSub) {
this.cmd = cmd;
this.argIndex = argIndex;
this.checkSub = checkSub;
}
String getCheckSub() {
return this.checkSub;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CommandSearch that = (CommandSearch) o;
return argIndex == that.argIndex &&
Objects.equals(cmd, that.cmd) &&
Objects.equals(checkSub, that.checkSub);
}
@Override
public int hashCode() {
return Objects.hash(cmd, argIndex, checkSub);
}
}
}
@@ -177,8 +177,6 @@ public class CommandContexts<R extends CommandExecutionContext<?, ? extends Comm
});
registerContext(String[].class, (c) -> {
String val;
// Go home IDEA, you're drunk
//noinspection unchecked
List<String> args = c.getArgs();
if (c.isLastArg() && !c.hasAnnotation(Single.class)) {
val = ACFUtil.join(args);
@@ -195,7 +193,7 @@ public class CommandContexts<R extends CommandExecutionContext<?, ? extends Comm
ACFUtil.sneaky(new IllegalStateException("Weird Command signature... String[] should be last or @Split"));
}
String[] result = args.toArray(new String[args.size()]);
String[] result = args.toArray(new String[0]);
args.clear();
return result;
});
@@ -241,7 +239,7 @@ public class CommandContexts<R extends CommandExecutionContext<?, ? extends Comm
// check if we have an exact match and should display the help page for that sub command instead
if (search != null) {
String cmd = String.join(" ", search);
if (commandHelp.isExactMatch(cmd)) {
if (commandHelp.testExactMatch(cmd)) {
return commandHelp;
}
}
@@ -28,7 +28,7 @@ import java.lang.reflect.Parameter;
import java.util.List;
import java.util.Map;
@SuppressWarnings({"WeakerAccess", "unused"})
@SuppressWarnings({"WeakerAccess"})
public class CommandExecutionContext <CEC extends CommandExecutionContext, I extends CommandIssuer> {
private final RegisteredCommand cmd;
private final CommandParameter param;
@@ -43,7 +43,7 @@ public class CommandHelp {
private int page;
private int perPage;
List<String> search;
private HelpEntry selectedEntry;
private Set<HelpEntry> selectedEntry = new HashSet<>();
private int totalResults;
private int totalPages;
private boolean lastPage;
@@ -117,14 +117,14 @@ public class CommandHelp {
return manager;
}
public boolean isExactMatch(String command) {
public boolean testExactMatch(String command) {
selectedEntry.clear();
for (HelpEntry helpEntry : helpEntries) {
if (helpEntry.getCommand().endsWith(" " + command)) {
selectedEntry = helpEntry;
return true;
selectedEntry.add(helpEntry);
}
}
return false;
return !selectedEntry.isEmpty();
}
public void showHelp() {
@@ -133,8 +133,15 @@ public class CommandHelp {
public void showHelp(CommandIssuer issuer) {
CommandHelpFormatter formatter = manager.getHelpFormatter();
if (selectedEntry != null) {
formatter.showDetailedHelp(this, selectedEntry);
if (!selectedEntry.isEmpty()) {
HelpEntry first = ACFUtil.getFirstElement(selectedEntry);
formatter.printDetailedHelpHeader(this, issuer, first);
for (HelpEntry helpEntry : selectedEntry) {
formatter.showDetailedHelp(this, helpEntry);
}
formatter.printDetailedHelpFooter(this, issuer, first);
return;
}
@@ -224,7 +231,7 @@ public class CommandHelp {
return search;
}
public HelpEntry getSelectedEntry() {
public Set<HelpEntry> getSelectedEntry() {
return selectedEntry;
}
@@ -56,8 +56,6 @@ public class CommandHelpFormatter {
public void showDetailedHelp(CommandHelp commandHelp, HelpEntry entry) {
CommandIssuer issuer = commandHelp.getIssuer();
// header
printDetailedHelpHeader(commandHelp, issuer, entry);
// normal help line
printDetailedHelpCommand(commandHelp, issuer, entry);
@@ -69,9 +67,6 @@ public class CommandHelpFormatter {
printDetailedParameter(commandHelp, issuer, entry, param);
}
}
// footer
printDetailedHelpFooter(commandHelp, issuer, entry);
}
// ########
@@ -86,6 +86,7 @@ public abstract class CommandManager <
private Set<String> unstableAPIs = new HashSet<>();
private Annotations annotations = new Annotations<>(this);
private CommandRouter router = new CommandRouter(this);
public static CommandOperationContext getCurrentCommandOperationContext() {
return commandOperationContext.get().peek();
@@ -199,6 +200,10 @@ public abstract class CommandManager <
return helpFormatter;
}
CommandRouter getRouter() {
return router;
}
/**
* Registers a command with ACF
*
@@ -270,14 +275,6 @@ public abstract class CommandManager <
return true;
}
BaseCommand getBaseCommand(String commandLabel, @NotNull String[] args) {
RootCommand rootCommand = obtainRootCommand(commandLabel);
if (rootCommand == null) {
return null;
}
return rootCommand.getBaseCommand(args);
}
public synchronized RootCommand getRootCommand(@NotNull String cmd) {
return rootCommands.get(ACFPatterns.SPACE.split(cmd.toLowerCase(), 2)[0]);
}
@@ -28,6 +28,7 @@ import co.aikar.commands.annotation.Default;
import co.aikar.commands.annotation.Description;
import co.aikar.commands.annotation.Flags;
import co.aikar.commands.annotation.Optional;
import co.aikar.commands.annotation.Single;
import co.aikar.commands.annotation.Syntax;
import co.aikar.commands.annotation.Values;
import co.aikar.commands.contexts.ContextResolver;
@@ -58,12 +59,12 @@ public class CommandParameter <CEC extends CommandExecutionContext<CEC, ? extend
private Map<String, String> flags;
private boolean canConsumeInput;
private boolean optionalResolver;
boolean consumesRest;
public CommandParameter(RegisteredCommand<CEC> command, Parameter param, int paramIndex) {
public CommandParameter(RegisteredCommand<CEC> command, Parameter param, int paramIndex, boolean isLast) {
this.parameter = param;
this.type = param.getType();
this.name = param.getName(); // do we care for an annotation to supply name?
//noinspection unchecked
this.manager = command.manager;
this.paramIndex = paramIndex;
Annotations annotations = manager.getAnnotations();
@@ -80,12 +81,13 @@ public class CommandParameter <CEC extends CommandExecutionContext<CEC, ? extend
));
}
this.optional = annotations.hasAnnotation(param, Optional.class) || this.defaultValue != null;
this.optional = annotations.hasAnnotation(param, Optional.class) || this.defaultValue != null || (isLast && type == String[].class);
this.optionalResolver = isOptionalResolver(resolver);
this.requiresInput = !this.optional && !this.optionalResolver;
//noinspection unchecked
this.commandIssuer = paramIndex == 0 && manager.isCommandIssuer(type);
this.canConsumeInput = !this.commandIssuer && !(resolver instanceof IssuerOnlyContextResolver);
this.consumesRest = (type == String.class && !annotations.hasAnnotation(param, Single.class)) || (isLast && type == String[].class);
this.values = annotations.getAnnotationValues(param, Values.class, Annotations.REPLACEMENTS | Annotations.NO_EMPTY);
@@ -0,0 +1,168 @@
/*
* Copyright (c) 2016-2019 Daniel Ennis (Aikar) - MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package co.aikar.commands;
import co.aikar.commands.apachecommonslang.ApacheCommonsLangUtil;
import com.google.common.collect.SetMultimap;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import static co.aikar.commands.BaseCommand.CATCHUNKNOWN;
import static co.aikar.commands.BaseCommand.DEFAULT;
class CommandRouter {
private final CommandManager manager;
CommandRouter(CommandManager manager) {
this.manager = manager;
}
CommandRouteResult matchCommand(RouteSearch search, boolean completion) {
Set<RegisteredCommand> cmds = search.commands;
String[] args = search.args;
if (!cmds.isEmpty()) {
if (cmds.size() == 1) {
return new CommandRouteResult(ACFUtil.getFirstElement(cmds), search);
} else {
Optional<RegisteredCommand> optCmd = cmds.stream()
.filter(c -> isProbableMatch(c, args, completion))
.min((c1, c2) -> {
int a = c1.consumeInputResolvers;
int b = c2.consumeInputResolvers;
if (a == b) {
return 0;
}
return a < b ? 1 : -1;
});
if (optCmd.isPresent()) {
return new CommandRouteResult(optCmd.get(), search);
}
}
}
return null;
}
/**
* @param c
* @param args
* @param completion
* @return
* @TODO: Improve this to be more accurate like @Default handling.
*/
private boolean isProbableMatch(RegisteredCommand c, String[] args, boolean completion) {
int required = c.requiredResolvers;
int optional = c.optionalResolvers;
return args.length <= required + optional && (completion || args.length >= required);
}
RouteSearch routeCommand(RootCommand command, String commandLabel, String[] args, boolean completion) {
SetMultimap<String, RegisteredCommand> subCommands = command.getSubCommands();
int argLength = args.length;
for (int i = argLength; i >= 0; i--) {
String subcommand = ApacheCommonsLangUtil.join(args, " ", 0, i).toLowerCase();
Set<RegisteredCommand> cmds = subCommands.get(subcommand);
if (!cmds.isEmpty()) {
return new RouteSearch(cmds, Arrays.copyOfRange(args, i, argLength), commandLabel, subcommand, completion);
}
}
Set<RegisteredCommand> defaultCommands = subCommands.get(DEFAULT);
Set<RegisteredCommand> unknownCommands = subCommands.get(CATCHUNKNOWN);
if (!defaultCommands.isEmpty()) {
Set<RegisteredCommand> matchedDefault = new HashSet<>();
for (RegisteredCommand c : defaultCommands) {
int required = c.requiredResolvers;
int optional = c.optionalResolvers;
CommandParameter lastParam = c.parameters.length > 0 ? c.parameters[c.parameters.length - 1] : null;
if (argLength <= required + optional || (
lastParam != null && (
lastParam.getType() == String[].class
||
(argLength >= required && lastParam.consumesRest)
)
)) {
matchedDefault.add(c);
}
}
if (!matchedDefault.isEmpty()) {
return new RouteSearch(matchedDefault, args, commandLabel, null, completion);
}
}
if (!unknownCommands.isEmpty()) {
return new RouteSearch(unknownCommands, args, commandLabel, null, completion);
}
return null;
}
RouteSearch routeCommand(String commandLabel, String[] args, boolean completion) {
return routeCommand(manager.getRootCommand(commandLabel), commandLabel, args, completion);
}
static class CommandRouteResult {
final RegisteredCommand cmd;
final String[] args;
final String commandLabel;
final String subcommand;
CommandRouteResult(RegisteredCommand cmd, RouteSearch search) {
this(cmd, search.args, search.subcommand, search.commandLabel);
}
CommandRouteResult(CommandRouteResult result, String[] args) {
this(result.cmd, args, result.subcommand, result.commandLabel);
}
CommandRouteResult(RegisteredCommand cmd, String[] args, String subcommand, String commandLabel) {
this.cmd = cmd;
this.args = args;
this.commandLabel = commandLabel;
this.subcommand = subcommand;
}
}
static class RouteSearch {
final String[] args;
final Set<RegisteredCommand> commands;
final String commandLabel;
final String subcommand;
RouteSearch(Set<RegisteredCommand> commands, String[] args, String commandLabel, String subcommand, boolean completion) {
this.commands = commands;
this.args = args;
this.commandLabel = commandLabel.toLowerCase();
this.subcommand = subcommand;
}
}
}
@@ -78,8 +78,9 @@ public class ForwardingCommand extends BaseCommand {
}
@Override
public void execute(CommandIssuer issuer, String commandLabel, String[] args) {
command.execute(issuer, commandLabel, ApacheCommonsLangUtil.addAll(baseArgs, args));
public void execute(CommandIssuer issuer, CommandRouter.CommandRouteResult result) {
result = new CommandRouter.CommandRouteResult(result, ApacheCommonsLangUtil.addAll(baseArgs, result.args));
command.execute(issuer, result);
}
BaseCommand getCommand() {
@@ -106,7 +106,7 @@ public class RegisteredCommand<CEC extends CommandExecutionContext<CEC, ? extend
StringBuilder syntaxBuilder = new StringBuilder(64);
for (int i = 0; i < parameters.length; i++) {
CommandParameter<CEC> parameter = this.parameters[i] = new CommandParameter<>(this, parameters[i], i);
CommandParameter<CEC> parameter = this.parameters[i] = new CommandParameter<>(this, parameters[i], i, i == parameters.length - 1);
if (!parameter.isCommandIssuer()) {
if (!parameter.requiresInput()) {
optionalResolvers++;
@@ -23,7 +23,8 @@
package co.aikar.commands;
import co.aikar.commands.apachecommonslang.ApacheCommonsLangUtil;
import co.aikar.commands.CommandRouter.CommandRouteResult;
import co.aikar.commands.CommandRouter.RouteSearch;
import com.google.common.collect.SetMultimap;
import java.util.ArrayList;
@@ -31,9 +32,6 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
import static co.aikar.commands.BaseCommand.CATCHUNKNOWN;
import static co.aikar.commands.BaseCommand.DEFAULT;
public interface RootCommand {
void addChild(BaseCommand command);
@@ -47,21 +45,7 @@ public interface RootCommand {
default void addChildShared(List<BaseCommand> children, SetMultimap<String, RegisteredCommand> subCommands, BaseCommand command) {
command.subCommands.entries().forEach(e -> {
String key = e.getKey();
RegisteredCommand registeredCommand = e.getValue();
if (key.equals(DEFAULT) || key.equals(BaseCommand.CATCHUNKNOWN)) {
return;
}
Set<RegisteredCommand> registered = subCommands.get(key);
if (!registered.isEmpty()) {
BaseCommand prevBase = registered.iterator().next().scope;
if (prevBase != registeredCommand.scope) {
this.getManager().log(LogLevel.ERROR, "ACF Error: " + command.getName() + " registered subcommand " + key + " for root command " + getCommandName() + " - but it is already defined in " + prevBase.getName());
this.getManager().log(LogLevel.ERROR, "2 subcommands of the same prefix may not be spread over 2 different classes. Ignoring this.");
return;
}
}
subCommands.put(key, registeredCommand);
subCommands.put(e.getKey(), e.getValue());
});
children.add(command);
@@ -105,35 +89,25 @@ public interface RootCommand {
}
default BaseCommand execute(CommandIssuer sender, String commandLabel, String[] args) {
BaseCommand command = getBaseCommand(args);
CommandRouter router = getManager().getRouter();
RouteSearch search = router.routeCommand(this, commandLabel, args, false);
BaseCommand defCommand = getDefCommand();
if (search != null) {
CommandRouteResult result = router.matchCommand(search, false);
if (result != null) {
BaseCommand scope = result.cmd.scope;
scope.execute(sender, result);
return scope;
}
command.execute(sender, commandLabel, args);
return command;
}
default BaseCommand getBaseCommand(String[] args) {
SetMultimap<String, RegisteredCommand> subCommands = getSubCommands();
RegisteredCommand command;
for (int i = args.length; i >= 0; i--) {
String checkSub = ApacheCommonsLangUtil.join(args, " ", 0, i).toLowerCase();
command = ACFUtil.getFirstElement(subCommands.get(checkSub));
if (command != null) {
return command.scope;
RegisteredCommand firstElement = ACFUtil.getFirstElement(search.commands);
if (firstElement != null) {
defCommand = firstElement.scope;
}
}
command = ACFUtil.getFirstElement(subCommands.get(DEFAULT));
if (command != null) {
if (args.length == 0 || command.consumeInputResolvers > 0) {
return command.scope;
}
}
command = ACFUtil.getFirstElement(subCommands.get(CATCHUNKNOWN));
if (command != null) {
return command.scope;
}
return getDefCommand();
defCommand.help(sender, args);
return defCommand;
}
default List<String> getTabCompletions(CommandIssuer sender, String alias, String[] args) {
@@ -1,9 +1,11 @@
package co.aikar.acfexample;
import co.aikar.commands.BaseCommand;
import co.aikar.commands.CommandHelp;
import co.aikar.commands.annotation.CatchUnknown;
import co.aikar.commands.annotation.CommandAlias;
import co.aikar.commands.annotation.Default;
import co.aikar.commands.annotation.HelpCommand;
import co.aikar.commands.annotation.Single;
import co.aikar.commands.annotation.Subcommand;
import org.bukkit.command.CommandSender;
@@ -31,4 +33,9 @@ public class SomeOtherCommand extends BaseCommand {
public void test(Player player, String string, @Default("1") int integer) {
player.sendMessage("Hi " + string + " - " + integer);
}
@HelpCommand
public void onHelp(CommandSender sender, CommandHelp help) {
help.showHelp();
}
}