# Source code
source: package: co.aikar.commands, class: BaseCommand

    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.CommandRouter.RouteSearch;
    027import co.aikar.commands.annotation.CatchAll;
    028import co.aikar.commands.annotation.CatchUnknown;
    029import co.aikar.commands.annotation.CommandAlias;
    030import co.aikar.commands.annotation.CommandPermission;
    031import co.aikar.commands.annotation.Conditions;
    032import co.aikar.commands.annotation.Default;
    033import co.aikar.commands.annotation.Description;
    034import co.aikar.commands.annotation.HelpCommand;
    035import co.aikar.commands.annotation.PreCommand;
    036import co.aikar.commands.annotation.Subcommand;
    037import co.aikar.commands.annotation.UnknownHandler;
    038import co.aikar.commands.apachecommonslang.ApacheCommonsLangUtil;
    039import com.google.common.collect.HashMultimap;
    040import com.google.common.collect.SetMultimap;
    041import org.jetbrains.annotations.Nullable;
    042
    043import java.lang.reflect.Constructor;
    044import java.lang.reflect.InvocationTargetException;
    045import java.lang.reflect.Method;
    046import java.lang.reflect.Parameter;
    047import java.util.ArrayList;
    048import java.util.Arrays;
    049import java.util.Collections;
    050import java.util.HashMap;
    051import java.util.HashSet;
    052import java.util.LinkedHashSet;
    053import java.util.List;
    054import java.util.Locale;
    055import java.util.Map;
    056import java.util.Objects;
    057import java.util.Set;
    058import java.util.Stack;
    059import java.util.stream.Collectors;
    060import java.util.stream.Stream;
    061
    062/**
    063 * A Base command is defined as a command group of related commands.
    064 * A BaseCommand does not imply nor enforce that they use the same root command.
    065 * <p>
    066 * It is up to the end user how to organize their command. you could use 1 base command per
    067 * command in your application.
    068 * <p>
    069 * Optionally (and encouraged), you can use the base command to represent a root command, and
    070 * then each actionable command is a sub command
    071 */
    072
    073public abstract class BaseCommand {
    074
    075    /**
    076     * This is a field which contains the magic key in the {@link #subCommands} map for the method to catch any unknown
    077     * argument to command states.
    078     */
    079    static final String CATCHUNKNOWN = "__catchunknown";
    080    /**
    081     * This is a field which contains the magic key in the {@link #subCommands} map for the method which is default for the
    082     * entire base command.
    083     */
    084    static final String DEFAULT = "__default";
    085
    086    /**
    087     * A map of all the registered commands for this base command, keyed to each potential subcommand to access it.
    088     */
    089    final SetMultimap<String, RegisteredCommand> subCommands = HashMultimap.create();
    090    final Set<BaseCommand> subScopes = new HashSet<>();
    091
    092    /**
    093     * A map of flags to pass to Context Resolution for every parameter of the type. This is like an automatic @Flags on each.
    094     */
    095    final Map<Class<?>, String> contextFlags = new HashMap<>();
    096
    097    /**
    098     * What method was annoated with {@link PreCommand} to execute before commands.
    099     */
    100    @Nullable
    101    private Method preCommandHandler;
    102
    103    /**
    104     * What root command the user actually entered to access the currently executing command
    105     */
    106    @SuppressWarnings("WeakerAccess")
    107    private String execLabel;
    108    /**
    109     * What subcommand the user actually entered to access the currently executing command
    110     */
    111    @SuppressWarnings("WeakerAccess")
    112    private String execSubcommand;
    113    /**
    114     * What arguments the user actually entered after the root command to access the currently executing command
    115     */
    116    @SuppressWarnings("WeakerAccess")
    117    private String[] origArgs;
    118
    119    /**
    120     * The manager this is registered to
    121     */
    122    CommandManager<?, ?, ?, ?, ?, ?> manager = null;
    123
    124    /**
    125     * The command which owns this. This may be null if there are no owners.
    126     */
    127    BaseCommand parentCommand;
    128    Map<String, RootCommand> registeredCommands = new HashMap<>();
    129    /**
    130     * The description of the command. This may be null if no description has been provided.
    131     * Used for help documentation
    132     */
    133    @Nullable String description;
    134    /**
    135     * The name of the command. This may be null if no name has been provided.
    136     */
    137    @Nullable String commandName;
    138    /**
    139     * The permission of the command. This may be null if no permission has been provided.
    140     */
    141    @Nullable String permission;
    142    /**
    143     * The conditions of the command. This may be null if no conditions has been provided.
    144     */
    145    @Nullable String conditions;
    146    /**
    147     * Identifies if the command has an explicit help command annotated with {@link HelpCommand}
    148     */
    149    boolean hasHelpCommand;
    150
    151    /**
    152     * The handler of all uncaught exceptions thrown by the user's command implementation.
    153     */
    154    private ExceptionHandler exceptionHandler = null;
    155    /**
    156     * The last operative context data of this command. This may be null if this command is not currently being executed.
    157     */
    158    private final ThreadLocal<CommandOperationContext> lastCommandOperationContext = new ThreadLocal<>();
    159    /**
    160     * If a parent exists to this command, and it has  a Subcommand annotation, prefix all subcommands in this class with this
    161     */
    162    @Nullable
    163    private String parentSubcommand;
    164
    165    /**
    166     * The permissions of the command.
    167     */
    168    private final Set<String> permissions = new HashSet<>();
    169
    170    public BaseCommand() {
    171    }
    172
    173    /**
    174     * Constructor based defining of commands will be removed in the next version bump.
    175     *
    176     * @param cmd
    177     * @deprecated Please switch to {@link CommandAlias} for defining all root commands.
    178     */
    179    @Deprecated
    180    public BaseCommand(@Nullable String cmd) {
    181        this.commandName = cmd;
    182    }
    183
    184    /**
    185     * Returns a reference to the last used CommandOperationContext.
    186     * This method is ThreadLocal, in that it can only be used on a thread that has executed a command
    187     *
    188     * @return
    189     */
    190    public CommandOperationContext getLastCommandOperationContext() {
    191        return lastCommandOperationContext.get();
    192    }
    193
    194    /**
    195     * Gets the root command name that the user actually typed
    196     *
    197     * @return Name
    198     */
    199    public String getExecCommandLabel() {
    200        return execLabel;
    201    }
    202
    203    /**
    204     * Gets the actual sub command name the user typed
    205     *
    206     * @return Name
    207     */
    208    public String getExecSubcommand() {
    209        return execSubcommand;
    210    }
    211
    212    /**
    213     * Gets the actual args in string form the user typed
    214     *
    215     * @return Args
    216     */
    217    public String[] getOrigArgs() {
    218        return origArgs;
    219    }
    220
    221    /**
    222     * This should be called whenever the command gets registered.
    223     * It sets all required fields correctly and injects dependencies.
    224     *
    225     * @param manager The manager to register as this command's owner and handler.
    226     */
    227    void onRegister(CommandManager manager) {
    228        onRegister(manager, this.commandName);
    229    }
    230
    231    /**
    232     * This should be called whenever the command gets registered.
    233     * It sets all required fields correctly and injects dependencies.
    234     *
    235     * @param manager The manager to register as this command's owner and handler.
    236     * @param cmd     The command name to use register with.
    237     */
    238    private void onRegister(CommandManager manager, String cmd) {
    239        manager.injectDependencies(this);
    240        this.manager = manager;
    241
    242        final Annotations annotations = manager.getAnnotations();
    243        final Class<? extends BaseCommand> self = this.getClass();
    244
    245        String[] cmdAliases = annotations.getAnnotationValues(self, CommandAlias.class, Annotations.REPLACEMENTS | Annotations.LOWERCASE | Annotations.NO_EMPTY);
    246
    247        if (cmd == null && cmdAliases != null) {
    248            cmd = cmdAliases[0];
    249        }
    250
    251        this.commandName = cmd != null ? cmd : self.getSimpleName().toLowerCase(Locale.ENGLISH);
    252        this.permission = annotations.getAnnotationValue(self, CommandPermission.class, Annotations.REPLACEMENTS);
    253        this.description = annotations.getAnnotationValue(self, Description.class, Annotations.NO_EMPTY | Annotations.REPLACEMENTS);
    254        this.parentSubcommand = getParentSubcommand(self);
    255        this.conditions = annotations.getAnnotationValue(self, Conditions.class, Annotations.REPLACEMENTS | Annotations.NO_EMPTY);
    256
    257        computePermissions(); // Must be before any subcommands so they can inherit permissions
    258        registerSubcommands();
    259        registerSubclasses(cmd);
    260
    261        if (cmdAliases != null) {
    262            Set<String> cmdList = new HashSet<>();
    263            Collections.addAll(cmdList, cmdAliases);
    264            cmdList.remove(cmd);
    265            for (String cmdAlias : cmdList) {
    266                register(cmdAlias, this);
    267            }
    268        }
    269
    270        if (cmd != null) {
    271            register(cmd, this);
    272        }
    273    }
    274
    275    /**
    276     * This recursively registers all subclasses of the command as subcommands, if they are of type {@link BaseCommand}.
    277     *
    278     * @param cmd The command name of this command.
    279     */
    280    private void registerSubclasses(String cmd) {
    281        for (Class<?> clazz : this.getClass().getDeclaredClasses()) {
    282            if (BaseCommand.class.isAssignableFrom(clazz)) {
    283                try {
    284                    BaseCommand subScope = null;
    285                    Constructor<?>[] declaredConstructors = clazz.getDeclaredConstructors();
    286                    for (Constructor<?> declaredConstructor : declaredConstructors) {
    287
    288                        declaredConstructor.setAccessible(true);
    289                        Parameter[] parameters = declaredConstructor.getParameters();
    290                        if (parameters.length == 1) {
    291                            subScope = (BaseCommand) declaredConstructor.newInstance(this);
    292                        } else {
    293                            manager.log(LogLevel.INFO, "Found unusable constructor: " + declaredConstructor.getName() + "(" + Stream.of(parameters).map(p -> p.getType().getSimpleName() + " " + p.getName()).collect(Collectors.joining("<c2>,</c2> ")) + ")");
    294                        }
    295                    }
    296                    if (subScope != null) {
    297                        subScope.parentCommand = this;
    298                        this.subScopes.add(subScope);
    299                        subScope.onRegister(manager, cmd);
    300                        this.subCommands.putAll(subScope.subCommands);
    301                        this.registeredCommands.putAll(subScope.registeredCommands);
    302                    } else {
    303                        this.manager.log(LogLevel.ERROR, "Could not find a subcommand ctor for " + clazz.getName());
    304                    }
    305                } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
    306                    this.manager.log(LogLevel.ERROR, "Error registering subclass", e);
    307                }
    308            }
    309        }
    310    }
    311
    312    /**
    313     * This registers all subcommands of the command.
    314     */
    315    private void registerSubcommands() {
    316        final Annotations annotations = manager.getAnnotations();
    317        boolean foundCatchUnknown = false;
    318        boolean isParentEmpty = parentSubcommand == null || parentSubcommand.isEmpty();
    319        Set<Method> methods = new LinkedHashSet<>();
    320        Collections.addAll(methods, this.getClass().getDeclaredMethods());
    321        Collections.addAll(methods, this.getClass().getMethods());
    322
    323        for (Method method : methods) {
    324            method.setAccessible(true);
    325            String sublist = null;
    326            String sub = getSubcommandValue(method);
    327            final String helpCommand = annotations.getAnnotationValue(method, HelpCommand.class, Annotations.NOTHING);
    328            final String commandAliases = annotations.getAnnotationValue(method, CommandAlias.class, Annotations.NOTHING);
    329
    330            if (annotations.hasAnnotation(method, Default.class)) {
    331                if (!isParentEmpty) {
    332                    sub = parentSubcommand;
    333                } else {
    334                    registerSubcommand(method, DEFAULT);
    335                }
    336            }
    337
    338            if (sub != null) {
    339                sublist = sub;
    340            } else if (commandAliases != null) {
    341                sublist = commandAliases;
    342            } else if (helpCommand != null) {
    343                sublist = helpCommand;
    344                hasHelpCommand = true;
    345            }
    346
    347            boolean preCommand = annotations.hasAnnotation(method, PreCommand.class);
    348            boolean hasCatchUnknown = annotations.hasAnnotation(method, CatchUnknown.class) ||
    349                    annotations.hasAnnotation(method, CatchAll.class) ||
    350                    annotations.hasAnnotation(method, UnknownHandler.class);
    351
    352            if (hasCatchUnknown || (!foundCatchUnknown && helpCommand != null)) {
    353                if (!foundCatchUnknown) {
    354                    if (hasCatchUnknown) {
    355                        this.subCommands.get(CATCHUNKNOWN).clear();
    356                        foundCatchUnknown = true;
    357                    }
    358                    registerSubcommand(method, CATCHUNKNOWN);
    359                } else {
    360                    ACFUtil.sneaky(new IllegalStateException("Multiple @CatchUnknown/@HelpCommand commands, duplicate on " + method.getDeclaringClass().getName() + "#" + method.getName()));
    361                }
    362            } else if (preCommand) {
    363                if (this.preCommandHandler == null) {
    364                    this.preCommandHandler = method;
    365                } else {
    366                    ACFUtil.sneaky(new IllegalStateException("Multiple @PreCommand commands, duplicate on " + method.getDeclaringClass().getName() + "#" + method.getName()));
    367                }
    368            }
    369            if (Objects.equals(method.getDeclaringClass(), this.getClass()) && sublist != null) {
    370                registerSubcommand(method, sublist);
    371            }
    372        }
    373    }
    374
    375    /**
    376     * This registers all the permissions required to execute this command.
    377     */
    378    private void computePermissions() {
    379        this.permissions.clear();
    380        if (this.permission != null && !this.permission.isEmpty()) {
    381            this.permissions.addAll(Arrays.asList(ACFPatterns.COMMA.split(this.permission)));
    382        }
    383        if (this.parentCommand != null) {
    384            this.permissions.addAll(this.parentCommand.getRequiredPermissions());
    385        }
    386        this.subCommands.values().forEach(RegisteredCommand::computePermissions);
    387        this.subScopes.forEach(BaseCommand::computePermissions);
    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(Locale.ENGLISH);
    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(Locale.ENGLISH));
    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    void execute(CommandIssuer issuer, CommandRouter.CommandRouteResult command) {
    510        try {
    511            CommandOperationContext commandContext = preCommandOperation(issuer, command.commandLabel, command.args, false);
    512            execSubcommand = command.subcommand;
    513            executeCommand(commandContext, issuer, command.args, command.cmd);
    514        } finally {
    515            postCommandOperation();
    516        }
    517    }
    518
    519    /**
    520     * This is ran after any command operation has been performed.
    521     */
    522    private void postCommandOperation() {
    523        CommandManager.commandOperationContext.get().pop();
    524        lastCommandOperationContext.set(null);
    525        execSubcommand = null;
    526        execLabel = null;
    527        origArgs = new String[]{};
    528    }
    529
    530    /**
    531     * This is ran before any command operation has been performed.
    532     *
    533     * @param issuer       The user who executed the command.
    534     * @param commandLabel The label the user used to execute the command. This is not the command name, but their input.
    535     *                     When there is multiple aliases, this is which alias was used
    536     * @param args         The arguments passed to the command when executing it.
    537     * @param isAsync      Whether the command is executed off of the main thread.
    538     * @return The context which is being registered to the {@link CommandManager}'s {@link
    539     * CommandManager#commandOperationContext thread local stack}.
    540     */
    541    private CommandOperationContext preCommandOperation(CommandIssuer issuer, String commandLabel, String[] args, boolean isAsync) {
    542        Stack<CommandOperationContext> contexts = CommandManager.commandOperationContext.get();
    543        CommandOperationContext context = this.manager.createCommandOperationContext(this, issuer, commandLabel, args, isAsync);
    544        contexts.push(context);
    545        lastCommandOperationContext.set(context);
    546        execSubcommand = null;
    547        execLabel = commandLabel;
    548        origArgs = args;
    549        return context;
    550    }
    551
    552    /**
    553     * Gets the current command issuer.
    554     *
    555     * @return The current command issuer.
    556     */
    557    public CommandIssuer getCurrentCommandIssuer() {
    558        return CommandManager.getCurrentCommandIssuer();
    559    }
    560
    561    /**
    562     * Gets the current command manager.
    563     *
    564     * @return The current command manager.
    565     */
    566    public CommandManager getCurrentCommandManager() {
    567        return CommandManager.getCurrentCommandManager();
    568    }
    569
    570    private void executeCommand(CommandOperationContext commandOperationContext,
    571                                CommandIssuer issuer, String[] args, RegisteredCommand cmd) {
    572        if (cmd.hasPermission(issuer)) {
    573            commandOperationContext.setRegisteredCommand(cmd);
    574            if (checkPrecommand(commandOperationContext, cmd, issuer, args)) {
    575                return;
    576            }
    577            List<String> sargs = Arrays.asList(args);
    578            cmd.invoke(issuer, sargs, commandOperationContext);
    579        } else {
    580            issuer.sendMessage(MessageType.ERROR, MessageKeys.PERMISSION_DENIED);
    581        }
    582    }
    583
    584    /**
    585     * Please use command conditions for restricting execution
    586     *
    587     * @param issuer
    588     * @param cmd
    589     * @return
    590     * @deprecated See {@link CommandConditions}
    591     */
    592    @SuppressWarnings("DeprecatedIsStillUsed")
    593    @Deprecated
    594    public boolean canExecute(CommandIssuer issuer, RegisteredCommand<?> cmd) {
    595        return true;
    596    }
    597
    598    /**
    599     * Gets tab completed data from the given command from the user.
    600     *
    601     * @param issuer       The user who executed the tabcomplete.
    602     * @param commandLabel The label which is being used by the user.
    603     * @param args         The arguments the user has typed so far.
    604     * @return All possibilities in the tab complete.
    605     */
    606    public List<String> tabComplete(CommandIssuer issuer, String commandLabel, String[] args) {
    607        return tabComplete(issuer, commandLabel, args, false);
    608    }
    609
    610    /**
    611     * Gets the tab complete suggestions from a given command. This will automatically find anything
    612     * which is valid for the specified command through the command's implementation.
    613     *
    614     * @param issuer       The issuer of the command.
    615     * @param commandLabel The command name as entered by the user instead of the ACF registered name.
    616     * @param args         All arguments entered by the user.
    617     * @param isAsync      Whether this is run off of the main thread.
    618     * @return The possibilities to tab complete in no particular order.
    619     */
    620    @SuppressWarnings("WeakerAccess")
    621    public List<String> tabComplete(CommandIssuer issuer, String commandLabel, String[] args, boolean isAsync)
    622            throws IllegalArgumentException {
    623        return tabComplete(issuer, manager.getRootCommand(commandLabel.toLowerCase(Locale.ENGLISH)), args, isAsync);
    624    }
    625
    626    List<String> tabComplete(CommandIssuer issuer, RootCommand rootCommand, String[] args, boolean isAsync)
    627            throws IllegalArgumentException {
    628        if (args.length == 0) {
    629            args = new String[]{""};
    630        }
    631        String commandLabel = rootCommand.getCommandName();
    632        try {
    633            CommandRouter router = manager.getRouter();
    634
    635            preCommandOperation(issuer, commandLabel, args, isAsync);
    636
    637            final RouteSearch search = router.routeCommand(rootCommand, commandLabel, args, true);
    638
    639            final List<String> cmds = new ArrayList<>();
    640            if (search != null) {
    641                for (RegisteredCommand<?> command : search.commands) {
    642                    cmds.addAll(completeCommand(issuer, command, search.args, commandLabel, isAsync));
    643                }
    644            }
    645
    646            return filterTabComplete(args[args.length - 1], cmds);
    647        } finally {
    648            postCommandOperation();
    649        }
    650    }
    651
    652    /**
    653     * Gets all subcommands which are possible to tabcomplete.
    654     *
    655     * @param issuer The command issuer.
    656     * @param args
    657     * @return
    658     */
    659    List<String> getCommandsForCompletion(CommandIssuer issuer, String[] args) {
    660        final Set<String> cmds = new HashSet<>();
    661        final int cmdIndex = Math.max(0, args.length - 1);
    662        String argString = ApacheCommonsLangUtil.join(args, " ").toLowerCase(Locale.ENGLISH);
    663        for (Map.Entry<String, RegisteredCommand> entry : subCommands.entries()) {
    664            final String key = entry.getKey();
    665            if (key.startsWith(argString) && !isSpecialSubcommand(key)) {
    666                final RegisteredCommand value = entry.getValue();
    667                if (!value.hasPermission(issuer) || value.isPrivate) {
    668                    continue;
    669                }
    670
    671                String[] split = ACFPatterns.SPACE.split(value.prefSubCommand);
    672                cmds.add(split[cmdIndex]);
    673            }
    674        }
    675        return new ArrayList<>(cmds);
    676    }
    677
    678    static boolean isSpecialSubcommand(String key) {
    679        return CATCHUNKNOWN.equals(key) || DEFAULT.equals(key);
    680    }
    681
    682    /**
    683     * Complete a command properly per issuer and input.
    684     *
    685     * @param issuer       The user who executed this.
    686     * @param cmd          The command to be completed.
    687     * @param args         All arguments given by the user.
    688     * @param commandLabel The command name the user used.
    689     * @param isAsync      Whether the command was executed async.
    690     * @return All results to complete the command.
    691     */
    692    private List<String> completeCommand(CommandIssuer issuer, RegisteredCommand cmd, String[] args, String commandLabel, boolean isAsync) {
    693        if (!cmd.hasPermission(issuer) || args.length == 0 || cmd.parameters.length == 0) {
    694            return Collections.emptyList();
    695        }
    696
    697        if (!cmd.parameters[cmd.parameters.length - 1].consumesRest && args.length > cmd.consumeInputResolvers) {
    698            return Collections.emptyList();
    699        }
    700
    701        List<String> cmds = manager.getCommandCompletions().of(cmd, issuer, args, isAsync);
    702        return filterTabComplete(args[args.length - 1], cmds);
    703    }
    704
    705    /**
    706     * Gets the actual args in string form the user typed
    707     * This returns a list of all tab complete options which are possible with the given argument and commands.
    708     *
    709     * @param arg  Argument which was pressed tab on.
    710     * @param cmds The possibilities to return.
    711     * @return All possible options. This may be empty.
    712     */
    713    private static List<String> filterTabComplete(String arg, List<String> cmds) {
    714        return cmds.stream()
    715                .distinct()
    716                .filter(cmd -> cmd != null && (arg.isEmpty() || ApacheCommonsLangUtil.startsWithIgnoreCase(cmd, arg)))
    717                .collect(Collectors.toList());
    718    }
    719
    720    /**
    721     * Executes the precommand and sees whether something is wrong. Ideally, you get false from this.
    722     *
    723     * @param commandOperationContext The context to use.
    724     * @param cmd                     The command executed.
    725     * @param issuer                  The issuer who executed the command.
    726     * @param args                    The arguments the issuer provided.
    727     * @return Whether something went wrong.
    728     */
    729    private boolean checkPrecommand(CommandOperationContext commandOperationContext, RegisteredCommand cmd, CommandIssuer issuer, String[] args) {
    730        Method pre = this.preCommandHandler;
    731        if (pre != null) {
    732            try {
    733                Class<?>[] types = pre.getParameterTypes();
    734                Object[] parameters = new Object[pre.getParameterCount()];
    735                for (int i = 0; i < parameters.length; i++) {
    736                    Class<?> type = types[i];
    737                    Object issuerObject = issuer.getIssuer();
    738                    if (manager.isCommandIssuer(type) && type.isAssignableFrom(issuerObject.getClass())) {
    739                        parameters[i] = issuerObject;
    740                    } else if (CommandIssuer.class.isAssignableFrom(type)) {
    741                        parameters[i] = issuer;
    742                    } else if (RegisteredCommand.class.isAssignableFrom(type)) {
    743                        parameters[i] = cmd;
    744                    } else if (String[].class.isAssignableFrom((type))) {
    745                        parameters[i] = args;
    746                    } else {
    747                        parameters[i] = null;
    748                    }
    749                }
    750
    751                return (boolean) pre.invoke(this, parameters);
    752            } catch (IllegalAccessException | InvocationTargetException e) {
    753                this.manager.log(LogLevel.ERROR, "Exception encountered while command pre-processing", e);
    754            }
    755        }
    756        return false;
    757    }
    758
    759    /**
    760     * @deprecated Unstable API
    761     */
    762    @Deprecated
    763    @UnstableAPI
    764    public CommandHelp getCommandHelp() {
    765        return manager.generateCommandHelp();
    766    }
    767
    768    /**
    769     * @deprecated Unstable API
    770     */
    771    @Deprecated
    772    @UnstableAPI
    773    public void showCommandHelp() {
    774        getCommandHelp().showHelp();
    775    }
    776
    777    public void help(Object issuer, String[] args) {
    778        help(manager.getCommandIssuer(issuer), args);
    779    }
    780
    781    public void help(CommandIssuer issuer, String[] args) {
    782        issuer.sendMessage(MessageType.ERROR, MessageKeys.UNKNOWN_COMMAND);
    783    }
    784
    785    public void doHelp(Object issuer, String... args) {
    786        doHelp(manager.getCommandIssuer(issuer), args);
    787    }
    788
    789    public void doHelp(CommandIssuer issuer, String... args) {
    790        help(issuer, args);
    791    }
    792
    793    public void showSyntax(CommandIssuer issuer, RegisteredCommand<?> cmd) {
    794        issuer.sendMessage(MessageType.SYNTAX, MessageKeys.INVALID_SYNTAX,
    795                "{command}", manager.getCommandPrefix(issuer) + cmd.command,
    796                "{syntax}", cmd.getSyntaxText(issuer)
    797        );
    798    }
    799
    800    public boolean hasPermission(Object issuer) {
    801        return hasPermission(manager.getCommandIssuer(issuer));
    802    }
    803
    804    public boolean hasPermission(CommandIssuer issuer) {
    805        return this.manager.hasPermission(issuer, getRequiredPermissions());
    806    }
    807
    808    public Set<String> getRequiredPermissions() {
    809        return this.permissions;
    810    }
    811
    812    public boolean requiresPermission(String permission) {
    813        return getRequiredPermissions().contains(permission);
    814    }
    815
    816    public String getName() {
    817        return commandName;
    818    }
    819
    820    public ExceptionHandler getExceptionHandler() {
    821        return exceptionHandler;
    822    }
    823
    824    public BaseCommand setExceptionHandler(ExceptionHandler exceptionHandler) {
    825        this.exceptionHandler = exceptionHandler;
    826        return this;
    827    }
    828
    829    public RegisteredCommand getDefaultRegisteredCommand() {
    830        return ACFUtil.getFirstElement(this.subCommands.get(DEFAULT));
    831    }
    832
    833    public String setContextFlags(Class<?> cls, String flags) {
    834        return this.contextFlags.put(cls, flags);
    835    }
    836
    837    public String getContextFlags(Class<?> cls) {
    838        return this.contextFlags.get(cls);
    839    }
    840
    841    public List<RegisteredCommand> getRegisteredCommands() {
    842        List<RegisteredCommand> registeredCommands = new ArrayList<>();
    843        registeredCommands.addAll(this.subCommands.values());
    844        return registeredCommands;
    845    }
    846
    847    protected SetMultimap<String, RegisteredCommand> getSubCommands() {
    848        return subCommands;
    849    }
    850}