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}