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}