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.ImmutableList;
039import com.google.common.collect.ImmutableSet;
040import com.google.common.collect.Iterables;
041import com.google.common.collect.Lists;
042import com.google.common.collect.Maps;
043import com.google.common.collect.SetMultimap;
044import com.google.common.collect.Sets;
045
046import java.lang.reflect.Constructor;
047import java.lang.reflect.InvocationTargetException;
048import java.lang.reflect.Method;
049import java.lang.reflect.Parameter;
050import java.util.ArrayList;
051import java.util.Arrays;
052import java.util.Collections;
053import java.util.HashMap;
054import java.util.HashSet;
055import java.util.List;
056import java.util.Map;
057import java.util.Objects;
058import java.util.Optional;
059import java.util.Set;
060import java.util.Stack;
061import java.util.stream.Collectors;
062import java.util.stream.Stream;
063
064@SuppressWarnings("unused")
065public abstract class BaseCommand {
066
067    public static final String CATCHUNKNOWN = "__catchunknown";
068    public static final String DEFAULT = "__default";
069    final SetMultimap<String, RegisteredCommand> subCommands = HashMultimap.create();
070    final Map<Class<?>, String> contextFlags = Maps.newHashMap();
071    private Method preCommandHandler;
072
073    @SuppressWarnings("WeakerAccess")
074    private String execLabel;
075    @SuppressWarnings("WeakerAccess")
076    private String execSubcommand;
077    @SuppressWarnings("WeakerAccess")
078    private String[] origArgs;
079    CommandManager<?, ?, ?, ?, ?, ?> manager = null;
080    BaseCommand parentCommand;
081    Map<String, RootCommand> registeredCommands = new HashMap<>();
082    String description;
083    String commandName;
084    String permission;
085    String conditions;
086
087    private ExceptionHandler exceptionHandler = null;
088    CommandOperationContext lastCommandOperationContext;
089    private String parentSubcommand;
090
091    public BaseCommand() {}
092    public BaseCommand(String cmd) {
093        this.commandName = cmd;
094    }
095
096    /**
097     * Gets the root command name that the user actually typed
098     * @return Name
099     */
100    public String getExecCommandLabel() {
101        return execLabel;
102    }
103
104    /**
105     * Gets the actual sub command name the user typed
106     * @return Name
107     */
108    public String getExecSubcommand() {
109        return execSubcommand;
110    }
111
112    /**
113     * Gets the actual args in string form the user typed
114     * @return Args
115     */
116    public String[] getOrigArgs() {
117        return origArgs;
118    }
119
120    void setParentCommand(BaseCommand command) {
121        this.parentCommand = command;
122    }
123    void onRegister(CommandManager manager) {
124        onRegister(manager, this.commandName);
125    }
126    void onRegister(CommandManager manager, String cmd) {
127        manager.injectDependencies(this);
128        this.manager = manager;
129
130        final Annotations annotations = manager.getAnnotations();
131        final Class<? extends BaseCommand> self = this.getClass();
132
133        String[] cmdAliases = annotations.getAnnotationValues(self, CommandAlias.class, Annotations.REPLACEMENTS | Annotations.LOWERCASE | Annotations.NO_EMPTY);
134
135        if (cmd == null && cmdAliases != null) {
136            cmd = cmdAliases[0];
137        }
138
139        this.commandName = cmd != null ? cmd : self.getSimpleName().toLowerCase();
140        this.permission = annotations.getAnnotationValue(self, CommandPermission.class, Annotations.REPLACEMENTS);
141        this.description = this.commandName + " commands";
142        this.parentSubcommand = getParentSubcommand(self);
143        this.conditions = annotations.getAnnotationValue(self, Conditions.class, Annotations.REPLACEMENTS | Annotations.NO_EMPTY);
144
145        registerSubcommands();
146
147        if (cmdAliases != null) {
148            Set<String> cmdList = new HashSet<>();
149            Collections.addAll(cmdList, cmdAliases);
150            cmdList.remove(cmd);
151            for (String cmdAlias : cmdList) {
152                register(cmdAlias, this);
153            }
154        }
155
156        if (cmd != null) {
157            register(cmd, this);
158        }
159        registerSubclasses(cmd);
160
161    }
162
163    private void registerSubclasses(String cmd) {
164        for (Class<?> clazz : this.getClass().getDeclaredClasses()) {
165            if (BaseCommand.class.isAssignableFrom(clazz)) {
166                try {
167                    BaseCommand subCommand = null;
168                    Constructor<?>[] declaredConstructors = clazz.getDeclaredConstructors();
169                    for (Constructor<?> declaredConstructor : declaredConstructors) {
170
171                        declaredConstructor.setAccessible(true);
172                        Parameter[] parameters = declaredConstructor.getParameters();
173                        if (parameters.length == 1) {
174                            subCommand = (BaseCommand) declaredConstructor.newInstance(this);
175                        } else {
176                            manager.log(LogLevel.INFO, "Found unusable constructor: " + declaredConstructor.getName() + "(" + Stream.of(parameters).map(p -> p.getType().getSimpleName() + " " + p.getName()).collect(Collectors.joining("<c2>,</c2> ")) + ")");
177                        }
178                    }
179                    if (subCommand != null) {
180                        subCommand.setParentCommand(this);
181                        subCommand.onRegister(manager, cmd);
182                        this.subCommands.putAll(subCommand.subCommands);
183                        this.registeredCommands.putAll(subCommand.registeredCommands);
184                    } else {
185                        this.manager.log(LogLevel.ERROR, "Could not find a subcommand ctor for " + clazz.getName());
186                    }
187                } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
188                    this.manager.log(LogLevel.ERROR, "Error registering subclass", e);
189                }
190            }
191        }
192    }
193
194    private void registerSubcommands() {
195        final Annotations annotations = manager.getAnnotations();
196        boolean foundDefault = false;
197        boolean foundCatchUnknown = false;
198        boolean isParentEmpty = parentSubcommand.isEmpty();
199
200        for (Method method : this.getClass().getMethods()) {
201            method.setAccessible(true);
202            String sublist = null;
203            String sub = getSubcommandValue(method);
204            final boolean def = annotations.hasAnnotation(method, Default.class);
205            final String helpCommand = annotations.getAnnotationValue(method, HelpCommand.class, Annotations.NOTHING);
206            final String commandAliases = annotations.getAnnotationValue(method, CommandAlias.class, Annotations.NOTHING);
207
208            if (!isParentEmpty && def) {
209                sub = parentSubcommand;
210            }
211            if (isParentEmpty && (def || (!foundDefault && helpCommand != null))) {
212                if (!foundDefault) {
213                    if (def) {
214                        this.subCommands.get(DEFAULT).clear();
215                        foundDefault = true;
216                    }
217                    registerSubcommand(method, DEFAULT);
218                } else {
219                    ACFUtil.sneaky(new IllegalStateException("Multiple @Default/@HelpCommand commands, duplicate on " + method.getDeclaringClass().getName() + "#" + method.getName()));
220                }
221            }
222
223            if (sub != null) {
224                sublist = sub;
225            } else if (commandAliases != null) {
226                sublist = commandAliases;
227            } else if (helpCommand != null) {
228                sublist = helpCommand;
229            }
230
231            boolean preCommand = annotations.hasAnnotation(method, PreCommand.class);
232            boolean hasCatchUnknown = annotations.hasAnnotation(method, CatchUnknown.class) ||
233                    annotations.hasAnnotation(method, CatchAll.class) ||
234                    annotations.hasAnnotation(method, UnknownHandler.class);
235
236            if (hasCatchUnknown || (!foundCatchUnknown && helpCommand != null)) {
237                if (!foundCatchUnknown) {
238                    if (hasCatchUnknown) {
239                        this.subCommands.get(CATCHUNKNOWN).clear();
240                        foundCatchUnknown = true;
241                    }
242                    registerSubcommand(method, CATCHUNKNOWN);
243                } else {
244                    ACFUtil.sneaky(new IllegalStateException("Multiple @UnknownHandler/@HelpCommand commands, duplicate on " + method.getDeclaringClass().getName() + "#" + method.getName()));
245                }
246            } else if (preCommand) {
247                if (this.preCommandHandler == null) {
248                    this.preCommandHandler = method;
249                } else {
250                    ACFUtil.sneaky(new IllegalStateException("Multiple @PreCommand commands, duplicate on " + method.getDeclaringClass().getName() + "#" + method.getName()));
251                }
252            }
253            if (Objects.equals(method.getDeclaringClass(), this.getClass()) && sublist != null) {
254                registerSubcommand(method, sublist);
255            }
256        }
257    }
258
259    private String getSubcommandValue(Method method) {
260        final String sub = manager.getAnnotations().getAnnotationValue(method, Subcommand.class, Annotations.NOTHING);
261        if (sub == null) {
262            return null;
263        }
264        Class<?> clazz = method.getDeclaringClass();
265        String parent = getParentSubcommand(clazz);
266        return parent == null || parent.isEmpty() ? sub : parent + " " + sub;
267    }
268
269    private String getParentSubcommand(Class<?> clazz) {
270        List<String> subList = new ArrayList<>();
271        while (clazz != null) {
272            String sub = manager.getAnnotations().getAnnotationValue(clazz, Subcommand.class, Annotations.NOTHING);
273            if (sub != null) {
274                subList.add(sub);
275            }
276            clazz = clazz.getEnclosingClass();
277        }
278        Collections.reverse(subList);
279        return ACFUtil.join(subList, " ");
280    }
281
282    private void register(String name, BaseCommand cmd) {
283        String nameLower = name.toLowerCase();
284        RootCommand rootCommand = manager.obtainRootCommand(nameLower);
285        rootCommand.addChild(cmd);
286
287        this.registeredCommands.put(nameLower, rootCommand);
288    }
289
290    private void registerSubcommand(Method method, String subCommand) {
291        subCommand = manager.getCommandReplacements().replace(subCommand.toLowerCase());
292        final String[] subCommandParts = ACFPatterns.SPACE.split(subCommand);
293        // Must run getSubcommandPossibility BEFORE we rewrite it just after this.
294        Set<String> cmdList = getSubCommandPossibilityList(subCommandParts);
295
296        // Strip pipes off for auto complete addition
297        for (int i = 0; i < subCommandParts.length; i++) {
298            String[] split = ACFPatterns.PIPE.split(subCommandParts[i]);
299            if (split.length == 0 || split[0].isEmpty()) {
300                throw new IllegalArgumentException("Invalid @Subcommand configuration for " + method.getName() + " - parts can not start with | or be empty");
301            }
302            subCommandParts[i] = split[0];
303        }
304        String prefSubCommand = ApacheCommonsLangUtil.join(subCommandParts, " ");
305        final String[] aliasNames = manager.getAnnotations().getAnnotationValues(method, CommandAlias.class, Annotations.REPLACEMENTS | Annotations.LOWERCASE);
306
307        String cmdName = aliasNames != null ? aliasNames[0] : this.commandName + " ";
308        RegisteredCommand cmd = manager.createRegisteredCommand(this, cmdName, method, prefSubCommand);
309
310        for (String subcmd : cmdList) {
311            subCommands.put(subcmd, cmd);
312        }
313        cmd.addSubcommands(cmdList);
314
315        if (aliasNames != null) {
316            for (String name : aliasNames) {
317                register(name, new ForwardingCommand(this, subCommandParts));
318            }
319        }
320    }
321
322    /**
323     * Takes a string like "foo|bar baz|qux" and generates a list of
324     * - foo baz
325     * - foo qux
326     * - bar baz
327     * - bar qux
328     *
329     * For every possible sub command combination
330     *
331     * @param subCommandParts
332     * @return List of all sub command possibilities
333     */
334    private static Set<String> getSubCommandPossibilityList(String[] subCommandParts) {
335        int i = 0;
336        Set<String> current = null;
337        while (true) {
338            Set<String> newList = new HashSet<>();
339
340            if (i < subCommandParts.length) {
341                for (String s1 : ACFPatterns.PIPE.split(subCommandParts[i])) {
342                    if (current != null) {
343                        newList.addAll(current.stream().map(s -> s + " " + s1).collect(Collectors.toList()));
344                    } else {
345                        newList.add(s1);
346                    }
347                }
348            }
349
350            if (i + 1 < subCommandParts.length) {
351                current = newList;
352                i = i + 1;
353                continue;
354            }
355
356            return newList;
357        }
358    }
359
360    public void execute(CommandIssuer issuer, String commandLabel, String[] args) {
361        commandLabel = commandLabel.toLowerCase();
362        try {
363            CommandOperationContext commandContext = preCommandOperation(issuer, commandLabel, args, false);
364
365            if (args.length > 0) {
366                CommandSearch cmd = findSubCommand(args);
367                if (cmd != null) {
368                    execSubcommand = cmd.getCheckSub();
369                    final String[] execargs = Arrays.copyOfRange(args, cmd.argIndex, args.length);
370                    executeCommand(commandContext, issuer, execargs, cmd.cmd);
371                    return;
372                }
373            }
374
375            if (subCommands.get(DEFAULT) != null && args.length == 0) {
376                executeSubcommand(commandContext, DEFAULT, issuer, args);
377            } else if (subCommands.get(CATCHUNKNOWN) != null) {
378                if (!executeSubcommand(commandContext, CATCHUNKNOWN, issuer, args)) {
379                    help(issuer, args);
380                }
381            } else if (subCommands.get(DEFAULT) != null) {
382                executeSubcommand(commandContext, DEFAULT, issuer, args);
383            }
384
385        } finally {
386            postCommandOperation();
387        }
388    }
389
390    RegisteredCommand<?> getRegisteredCommand(String[] args) {
391        final CommandSearch cmd = findSubCommand(args);
392        return cmd != null ? cmd.cmd : null;
393    }
394
395    private void postCommandOperation() {
396        CommandManager.commandOperationContext.get().pop();
397        execSubcommand = null;
398        execLabel = null;
399        origArgs = new String[]{};
400    }
401
402    private CommandOperationContext preCommandOperation(CommandIssuer issuer, String commandLabel, String[] args, boolean isAsync) {
403        Stack<CommandOperationContext> contexts = CommandManager.commandOperationContext.get();
404        CommandOperationContext context = this.manager.createCommandOperationContext(this, issuer, commandLabel, args, isAsync);
405        contexts.push(context);
406        lastCommandOperationContext = context;
407        execSubcommand = null;
408        execLabel = commandLabel;
409        origArgs = args;
410        return context;
411    }
412
413    public CommandIssuer getCurrentCommandIssuer() {
414        return CommandManager.getCurrentCommandIssuer();
415    }
416    public CommandManager getCurrentCommandManager() {
417        return CommandManager.getCurrentCommandManager();
418    }
419
420    private CommandSearch findSubCommand(String[] args) {
421        return findSubCommand(args, false);
422    }
423    private CommandSearch findSubCommand(String[] args, boolean completion) {
424        for (int i = args.length; i >= 0; i--) {
425            String checkSub = ApacheCommonsLangUtil.join(args, " ", 0, i).toLowerCase();
426            Set<RegisteredCommand> cmds = subCommands.get(checkSub);
427
428            final int extraArgs = args.length - i;
429            if (!cmds.isEmpty()) {
430                RegisteredCommand cmd = null;
431                if (cmds.size() == 1) {
432                    cmd = Iterables.getOnlyElement(cmds);
433                } else {
434                    Optional<RegisteredCommand> optCmd = cmds.stream().filter(c -> {
435                        int required = c.requiredResolvers;
436                        int optional = c.optionalResolvers;
437                        return extraArgs <= required + optional && (completion || extraArgs >= required);
438                    }).sorted((c1, c2) -> {
439                        int a = c1.consumeInputResolvers;
440                        int b = c2.consumeInputResolvers;
441
442                        if (a == b) {
443                            return 0;
444                        }
445                        return a < b ? 1 : -1;
446                    }).findFirst();
447                    if (optCmd.isPresent()) {
448                        cmd = optCmd.get();
449                    }
450                }
451                if (cmd != null) {
452                    return new CommandSearch(cmd, i, checkSub);
453                }
454            }
455        }
456        return null;
457    }
458
459    private void executeCommand(CommandOperationContext commandOperationContext,
460                                CommandIssuer issuer, String[] args, RegisteredCommand cmd) {
461        if (cmd.hasPermission(issuer)) {
462            commandOperationContext.setRegisteredCommand(cmd);
463            if (checkPrecommand(commandOperationContext, cmd, issuer, args)) {
464                return;
465            }
466            List<String> sargs = Lists.newArrayList(args);
467            cmd.invoke(issuer, sargs, commandOperationContext);
468        } else {
469            issuer.sendMessage(MessageType.ERROR, MessageKeys.PERMISSION_DENIED);
470        }
471    }
472
473    public boolean canExecute(CommandIssuer issuer, RegisteredCommand<?> cmd) {
474        return true;
475    }
476
477    public List<String> tabComplete(CommandIssuer issuer, String commandLabel, String[] args) {
478        return tabComplete(issuer, commandLabel, args, false);
479    }
480    public List<String> tabComplete(CommandIssuer issuer, String commandLabel, String[] args, boolean isAsync)
481        throws IllegalArgumentException {
482
483        commandLabel = commandLabel.toLowerCase();
484        if (args.length == 0) {
485            args = new String[]{""};
486        }
487        try {
488            CommandOperationContext commandOperationContext = preCommandOperation(issuer, commandLabel, args, isAsync);
489
490            final CommandSearch search = findSubCommand(args, true);
491
492
493            final List<String> cmds = new ArrayList<>();
494
495            if (search != null) {
496                cmds.addAll(completeCommand(issuer, search.cmd, Arrays.copyOfRange(args, search.argIndex, args.length), commandLabel, isAsync));
497            } else if (subCommands.get(CATCHUNKNOWN).size() == 1) {
498                cmds.addAll(completeCommand(issuer, Iterables.getOnlyElement(subCommands.get(CATCHUNKNOWN)), args, commandLabel, isAsync));
499            } else if (subCommands.get(DEFAULT).size() == 1) {
500                cmds.addAll(completeCommand(issuer, Iterables.getOnlyElement(subCommands.get(DEFAULT)), args, commandLabel, isAsync));
501            }
502
503            return filterTabComplete(args[args.length - 1], cmds);
504        } finally {
505            postCommandOperation();
506        }
507    }
508
509    List<String> getCommandsForCompletion(CommandIssuer issuer, String[] args) {
510        final Set<String> cmds = new HashSet<>();
511        final int cmdIndex = Math.max(0, args.length - 1);
512        String argString = ApacheCommonsLangUtil.join(args, " ").toLowerCase();
513        for (Map.Entry<String, RegisteredCommand> entry : subCommands.entries()) {
514            final String key = entry.getKey();
515            if (key.startsWith(argString) && !CATCHUNKNOWN.equals(key) && !DEFAULT.equals(key)) {
516                final RegisteredCommand value = entry.getValue();
517                if (!value.hasPermission(issuer)) {
518                    continue;
519                }
520
521                String[] split = ACFPatterns.SPACE.split(value.prefSubCommand);
522                cmds.add(split[cmdIndex]);
523            }
524        }
525        return new ArrayList<>(cmds);
526    }
527
528    private List<String> completeCommand(CommandIssuer issuer, RegisteredCommand cmd, String[] args, String commandLabel, boolean isAsync) {
529        if (!cmd.hasPermission(issuer) || args.length > cmd.consumeInputResolvers || args.length == 0 || cmd.complete == null) {
530            return ImmutableList.of();
531        }
532
533        List<String> cmds = manager.getCommandCompletions().of(cmd, issuer, args, isAsync);
534        return filterTabComplete(args[args.length-1], cmds);
535    }
536
537    private static List<String> filterTabComplete(String arg, List<String> cmds) {
538        return cmds.stream()
539                   .distinct()
540                   .filter(cmd -> cmd != null && (arg.isEmpty() || ApacheCommonsLangUtil.startsWithIgnoreCase(cmd, arg)))
541                   .collect(Collectors.toList());
542    }
543
544    RegisteredCommand getSubcommand(String subcommand) {
545        return getSubcommand(subcommand, false);
546    }
547
548    RegisteredCommand getSubcommand(String subcommand, boolean requireOne) {
549        final Set<RegisteredCommand> commands = subCommands.get(subcommand);
550        if (!commands.isEmpty() && (!requireOne || commands.size() == 1)) {
551            return commands.iterator().next();
552        }
553        return null;
554    }
555
556    private boolean executeSubcommand(CommandOperationContext commandContext, String subcommand, CommandIssuer issuer, String... args) {
557        final RegisteredCommand cmd = this.getSubcommand(subcommand);
558        if (cmd != null) {
559            executeCommand(commandContext, issuer, args, cmd);
560            return true;
561        }
562
563        return false;
564    }
565
566    private boolean checkPrecommand(CommandOperationContext commandOperationContext, RegisteredCommand cmd, CommandIssuer issuer, String[] args) {
567        Method pre = this.preCommandHandler;
568        if (pre != null) {
569            try {
570                Class<?>[] types = pre.getParameterTypes();
571                Object[] parameters = new Object[pre.getParameterCount()];
572                for (int i = 0; i < parameters.length; i++) {
573                    Class<?> type = types[i];
574                    Object issuerObject = issuer.getIssuer();
575                    if (manager.isCommandIssuer(type) && type.isAssignableFrom(issuerObject.getClass())) {
576                        parameters[i] = issuerObject;
577                    } else if (CommandIssuer.class.isAssignableFrom(type)) {
578                        parameters[i] = issuer;
579                    } else if (RegisteredCommand.class.isAssignableFrom(type)) {
580                        parameters[i] = cmd;
581                    } else if (String[].class.isAssignableFrom((type))) {
582                        parameters[i] = args;
583                    } else {
584                        parameters[i] = null;
585                    }
586                }
587
588                return (boolean) pre.invoke(this, parameters);
589            } catch (IllegalAccessException | InvocationTargetException e) {
590                this.manager.log(LogLevel.ERROR, "Exception encountered while command pre-processing", e);
591            }
592        }
593        return false;
594    }
595
596    /** @deprecated Unstable API */ @Deprecated @UnstableAPI
597    public CommandHelp getCommandHelp() {
598       return manager.generateCommandHelp();
599    }
600
601    /** @deprecated Unstable API */ @Deprecated @UnstableAPI
602    public void showCommandHelp() {
603        getCommandHelp().showHelp();
604    }
605
606    public void help(Object issuer, String[] args) {
607        help(manager.getCommandIssuer(issuer), args);
608    }
609    public void help(CommandIssuer issuer, String[] args) {
610        issuer.sendMessage(MessageType.ERROR, MessageKeys.UNKNOWN_COMMAND);
611    }
612    public void doHelp(Object issuer, String... args) {
613        doHelp(manager.getCommandIssuer(issuer), args);
614    }
615    public void doHelp(CommandIssuer issuer, String... args) {
616        help(issuer, args);
617    }
618
619    public void showSyntax(CommandIssuer issuer, RegisteredCommand<?> cmd) {
620        issuer.sendMessage(MessageType.SYNTAX, MessageKeys.INVALID_SYNTAX,
621                "{command}", manager.getCommandPrefix(issuer) + cmd.command,
622                "{syntax}", cmd.syntaxText
623        );
624    }
625
626    public boolean hasPermission(Object issuer) {
627        return hasPermission(manager.getCommandIssuer(issuer));
628    }
629
630    public boolean hasPermission(CommandIssuer issuer) {
631        return permission == null || permission.isEmpty() || (manager.hasPermission(issuer, permission) && (parentCommand == null || parentCommand.hasPermission(issuer)));
632    }
633
634
635    public Set<String> getRequiredPermissions() {
636        if (this.permission == null || this.permission.isEmpty()) {
637            return ImmutableSet.of();
638        }
639        return Sets.newHashSet(ACFPatterns.COMMA.split(this.permission));
640    }
641
642    public boolean requiresPermission(String permission) {
643        return getRequiredPermissions().contains(permission) || this.parentCommand != null && parentCommand.requiresPermission(permission);
644    }
645
646    public String getName() {
647        return commandName;
648    }
649
650    public ExceptionHandler getExceptionHandler() {
651        return exceptionHandler;
652    }
653
654    public BaseCommand setExceptionHandler(ExceptionHandler exceptionHandler) {
655        this.exceptionHandler = exceptionHandler;
656        return this;
657    }
658
659    public RegisteredCommand getDefaultRegisteredCommand() {
660        return this.getSubcommand(DEFAULT);
661    }
662
663    public String setContextFlags(Class<?> cls, String flags) {
664        return this.contextFlags.put(cls, flags);
665    }
666
667    public String getContextFlags(Class<?> cls) {
668        return this.contextFlags.get(cls);
669    }
670
671    private static class CommandSearch { RegisteredCommand cmd; int argIndex; String checkSub;
672
673        CommandSearch(RegisteredCommand cmd, int argIndex, String checkSub) {
674            this.cmd = cmd;
675            this.argIndex = argIndex;
676            this.checkSub = checkSub;
677        }
678
679        String getCheckSub() {
680            return this.checkSub;
681        }
682
683        @Override
684        public boolean equals(Object o) {
685            if (this == o) return true;
686            if (o == null || getClass() != o.getClass()) return false;
687            CommandSearch that = (CommandSearch) o;
688            return argIndex == that.argIndex &&
689                    Objects.equals(cmd, that.cmd) &&
690                    Objects.equals(checkSub, that.checkSub);
691        }
692
693        @Override
694        public int hashCode() {
695            return Objects.hash(cmd, argIndex, checkSub);
696        }
697    }
698}