    001/*
    002 * Copyright (c) 2016-2017 Daniel Ennis (Aikar) - MIT License
    003 *
    004 *  Permission is hereby granted, free of charge, to any person obtaining
    005 *  a copy of this software and associated documentation files (the
    006 *  "Software"), to deal in the Software without restriction, including
    007 *  without limitation the rights to use, copy, modify, merge, publish,
    008 *  distribute, sublicense, and/or sell copies of the Software, and to
    009 *  permit persons to whom the Software is furnished to do so, subject to
    010 *  the following conditions:
    011 *
    012 *  The above copyright notice and this permission notice shall be
    013 *  included in all copies or substantial portions of the Software.
    014 *
    015 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    016 *  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
    017 *  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
    018 *  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
    019 *  LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    020 *  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
    021 *  WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    022 */
    023
    024package co.aikar.commands;
    025
    026import co.aikar.commands.annotation.CatchAll;
    027import co.aikar.commands.annotation.CatchUnknown;
    028import co.aikar.commands.annotation.CommandAlias;
    029import co.aikar.commands.annotation.CommandPermission;
    030import co.aikar.commands.annotation.Conditions;
    031import co.aikar.commands.annotation.Default;
    032import co.aikar.commands.annotation.HelpCommand;
    033import co.aikar.commands.annotation.PreCommand;
    034import co.aikar.commands.annotation.Subcommand;
    035import co.aikar.commands.annotation.UnknownHandler;
    036import co.aikar.commands.apachecommonslang.ApacheCommonsLangUtil;
    037import com.google.common.collect.HashMultimap;
    038import com.google.common.collect.SetMultimap;
    039import org.jetbrains.annotations.Nullable;
    040
    041import java.lang.reflect.Constructor;
    042import java.lang.reflect.InvocationTargetException;
    043import java.lang.reflect.Method;
    044import java.lang.reflect.Parameter;
    045import java.util.ArrayList;
    046import java.util.Arrays;
    047import java.util.Collections;
    048import java.util.HashMap;
    049import java.util.HashSet;
    050import java.util.List;
    051import java.util.Map;
    052import java.util.Objects;
    053import java.util.Optional;
    054import java.util.Set;
    055import java.util.Stack;
    056import java.util.stream.Collectors;
    057import java.util.stream.Stream;
    058
    059/**
    060 * A Base command is defined as a command group of related commands.
    061 * A BaseCommand does not imply nor enforce that they use the same root command.
    062 * <p>
    063 * It is up to the end user how to organize their command. you could use 1 base command per
    064 * command in your application.
    065 * <p>
    066 * Optionally (and encouraged), you can use the base command to represent a root command, and
    067 * then each actionable command is a sub command
    068 */
    069
    070@SuppressWarnings("unused")
    071public abstract class BaseCommand {
    072
    073    /**
    074     * This is a field which contains the magic key in the {@link #subCommands} map for the method to catch any unknown
    075     * argument to command states.
    076     */
    077    static final String CATCHUNKNOWN = "__catchunknown";
    078    /**
    079     * This is a field which contains the magic key in the {@link #subCommands} map for the method which is default for the
    080     * entire base command.
    081     */
    082    static final String DEFAULT = "__default";
    083
    084    /**
    085     * A map of all the registered commands for this base command, keyed to each potential subcommand to access it.
    086     */
    087    final SetMultimap<String, RegisteredCommand> subCommands = HashMultimap.create();
    088
    089    /**
    090     * A map of flags to pass to Context Resolution for every parameter of the type. This is like an automatic @Flags on each.
    091     */
    092    final Map<Class<?>, String> contextFlags = new HashMap<>();
    093
    094    /**
    095     * What method was annoated with {@link PreCommand} to execute before commands.
    096     */
    097    @Nullable
    098    private Method preCommandHandler;
    099
    100    /**
    101     * What root command the user actually entered to access the currently executing command
    102     */
    103    @SuppressWarnings("WeakerAccess")
    104    private String execLabel;
    105    /**
    106     * What subcommand the user actually entered to access the currently executing command
    107     */
    108    @SuppressWarnings("WeakerAccess")
    109    private String execSubcommand;
    110    /**
    111     * What arguments the user actually entered after the root command to access the currently executing command
    112     */
    113    @SuppressWarnings("WeakerAccess")
    114    private String[] origArgs;
    115
    116    /**
    117     * The manager this is registered to
    118     */
    119    CommandManager<?, ?, ?, ?, ?, ?> manager = null;
    120
    121    /**
    122     * The command which owns this. This may be null if there are no owners.
    123     */
    124    BaseCommand parentCommand;
    125    Map<String, RootCommand> registeredCommands = new HashMap<>();
    126    /**
    127     * The description of the command. This may be null if no description has been provided.
    128     * Used for help documentation
    129     */
    130    @Nullable String description;
    131    /**
    132     * The name of the command. This may be null if no name has been provided.
    133     */
    134    @Nullable String commandName;
    135    /**
    136     * The permission of the command. This may be null if no permission has been provided.
    137     */
    138    @Nullable String permission;
    139    /**
    140     * The conditions of the command. This may be null if no conditions has been provided.
    141     */
    142    @Nullable String conditions;
    143    /**
    144     * Identifies if the command has an explicit help command annotated with {@link HelpCommand}
    145     */
    146    boolean hasHelpCommand;
    147
    148    /**
    149     * The handler of all uncaught exceptions thrown by the user's command implementation.
    150     */
    151    private ExceptionHandler exceptionHandler = null;
    152    /**
    153     * The last operative context data of this command. This may be null if this command hasn't been run yet.
    154     */
    155    private final ThreadLocal<CommandOperationContext> lastCommandOperationContext = new ThreadLocal<>();
    156    /**
    157     * If a parent exists to this command, and it has  a Subcommand annotation, prefix all subcommands in this class with this
    158     */
    159    @Nullable
    160    private String parentSubcommand;
    161
    162    /**
    163     * The permissions of the command.
    164     */
    165    private final Set<String> permissions = new HashSet<>();
    166
    167    public BaseCommand() {
    168    }
    169
    170    /**
    171     * Constructor based defining of commands will be removed in the next version bump.
    172     *
    173     * @param cmd
    174     * @deprecated Please switch to {@link CommandAlias} for defining all root commands.
    175     */
    176    @Deprecated
    177    public BaseCommand(@Nullable String cmd) {
    178        this.commandName = cmd;
    179    }
    180
    181    /**
    182     * Returns a reference to the last used CommandOperationContext.
    183     * This method is ThreadLocal, in that it can only be used on a thread that has executed a command
    184     *
    185     * @return
    186     */
    187    public CommandOperationContext getLastCommandOperationContext() {
    188        return lastCommandOperationContext.get();
    189    }
    190
    191    /**
    192     * Gets the root command name that the user actually typed
    193     *
    194     * @return Name
    195     */
    196    public String getExecCommandLabel() {
    197        return execLabel;
    198    }
    199
    200    /**
    201     * Gets the actual sub command name the user typed
    202     *
    203     * @return Name
    204     */
    205    public String getExecSubcommand() {
    206        return execSubcommand;
    207    }
    208
    209    /**
    210     * Gets the actual args in string form the user typed
    211     *
    212     * @return Args
    213     */
    214    public String[] getOrigArgs() {
    215        return origArgs;
    216    }
    217
    218    /**
    219     * This should be called whenever the command gets registered.
    220     * It sets all required fields correctly and injects dependencies.
    221     *
    222     * @param manager The manager to register as this command's owner and handler.
    223     */
    224    void onRegister(CommandManager manager) {
    225        onRegister(manager, this.commandName);
    226    }
    227
    228    /**
    229     * This should be called whenever the command gets registered.
    230     * It sets all required fields correctly and injects dependencies.
    231     *
    232     * @param manager The manager to register as this command's owner and handler.
    233     * @param cmd     The command name to use register with.
    234     */
    235    private void onRegister(CommandManager manager, String cmd) {
    236        manager.injectDependencies(this);
    237        this.manager = manager;
    238
    239        final Annotations annotations = manager.getAnnotations();
    240        final Class<? extends BaseCommand> self = this.getClass();
    241
    242        String[] cmdAliases = annotations.getAnnotationValues(self, CommandAlias.class, Annotations.REPLACEMENTS | Annotations.LOWERCASE | Annotations.NO_EMPTY);
    243
    244        if (cmd == null && cmdAliases != null) {
    245            cmd = cmdAliases[0];
    246        }
    247
    248        this.commandName = cmd != null ? cmd : self.getSimpleName().toLowerCase();
    249        this.permission = annotations.getAnnotationValue(self, CommandPermission.class, Annotations.REPLACEMENTS);
    250        this.description = this.commandName + " commands";
    251        this.parentSubcommand = getParentSubcommand(self);
    252        this.conditions = annotations.getAnnotationValue(self, Conditions.class, Annotations.REPLACEMENTS | Annotations.NO_EMPTY);
    253
    254        registerSubcommands();
    255        computePermissions();
    256        registerSubclasses(cmd);
    257
    258        if (cmdAliases != null) {
    259            Set<String> cmdList = new HashSet<>();
    260            Collections.addAll(cmdList, cmdAliases);
    261            cmdList.remove(cmd);
    262            for (String cmdAlias : cmdList) {
    263                register(cmdAlias, this);
    264            }
    265        }
    266
    267        if (cmd != null) {
    268            register(cmd, this);
    269        }
    270    }
    271
    272    /**
    273     * This recursively registers all subclasses of the command as subcommands, if they are of type {@link BaseCommand}.
    274     *
    275     * @param cmd The command name of this command.
    276     */
    277    private void registerSubclasses(String cmd) {
    278        for (Class<?> clazz : this.getClass().getDeclaredClasses()) {
    279            if (BaseCommand.class.isAssignableFrom(clazz)) {
    280                try {
    281                    BaseCommand subCommand = null;
    282                    Constructor<?>[] declaredConstructors = clazz.getDeclaredConstructors();
    283                    for (Constructor<?> declaredConstructor : declaredConstructors) {
    284
    285                        declaredConstructor.setAccessible(true);
    286                        Parameter[] parameters = declaredConstructor.getParameters();
    287                        if (parameters.length == 1) {
    288                            subCommand = (BaseCommand) declaredConstructor.newInstance(this);
    289                        } else {
    290                            manager.log(LogLevel.INFO, "Found unusable constructor: " + declaredConstructor.getName() + "(" + Stream.of(parameters).map(p -> p.getType().getSimpleName() + " " + p.getName()).collect(Collectors.joining("<c2>,</c2> ")) + ")");
    291                        }
    292                    }
    293                    if (subCommand != null) {
    294                        subCommand.parentCommand = this;
    295                        subCommand.onRegister(manager, cmd);
    296                        this.subCommands.putAll(subCommand.subCommands);
    297                        this.registeredCommands.putAll(subCommand.registeredCommands);
    298                    } else {
    299                        this.manager.log(LogLevel.ERROR, "Could not find a subcommand ctor for " + clazz.getName());
    300                    }
    301                } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
    302                    this.manager.log(LogLevel.ERROR, "Error registering subclass", e);
    303                }
    304            }
    305        }
    306    }
    307
    308    /**
    309     * This registers all subcommands of the command.
    310     */
    311    private void registerSubcommands() {
    312        final Annotations annotations = manager.getAnnotations();
    313        boolean foundDefault = false;
    314        boolean foundCatchUnknown = false;
    315        boolean isParentEmpty = parentSubcommand == null || parentSubcommand.isEmpty();
    316
    317        for (Method method : this.getClass().getMethods()) {
    318            method.setAccessible(true);
    319            String sublist = null;
    320            String sub = getSubcommandValue(method);
    321            final boolean def = annotations.hasAnnotation(method, Default.class);
    322            final String helpCommand = annotations.getAnnotationValue(method, HelpCommand.class, Annotations.NOTHING);
    323            final String commandAliases = annotations.getAnnotationValue(method, CommandAlias.class, Annotations.NOTHING);
    324
    325            if (!isParentEmpty && def) {
    326                sub = parentSubcommand;
    327            }
    328            if (isParentEmpty && (def || (!foundDefault && helpCommand != null))) {
    329                if (!foundDefault) {
    330                    if (def) {
    331                        this.subCommands.get(DEFAULT).clear();
    332                        foundDefault = true;
    333                    }
    334                    registerSubcommand(method, DEFAULT);
    335                } else {
    336                    ACFUtil.sneaky(new IllegalStateException("Multiple @Default/@HelpCommand commands, duplicate on " + method.getDeclaringClass().getName() + "#" + method.getName()));
    337                }
    338            }
    339
    340            if (sub != null) {
    341                sublist = sub;
    342            } else if (commandAliases != null) {
    343                sublist = commandAliases;
    344            } else if (helpCommand != null) {
    345                sublist = helpCommand;
    346                hasHelpCommand = true;
    347            }
    348
    349            boolean preCommand = annotations.hasAnnotation(method, PreCommand.class);
    350            boolean hasCatchUnknown = annotations.hasAnnotation(method, CatchUnknown.class) ||
    351                    annotations.hasAnnotation(method, CatchAll.class) ||
    352                    annotations.hasAnnotation(method, UnknownHandler.class);
    353
    354            if (hasCatchUnknown || (!foundCatchUnknown && helpCommand != null)) {
    355                if (!foundCatchUnknown) {
    356                    if (hasCatchUnknown) {
    357                        this.subCommands.get(CATCHUNKNOWN).clear();
    358                        foundCatchUnknown = true;
    359                    }
    360                    registerSubcommand(method, CATCHUNKNOWN);
    361                } else {
    362                    ACFUtil.sneaky(new IllegalStateException("Multiple @UnknownHandler/@HelpCommand commands, duplicate on " + method.getDeclaringClass().getName() + "#" + method.getName()));
    363                }
    364            } else if (preCommand) {
    365                if (this.preCommandHandler == null) {
    366                    this.preCommandHandler = method;
    367                } else {
    368                    ACFUtil.sneaky(new IllegalStateException("Multiple @PreCommand commands, duplicate on " + method.getDeclaringClass().getName() + "#" + method.getName()));
    369                }
    370            }
    371            if (Objects.equals(method.getDeclaringClass(), this.getClass()) && sublist != null) {
    372                registerSubcommand(method, sublist);
    373            }
    374        }
    375    }
    376
    377    /**
    378     * This registers all the permissions required to execute this command.
    379     */
    380    private void computePermissions() {
    381        this.permissions.clear();
    382        if (this.permission != null && !this.permission.isEmpty()) {
    383            this.permissions.addAll(Arrays.asList(ACFPatterns.COMMA.split(this.permission)));
    384        }
    385        if (this.parentCommand != null) {
    386            this.permissions.addAll(this.parentCommand.getRequiredPermissions());
    387        }
    388    }
    389
    390    /**
    391     * Gets the subcommand name of the method given.
    392     *
    393     * @param method The method to check.
    394     * @return The name of the subcommand. It returns null if the input doesn't have {@link Subcommand} attached.
    395     */
    396    private String getSubcommandValue(Method method) {
    397        final String sub = manager.getAnnotations().getAnnotationValue(method, Subcommand.class, Annotations.NOTHING);
    398        if (sub == null) {
    399            return null;
    400        }
    401        Class<?> clazz = method.getDeclaringClass();
    402        String parent = getParentSubcommand(clazz);
    403        return parent == null || parent.isEmpty() ? sub : parent + " " + sub;
    404    }
    405
    406    private String getParentSubcommand(Class<?> clazz) {
    407        List<String> subList = new ArrayList<>();
    408        while (clazz != null) {
    409            String sub = manager.getAnnotations().getAnnotationValue(clazz, Subcommand.class, Annotations.NOTHING);
    410            if (sub != null) {
    411                subList.add(sub);
    412            }
    413            clazz = clazz.getEnclosingClass();
    414        }
    415        Collections.reverse(subList);
    416        return ACFUtil.join(subList, " ");
    417    }
    418
    419    /**
    420     * Registers the given {@link BaseCommand cmd} as a child of the {@link RootCommand} linked to the name given.
    421     *
    422     * @param name Name of the parent to cmd.
    423     * @param cmd  The {@link BaseCommand} to add as a child to the {@link RootCommand} owned name field.
    424     */
    425    private void register(String name, BaseCommand cmd) {
    426        String nameLower = name.toLowerCase();
    427        RootCommand rootCommand = manager.obtainRootCommand(nameLower);
    428        rootCommand.addChild(cmd);
    429
    430        this.registeredCommands.put(nameLower, rootCommand);
    431    }
    432
    433    /**
    434     * Registers the given {@link Method} as a subcommand.
    435     *
    436     * @param method     The method to register as a subcommand.
    437     * @param subCommand The subcommand's name(s).
    438     */
    439    private void registerSubcommand(Method method, String subCommand) {
    440        subCommand = manager.getCommandReplacements().replace(subCommand.toLowerCase());
    441        final String[] subCommandParts = ACFPatterns.SPACE.split(subCommand);
    442        // Must run getSubcommandPossibility BEFORE we rewrite it just after this.
    443        Set<String> cmdList = getSubCommandPossibilityList(subCommandParts);
    444
    445        // Strip pipes off for auto complete addition
    446        for (int i = 0; i < subCommandParts.length; i++) {
    447            String[] split = ACFPatterns.PIPE.split(subCommandParts[i]);
    448            if (split.length == 0 || split[0].isEmpty()) {
    449                throw new IllegalArgumentException("Invalid @Subcommand configuration for " + method.getName() + " - parts can not start with | or be empty");
    450            }
    451            subCommandParts[i] = split[0];
    452        }
    453        String prefSubCommand = ApacheCommonsLangUtil.join(subCommandParts, " ");
    454        final String[] aliasNames = manager.getAnnotations().getAnnotationValues(method, CommandAlias.class, Annotations.REPLACEMENTS | Annotations.LOWERCASE);
    455
    456        String cmdName = aliasNames != null ? aliasNames[0] : this.commandName + " ";
    457        RegisteredCommand cmd = manager.createRegisteredCommand(this, cmdName, method, prefSubCommand);
    458
    459        for (String subcmd : cmdList) {
    460            subCommands.put(subcmd, cmd);
    461        }
    462        cmd.addSubcommands(cmdList);
    463
    464        if (aliasNames != null) {
    465            for (String name : aliasNames) {
    466                register(name, new ForwardingCommand(this, cmd, subCommandParts));
    467            }
    468        }
    469    }
    470
    471    /**
    472     * Takes a string like "foo|bar baz|qux" and generates a list of
    473     * - foo baz
    474     * - foo qux
    475     * - bar baz
    476     * - bar qux
    477     * <p>
    478     * For every possible sub command combination
    479     *
    480     * @param subCommandParts
    481     * @return List of all sub command possibilities
    482     */
    483    private static Set<String> getSubCommandPossibilityList(String[] subCommandParts) {
    484        int i = 0;
    485        Set<String> current = null;
    486        while (true) {
    487            Set<String> newList = new HashSet<>();
    488
    489            if (i < subCommandParts.length) {
    490                for (String s1 : ACFPatterns.PIPE.split(subCommandParts[i])) {
    491                    if (current != null) {
    492                        newList.addAll(current.stream().map(s -> s + " " + s1).collect(Collectors.toList()));
    493                    } else {
    494                        newList.add(s1);
    495                    }
    496                }
    497            }
    498
    499            if (i + 1 < subCommandParts.length) {
    500                current = newList;
    501                i = i + 1;
    502                continue;
    503            }
    504
    505            return newList;
    506        }
    507    }
    508
    509    public void execute(CommandIssuer issuer, String commandLabel, String[] args) {
    510        commandLabel = commandLabel.toLowerCase();
    511        try {
    512            CommandOperationContext commandContext = preCommandOperation(issuer, commandLabel, args, false);
    513
    514            if (args.length > 0) {
    515                CommandSearch cmd = findSubCommand(args);
    516                if (cmd != null) {
    517                    execSubcommand = cmd.getCheckSub();
    518                    final String[] execargs = Arrays.copyOfRange(args, cmd.argIndex, args.length);
    519                    executeCommand(commandContext, issuer, execargs, cmd.cmd);
    520                    return;
    521                }
    522            }
    523
    524            Set<RegisteredCommand> defaultCommands = subCommands.get(DEFAULT);
    525            RegisteredCommand defCommand = ACFUtil.getFirstElement(defaultCommands);
    526            if (defCommand != null && (args.length == 0 || defCommand.consumeInputResolvers > 0)) {
    527                findAndExecuteCommand(commandContext, DEFAULT, issuer, args);
    528            } else if (subCommands.get(CATCHUNKNOWN) != null) {
    529                if (!findAndExecuteCommand(commandContext, CATCHUNKNOWN, issuer, args)) {
    530                    help(issuer, args);
    531                }
    532            }
    533
    534        } finally {
    535            postCommandOperation();
    536        }
    537    }
    538
    539    /**
    540     * Gets the registered command of the given arguments.
    541     *
    542     * @param args The arguments given by the user.
    543     * @return The subcommand or null if none were found.
    544     * @see #findSubCommand(String[])
    545     */
    546    RegisteredCommand<?> getRegisteredCommand(String[] args) {
    547        final CommandSearch cmd = findSubCommand(args);
    548        return cmd != null ? cmd.cmd : null;
    549    }
    550
    551    /**
    552     * This is ran after any command operation has been performed.
    553     */
    554    private void postCommandOperation() {
    555        CommandManager.commandOperationContext.get().pop();
    556        execSubcommand = null;
    557        execLabel = null;
    558        origArgs = new String[]{};
    559    }
    560
    561    /**
    562     * This is ran before any command operation has been performed.
    563     *
    564     * @param issuer       The user who executed the command.
    565     * @param commandLabel The label the user used to execute the command. This is not the command name, but their input.
    566     *                     When there is multiple aliases, this is which alias was used
    567     * @param args         The arguments passed to the command when executing it.
    568     * @param isAsync      Whether the command is executed off of the main thread.
    569     * @return The context which is being registered to the {@link CommandManager}'s {@link
    570     * CommandManager#commandOperationContext thread local stack}.
    571     */
    572    private CommandOperationContext preCommandOperation(CommandIssuer issuer, String commandLabel, String[] args, boolean isAsync) {
    573        Stack<CommandOperationContext> contexts = CommandManager.commandOperationContext.get();
    574        CommandOperationContext context = this.manager.createCommandOperationContext(this, issuer, commandLabel, args, isAsync);
    575        contexts.push(context);
    576        lastCommandOperationContext.set(context);
    577        execSubcommand = null;
    578        execLabel = commandLabel;
    579        origArgs = args;
    580        return context;
    581    }
    582
    583    /**
    584     * Gets the current command issuer.
    585     *
    586     * @return The current command issuer.
    587     */
    588    public CommandIssuer getCurrentCommandIssuer() {
    589        return CommandManager.getCurrentCommandIssuer();
    590    }
    591
    592    /**
    593     * Gets the current command manager.
    594     *
    595     * @return The current command manager.
    596     */
    597    public CommandManager getCurrentCommandManager() {
    598        return CommandManager.getCurrentCommandManager();
    599    }
    600
    601    /**
    602     * Finds a subcommand of the given arguments.
    603     *
    604     * @param args The arguments the user input.
    605     * @return The identified subcommand.
    606     * @see #findSubCommand(String[], boolean)
    607     */
    608    private CommandSearch findSubCommand(String[] args) {
    609        return findSubCommand(args, false);
    610    }
    611
    612    /**
    613     * Finds a subcommand of the given arguments.
    614     *
    615     * @param args       The arguments the user input.
    616     * @param completion Whether or not completion of arguments should kick in. This may end up with worse than wanted results.
    617     * @return The identified subcommand.
    618     */
    619    private CommandSearch findSubCommand(String[] args, boolean completion) {
    620        for (int i = args.length; i >= 0; i--) {
    621            String checkSub = ApacheCommonsLangUtil.join(args, " ", 0, i).toLowerCase();
    622            Set<RegisteredCommand> cmds = subCommands.get(checkSub);
    623
    624            final int extraArgs = args.length - i;
    625            if (!cmds.isEmpty()) {
    626                RegisteredCommand cmd = null;
    627                if (cmds.size() == 1) {
    628                    cmd = ACFUtil.getFirstElement(cmds);
    629                } else {
    630                    Optional<RegisteredCommand> optCmd = cmds.stream().filter(c -> {
    631                        int required = c.requiredResolvers;
    632                        int optional = c.optionalResolvers;
    633                        return extraArgs <= required + optional && (completion || extraArgs >= required);
    634                    }).min((c1, c2) -> {
    635                        int a = c1.consumeInputResolvers;
    636                        int b = c2.consumeInputResolvers;
    637
    638                        if (a == b) {
    639                            return 0;
    640                        }
    641                        return a < b ? 1 : -1;
    642                    });
    643                    if (optCmd.isPresent()) {
    644                        cmd = optCmd.get();
    645                    }
    646                }
    647                if (cmd != null) {
    648                    return new CommandSearch(cmd, i, checkSub);
    649                }
    650            }
    651        }
    652        return null;
    653    }
    654
    655    private void executeCommand(CommandOperationContext commandOperationContext,
    656                                CommandIssuer issuer, String[] args, RegisteredCommand cmd) {
    657        if (cmd.hasPermission(issuer)) {
    658            commandOperationContext.setRegisteredCommand(cmd);
    659            if (checkPrecommand(commandOperationContext, cmd, issuer, args)) {
    660                return;
    661            }
    662            List<String> sargs = Arrays.asList(args);
    663            cmd.invoke(issuer, sargs, commandOperationContext);
    664        } else {
    665            issuer.sendMessage(MessageType.ERROR, MessageKeys.PERMISSION_DENIED);
    666        }
    667    }
    668
    669    /**
    670     * Please use command conditions for restricting execution
    671     *
    672     * @param issuer
    673     * @param cmd
    674     * @return
    675     * @deprecated See {@link CommandConditions}
    676     */
    677    @SuppressWarnings("DeprecatedIsStillUsed")
    678    @Deprecated
    679    public boolean canExecute(CommandIssuer issuer, RegisteredCommand<?> cmd) {
    680        return true;
    681    }
    682
    683    /**
    684     * Gets tab completed data from the given command from the user.
    685     *
    686     * @param issuer       The user who executed the tabcomplete.
    687     * @param commandLabel The label which is being used by the user.
    688     * @param args         The arguments the user has typed so far.
    689     * @return All possibilities in the tab complete.
    690     */
    691    public List<String> tabComplete(CommandIssuer issuer, String commandLabel, String[] args) {
    692        return tabComplete(issuer, commandLabel, args, false);
    693    }
    694
    695    /**
    696     * Gets the tab complete suggestions from a given command. This will automatically find anything
    697     * which is valid for the specified command through the command's implementation.
    698     *
    699     * @param issuer       The issuer of the command.
    700     * @param commandLabel The command name as entered by the user instead of the ACF registered name.
    701     * @param args         All arguments entered by the user.
    702     * @param isAsync      Whether this is run off of the main thread.
    703     * @return The possibilities to tab complete in no particular order.
    704     */
    705    @SuppressWarnings("WeakerAccess")
    706    public List<String> tabComplete(CommandIssuer issuer, String commandLabel, String[] args, boolean isAsync)
    707            throws IllegalArgumentException {
    708
    709        commandLabel = commandLabel.toLowerCase();
    710        if (args.length == 0) {
    711            args = new String[]{""};
    712        }
    713        try {
    714            CommandOperationContext commandOperationContext = preCommandOperation(issuer, commandLabel, args, isAsync);
    715
    716            final CommandSearch search = findSubCommand(args, true);
    717
    718
    719            final List<String> cmds = new ArrayList<>();
    720
    721            if (search != null) {
    722                cmds.addAll(completeCommand(issuer, search.cmd, Arrays.copyOfRange(args, search.argIndex, args.length), commandLabel, isAsync));
    723            } else if (subCommands.get(CATCHUNKNOWN).size() == 1) {
    724                cmds.addAll(completeCommand(issuer, ACFUtil.getFirstElement(subCommands.get(CATCHUNKNOWN)), args, commandLabel, isAsync));
    725            } else if (subCommands.get(DEFAULT).size() == 1) {
    726                cmds.addAll(completeCommand(issuer, ACFUtil.getFirstElement(subCommands.get(DEFAULT)), args, commandLabel, isAsync));
    727            }
    728
    729            return filterTabComplete(args[args.length - 1], cmds);
    730        } finally {
    731            postCommandOperation();
    732        }
    733    }
    734
    735    /**
    736     * Gets all subcommands which are possible to tabcomplete.
    737     *
    738     * @param issuer The command issuer.
    739     * @param args
    740     * @return
    741     */
    742    List<String> getCommandsForCompletion(CommandIssuer issuer, String[] args) {
    743        final Set<String> cmds = new HashSet<>();
    744        final int cmdIndex = Math.max(0, args.length - 1);
    745        String argString = ApacheCommonsLangUtil.join(args, " ").toLowerCase();
    746        for (Map.Entry<String, RegisteredCommand> entry : subCommands.entries()) {
    747            final String key = entry.getKey();
    748            if (key.startsWith(argString) && !CATCHUNKNOWN.equals(key) && !DEFAULT.equals(key)) {
    749                final RegisteredCommand value = entry.getValue();
    750                if (!value.hasPermission(issuer) || value.isPrivate) {
    751                    continue;
    752                }
    753
    754                String[] split = ACFPatterns.SPACE.split(value.prefSubCommand);
    755                cmds.add(split[cmdIndex]);
    756            }
    757        }
    758        return new ArrayList<>(cmds);
    759    }
    760
    761    /**
    762     * Complete a command properly per issuer and input.
    763     *
    764     * @param issuer       The user who executed this.
    765     * @param cmd          The command to be completed.
    766     * @param args         All arguments given by the user.
    767     * @param commandLabel The command name the user used.
    768     * @param isAsync      Whether the command was executed async.
    769     * @return All results to complete the command.
    770     */
    771    private List<String> completeCommand(CommandIssuer issuer, RegisteredCommand cmd, String[] args, String commandLabel, boolean isAsync) {
    772        if (!cmd.hasPermission(issuer) || args.length > cmd.consumeInputResolvers || args.length == 0 || cmd.complete == null) {
    773            return Collections.emptyList();
    774        }
    775
    776        List<String> cmds = manager.getCommandCompletions().of(cmd, issuer, args, isAsync);
    777        return filterTabComplete(args[args.length - 1], cmds);
    778    }
    779
    780    /**
    781     * Gets the actual args in string form the user typed
    782     * This returns a list of all tab complete options which are possible with the given argument and commands.
    783     *
    784     * @param arg  Argument which was pressed tab on.
    785     * @param cmds The possibilities to return.
    786     * @return All possible options. This may be empty.
    787     */
    788    private static List<String> filterTabComplete(String arg, List<String> cmds) {
    789        return cmds.stream()
    790                .distinct()
    791                .filter(cmd -> cmd != null && (arg.isEmpty() || ApacheCommonsLangUtil.startsWithIgnoreCase(cmd, arg)))
    792                .collect(Collectors.toList());
    793    }
    794
    795    /**
    796     * Gets a registered command under the given subcommand name.
    797     *
    798     * @param subcommand The name of the subcommand requested.
    799     * @return The subcommand found or null if none.
    800     */
    801    private RegisteredCommand getCommandBySubcommand(String subcommand) {
    802        return getCommandBySubcommand(subcommand, false);
    803    }
    804
    805    /**
    806     * Gets a registered command under the given name.
    807     * If requireOne is true, it won't accept more than a single matching subcommand.
    808     *
    809     * @param subcommand Name of the subcommand wanted.
    810     * @param requireOne Whether to only accept 1 result.
    811     * @return The subcommand found, or null if none/too many.
    812     */
    813    private RegisteredCommand getCommandBySubcommand(String subcommand, boolean requireOne) {
    814        final Set<RegisteredCommand> commands = subCommands.get(subcommand);
    815        if (!commands.isEmpty() && (!requireOne || commands.size() == 1)) {
    816            return commands.iterator().next();
    817        }
    818        return null;
    819    }
    820
    821    /**
    822     * Internally calls {@link #executeCommand(CommandOperationContext, CommandIssuer, String[], RegisteredCommand)}
    823     * and gets through {@link #getCommandBySubcommand(String)}.
    824     *
    825     * @param commandContext The command context to use.
    826     * @param subcommand     The subcommand to find the executor of.
    827     * @param issuer         The issuer who executed the subcommand.
    828     * @param args           All arguments given by the issuer.
    829     * @return Whether it found a command or not.
    830     * @see #executeCommand(CommandOperationContext, CommandIssuer, String[], RegisteredCommand)
    831     * @see #getCommandBySubcommand(String)
    832     * @see RegisteredCommand#invoke(CommandIssuer, List, CommandOperationContext)
    833     */
    834    private boolean findAndExecuteCommand(CommandOperationContext commandContext, String subcommand, CommandIssuer issuer, String... args) {
    835        final RegisteredCommand cmd = this.getCommandBySubcommand(subcommand);
    836        if (cmd != null) {
    837            executeCommand(commandContext, issuer, args, cmd);
    838            return true;
    839        }
    840
    841        return false;
    842    }
    843
    844    /**
    845     * Executes the precommand and sees whether something is wrong. Ideally, you get false from this.
    846     *
    847     * @param commandOperationContext The context to use.
    848     * @param cmd                     The command executed.
    849     * @param issuer                  The issuer who executed the command.
    850     * @param args                    The arguments the issuer provided.
    851     * @return Whether something went wrong.
    852     */
    853    private boolean checkPrecommand(CommandOperationContext commandOperationContext, RegisteredCommand cmd, CommandIssuer issuer, String[] args) {
    854        Method pre = this.preCommandHandler;
    855        if (pre != null) {
    856            try {
    857                Class<?>[] types = pre.getParameterTypes();
    858                Object[] parameters = new Object[pre.getParameterCount()];
    859                for (int i = 0; i < parameters.length; i++) {
    860                    Class<?> type = types[i];
    861                    Object issuerObject = issuer.getIssuer();
    862                    if (manager.isCommandIssuer(type) && type.isAssignableFrom(issuerObject.getClass())) {
    863                        parameters[i] = issuerObject;
    864                    } else if (CommandIssuer.class.isAssignableFrom(type)) {
    865                        parameters[i] = issuer;
    866                    } else if (RegisteredCommand.class.isAssignableFrom(type)) {
    867                        parameters[i] = cmd;
    868                    } else if (String[].class.isAssignableFrom((type))) {
    869                        parameters[i] = args;
    870                    } else {
    871                        parameters[i] = null;
    872                    }
    873                }
    874
    875                return (boolean) pre.invoke(this, parameters);
    876            } catch (IllegalAccessException | InvocationTargetException e) {
    877                this.manager.log(LogLevel.ERROR, "Exception encountered while command pre-processing", e);
    878            }
    879        }
    880        return false;
    881    }
    882
    883    /**
    884     * @deprecated Unstable API
    885     */
    886    @Deprecated
    887    @UnstableAPI
    888    public CommandHelp getCommandHelp() {
    889        return manager.generateCommandHelp();
    890    }
    891
    892    /**
    893     * @deprecated Unstable API
    894     */
    895    @Deprecated
    896    @UnstableAPI
    897    public void showCommandHelp() {
    898        getCommandHelp().showHelp();
    899    }
    900
    901    public void help(Object issuer, String[] args) {
    902        help(manager.getCommandIssuer(issuer), args);
    903    }
    904
    905    public void help(CommandIssuer issuer, String[] args) {
    906        issuer.sendMessage(MessageType.ERROR, MessageKeys.UNKNOWN_COMMAND);
    907    }
    908
    909    public void doHelp(Object issuer, String... args) {
    910        doHelp(manager.getCommandIssuer(issuer), args);
    911    }
    912
    913    public void doHelp(CommandIssuer issuer, String... args) {
    914        help(issuer, args);
    915    }
    916
    917    public void showSyntax(CommandIssuer issuer, RegisteredCommand<?> cmd) {
    918        issuer.sendMessage(MessageType.SYNTAX, MessageKeys.INVALID_SYNTAX,
    919                "{command}", manager.getCommandPrefix(issuer) + cmd.command,
    920                "{syntax}", cmd.syntaxText
    921        );
    922    }
    923
    924    public boolean hasPermission(Object issuer) {
    925        return hasPermission(manager.getCommandIssuer(issuer));
    926    }
    927
    928    public boolean hasPermission(CommandIssuer issuer) {
    929        return getRequiredPermissions().isEmpty() || getRequiredPermissions().stream().allMatch(permission -> manager.hasPermission(issuer, permission));
    930    }
    931
    932    public Set<String> getRequiredPermissions() {
    933        return this.permissions;
    934    }
    935
    936    public boolean requiresPermission(String permission) {
    937        return getRequiredPermissions().contains(permission);
    938    }
    939
    940    public String getName() {
    941        return commandName;
    942    }
    943
    944    public ExceptionHandler getExceptionHandler() {
    945        return exceptionHandler;
    946    }
    947
    948    public BaseCommand setExceptionHandler(ExceptionHandler exceptionHandler) {
    949        this.exceptionHandler = exceptionHandler;
    950        return this;
    951    }
    952
    953    public RegisteredCommand getDefaultRegisteredCommand() {
    954        return this.getCommandBySubcommand(DEFAULT);
    955    }
    956
    957    public String setContextFlags(Class<?> cls, String flags) {
    958        return this.contextFlags.put(cls, flags);
    959    }
    960
    961    public String getContextFlags(Class<?> cls) {
    962        return this.contextFlags.get(cls);
    963    }
    964
    965    public List<RegisteredCommand> getRegisteredCommands() {
    966        List<RegisteredCommand> registeredCommands = new ArrayList<>();
    967        registeredCommands.addAll(this.subCommands.values());
    968        return registeredCommands;
    969    }
    970
    971    private static class CommandSearch {
    972        RegisteredCommand cmd;
    973        int argIndex;
    974        String checkSub;
    975
    976        CommandSearch(RegisteredCommand cmd, int argIndex, String checkSub) {
    977            this.cmd = cmd;
    978            this.argIndex = argIndex;
    979            this.checkSub = checkSub;
    980        }
    981
    982        String getCheckSub() {
    983            return this.checkSub;
    984        }
    985
    986        @Override
    987        public boolean equals(Object o) {
    988            if (this == o) return true;
    989            if (o == null || getClass() != o.getClass()) return false;
    990            CommandSearch that = (CommandSearch) o;
    991            return argIndex == that.argIndex &&
    992                    Objects.equals(cmd, that.cmd) &&
    993                    Objects.equals(checkSub, that.checkSub);
    994        }
    995
    996        @Override
    997        public int hashCode() {
    998            return Objects.hash(cmd, argIndex, checkSub);
    999        }
    1000    }
    1001}