mirror of
https://github.com/aikar/commands.git
synced 2026-06-28 01:18:26 +00:00
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:
+15
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user