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

    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.CommandAlias;
    027import co.aikar.commands.annotation.CommandCompletion;
    028import co.aikar.commands.annotation.CommandPermission;
    029import co.aikar.commands.annotation.Conditions;
    030import co.aikar.commands.annotation.Description;
    031import co.aikar.commands.annotation.HelpSearchTags;
    032import co.aikar.commands.annotation.Private;
    033import co.aikar.commands.annotation.Syntax;
    034import co.aikar.commands.contexts.ContextResolver;
    035import org.jetbrains.annotations.Nullable;
    036
    037import java.lang.annotation.Annotation;
    038import java.lang.reflect.InvocationTargetException;
    039import java.lang.reflect.Method;
    040import java.lang.reflect.Parameter;
    041import java.util.ArrayList;
    042import java.util.Arrays;
    043import java.util.Collection;
    044import java.util.HashSet;
    045import java.util.LinkedHashMap;
    046import java.util.List;
    047import java.util.Locale;
    048import java.util.Map;
    049import java.util.Objects;
    050import java.util.Set;
    051import java.util.concurrent.CompletionException;
    052import java.util.concurrent.CompletionStage;
    053import java.util.concurrent.ExecutionException;
    054import java.util.stream.Collectors;
    055
    056@SuppressWarnings("WeakerAccess")
    057public class RegisteredCommand<CEC extends CommandExecutionContext<CEC, ? extends CommandIssuer>> {
    058    final BaseCommand scope;
    059    final Method method;
    060    final CommandParameter<CEC>[] parameters;
    061    final CommandManager manager;
    062    final List<String> registeredSubcommands = new ArrayList<>();
    063
    064    String command;
    065    String prefSubCommand;
    066    String syntaxText;
    067    String helpText;
    068    String permission;
    069    String complete;
    070    String conditions;
    071    public String helpSearchTags;
    072
    073    boolean isPrivate;
    074
    075    final int requiredResolvers;
    076    final int consumeInputResolvers;
    077    final int doesNotConsumeInputResolvers;
    078    final int optionalResolvers;
    079
    080    final Set<String> permissions = new HashSet<>();
    081
    082    RegisteredCommand(BaseCommand scope, String command, Method method, String prefSubCommand) {
    083        this.scope = scope;
    084        this.manager = this.scope.manager;
    085        final Annotations annotations = this.manager.getAnnotations();
    086
    087        if (BaseCommand.isSpecialSubcommand(prefSubCommand)) {
    088            prefSubCommand = "";
    089            command = command.trim();
    090        }
    091        this.command = command + (!annotations.hasAnnotation(method, CommandAlias.class, false) && !prefSubCommand.isEmpty() ? prefSubCommand : "");
    092        this.method = method;
    093        this.prefSubCommand = prefSubCommand;
    094
    095        this.permission = annotations.getAnnotationValue(method, CommandPermission.class, Annotations.REPLACEMENTS | Annotations.NO_EMPTY);
    096        this.complete = annotations.getAnnotationValue(method, CommandCompletion.class, Annotations.REPLACEMENTS | Annotations.DEFAULT_EMPTY);
    097        this.helpText = annotations.getAnnotationValue(method, Description.class, Annotations.REPLACEMENTS | Annotations.DEFAULT_EMPTY);
    098        this.conditions = annotations.getAnnotationValue(method, Conditions.class, Annotations.REPLACEMENTS | Annotations.NO_EMPTY);
    099        this.helpSearchTags = annotations.getAnnotationValue(method, HelpSearchTags.class, Annotations.REPLACEMENTS | Annotations.NO_EMPTY);
    100        this.syntaxText = annotations.getAnnotationValue(method, Syntax.class, Annotations.REPLACEMENTS);
    101
    102        Parameter[] parameters = method.getParameters();
    103        //noinspection unchecked
    104        this.parameters = new CommandParameter[parameters.length];
    105
    106        this.isPrivate = annotations.hasAnnotation(method, Private.class) || annotations.getAnnotationFromClass(scope.getClass(), Private.class) != null;
    107
    108        int requiredResolvers = 0;
    109        int consumeInputResolvers = 0;
    110        int doesNotConsumeInputResolvers = 0;
    111        int optionalResolvers = 0;
    112
    113        CommandParameter<CEC> previousParam = null;
    114        for (int i = 0; i < parameters.length; i++) {
    115            CommandParameter<CEC> parameter = this.parameters[i] = new CommandParameter<>(this, parameters[i], i, i == parameters.length - 1);
    116            if (previousParam != null) {
    117                previousParam.setNextParam(parameter);
    118            }
    119            previousParam = parameter;
    120            if (!parameter.isCommandIssuer()) {
    121                if (!parameter.requiresInput()) {
    122                    optionalResolvers++;
    123                } else {
    124                    requiredResolvers++;
    125                }
    126                if (parameter.canConsumeInput()) {
    127                    consumeInputResolvers++;
    128                } else {
    129                    doesNotConsumeInputResolvers++;
    130                }
    131            }
    132        }
    133
    134        this.requiredResolvers = requiredResolvers;
    135        this.consumeInputResolvers = consumeInputResolvers;
    136        this.doesNotConsumeInputResolvers = doesNotConsumeInputResolvers;
    137        this.optionalResolvers = optionalResolvers;
    138        this.computePermissions();
    139    }
    140
    141
    142    void invoke(CommandIssuer sender, List<String> args, CommandOperationContext context) {
    143        if (!scope.canExecute(sender, this)) {
    144            return;
    145        }
    146        preCommand();
    147        try {
    148            this.manager.getCommandConditions().validateConditions(context);
    149            Map<String, Object> passedArgs = resolveContexts(sender, args);
    150            if (passedArgs == null) return;
    151
    152            Object obj = method.invoke(scope, passedArgs.values().toArray());
    153            if (obj instanceof CompletionStage<?>) {
    154                CompletionStage<?> future = (CompletionStage<?>) obj;
    155                future.exceptionally(t -> {
    156                    handleException(sender, args, t);
    157                    return null;
    158                });
    159            }
    160        } catch (Exception e) {
    161            handleException(sender, args, e);
    162        } finally {
    163            postCommand();
    164        }
    165    }
    166
    167    public void preCommand() {
    168    }
    169
    170    public void postCommand() {
    171    }
    172
    173    void handleException(CommandIssuer sender, List<String> args, Throwable e) {
    174        while (e instanceof ExecutionException || e instanceof CompletionException || e instanceof InvocationTargetException) {
    175            e = e.getCause();
    176        }
    177        if (e instanceof ShowCommandHelp) {
    178            ShowCommandHelp showHelp = (ShowCommandHelp) e;
    179            CommandHelp commandHelp = manager.generateCommandHelp();
    180            if (showHelp.search) {
    181                commandHelp.setSearch(showHelp.searchArgs == null ? args : showHelp.searchArgs);
    182            }
    183            commandHelp.showHelp(sender);
    184        } else if (e instanceof InvalidCommandArgument) {
    185            InvalidCommandArgument invalidCommandArg = (InvalidCommandArgument) e;
    186            if (invalidCommandArg.key != null) {
    187                sender.sendMessage(MessageType.ERROR, invalidCommandArg.key, invalidCommandArg.replacements);
    188            } else if (e.getMessage() != null && !e.getMessage().isEmpty()) {
    189                sender.sendMessage(MessageType.ERROR, MessageKeys.ERROR_PREFIX, "{message}", e.getMessage());
    190            }
    191            if (invalidCommandArg.showSyntax) {
    192                scope.showSyntax(sender, this);
    193            }
    194        } else {
    195            try {
    196                if (!this.manager.handleUncaughtException(scope, this, sender, args, e)) {
    197                    sender.sendMessage(MessageType.ERROR, MessageKeys.ERROR_PERFORMING_COMMAND);
    198                }
    199                boolean hasExceptionHandler = this.manager.defaultExceptionHandler != null || this.scope.getExceptionHandler() != null;
    200                if (!hasExceptionHandler || this.manager.logUnhandledExceptions) {
    201                    this.manager.log(LogLevel.ERROR, "Exception in command: " + command + " " + ACFUtil.join(args), e);
    202                }
    203            } catch (Exception e2) {
    204                this.manager.log(LogLevel.ERROR, "Exception in handleException for command: " + command + " " + ACFUtil.join(args), e);
    205                this.manager.log(LogLevel.ERROR, "Exception triggered by exception handler:", e2);
    206            }
    207        }
    208    }
    209
    210    @Nullable
    211    Map<String, Object> resolveContexts(CommandIssuer sender, List<String> args) throws InvalidCommandArgument {
    212        return resolveContexts(sender, args, null);
    213    }
    214
    215    @Nullable
    216    Map<String, Object> resolveContexts(CommandIssuer sender, List<String> args, String name) throws InvalidCommandArgument {
    217        args = new ArrayList<>(args);
    218        String[] origArgs = args.toArray(new String[args.size()]);
    219        Map<String, Object> passedArgs = new LinkedHashMap<>();
    220        int remainingRequired = requiredResolvers;
    221        CommandOperationContext opContext = CommandManager.getCurrentCommandOperationContext();
    222        for (int i = 0; i < parameters.length && (name == null || !passedArgs.containsKey(name)); i++) {
    223            boolean isLast = i == parameters.length - 1;
    224            boolean allowOptional = remainingRequired == 0;
    225            final CommandParameter<CEC> parameter = parameters[i];
    226            final String parameterName = parameter.getName();
    227            final Class<?> type = parameter.getType();
    228            final ContextResolver<?, CEC> resolver = parameter.getResolver();
    229            //noinspection unchecked
    230            CEC context = (CEC) this.manager.createCommandContext(this, parameter, sender, args, i, passedArgs);
    231            boolean requiresInput = parameter.requiresInput();
    232            if (requiresInput && remainingRequired > 0) {
    233                remainingRequired--;
    234            }
    235
    236            Set<String> parameterPermissions = parameter.getRequiredPermissions();
    237            if (args.isEmpty() && !(isLast && type == String[].class)) {
    238                if (allowOptional && parameter.getDefaultValue() != null) {
    239                    args.add(parameter.getDefaultValue());
    240                } else if (allowOptional && parameter.isOptional()) {
    241                    Object value;
    242                    if (!parameter.isOptionalResolver() || !this.manager.hasPermission(sender, parameterPermissions)) {
    243                        value = null;
    244                    } else {
    245                        value = resolver.getContext(context);
    246                    }
    247
    248                    if (value == null && parameter.getClass().isPrimitive()) {
    249                        throw new IllegalStateException("Parameter " + parameter.getName() + " is primitive and does not support Optional.");
    250                    }
    251                    //noinspection unchecked
    252                    this.manager.getCommandConditions().validateConditions(context, value);
    253                    passedArgs.put(parameterName, value);
    254                    continue;
    255                } else if (requiresInput) {
    256                    scope.showSyntax(sender, this);
    257                    return null;
    258                }
    259            } else {
    260                if (!this.manager.hasPermission(sender, parameterPermissions)) {
    261                    sender.sendMessage(MessageType.ERROR, MessageKeys.PERMISSION_DENIED_PARAMETER, "{param}", parameterName);
    262                    throw new InvalidCommandArgument(false);
    263                }
    264            }
    265
    266            if (parameter.getValues() != null) {
    267                String arg = !args.isEmpty() ? args.get(0) : "";
    268
    269                Set<String> possible = new HashSet<>();
    270                CommandCompletions commandCompletions = this.manager.getCommandCompletions();
    271                for (String s : parameter.getValues()) {
    272                    if ("*".equals(s) || "@completions".equals(s)) {
    273                        s = commandCompletions.findDefaultCompletion(this, origArgs);
    274                    }
    275                    //noinspection unchecked
    276                    List<String> check = commandCompletions.getCompletionValues(this, sender, s, origArgs, opContext.isAsync());
    277                    if (!check.isEmpty()) {
    278                        possible.addAll(check.stream().filter(Objects::nonNull).
    279                                map(String::toLowerCase).collect(Collectors.toList()));
    280                    } else {
    281                        possible.add(s.toLowerCase(Locale.ENGLISH));
    282                    }
    283                }
    284                if (!possible.contains(arg.toLowerCase(Locale.ENGLISH))) {
    285                    throw new InvalidCommandArgument(MessageKeys.PLEASE_SPECIFY_ONE_OF,
    286                            "{valid}", ACFUtil.join(possible, ", "));
    287                }
    288            }
    289
    290            Object paramValue = resolver.getContext(context);
    291
    292            //noinspection unchecked
    293            this.manager.getCommandConditions().validateConditions(context, paramValue);
    294            passedArgs.put(parameterName, paramValue);
    295        }
    296        return passedArgs;
    297    }
    298
    299    boolean hasPermission(CommandIssuer issuer) {
    300        return this.manager.hasPermission(issuer, getRequiredPermissions());
    301    }
    302
    303    /**
    304     * @see #getRequiredPermissions()
    305     * @deprecated
    306     */
    307    @Deprecated
    308    public String getPermission() {
    309        if (this.permission == null || this.permission.isEmpty()) {
    310            return null;
    311        }
    312        return ACFPatterns.COMMA.split(this.permission)[0];
    313    }
    314
    315    void computePermissions() {
    316        this.permissions.clear();
    317        this.permissions.addAll(this.scope.getRequiredPermissions());
    318        if (this.permission != null && !this.permission.isEmpty()) {
    319            this.permissions.addAll(Arrays.asList(ACFPatterns.COMMA.split(this.permission)));
    320        }
    321    }
    322
    323    public Set<String> getRequiredPermissions() {
    324        return this.permissions;
    325    }
    326
    327    public boolean requiresPermission(String permission) {
    328        return getRequiredPermissions().contains(permission);
    329    }
    330
    331    public String getPrefSubCommand() {
    332        return prefSubCommand;
    333    }
    334
    335    public String getSyntaxText() {
    336        return getSyntaxText(null);
    337    }
    338
    339    public String getSyntaxText(CommandIssuer issuer) {
    340        if (syntaxText != null) return syntaxText;
    341        StringBuilder syntaxBuilder = new StringBuilder(64);
    342        for (CommandParameter<?> parameter : parameters) {
    343            String syntax = parameter.getSyntax(issuer);
    344            if (syntax != null) {
    345                if (syntaxBuilder.length() > 0) {
    346                    syntaxBuilder.append(' ');
    347                }
    348                syntaxBuilder.append(syntax);
    349            }
    350        }
    351        return syntaxBuilder.toString().trim();
    352    }
    353
    354    public String getHelpText() {
    355        return helpText != null ? helpText : "";
    356    }
    357
    358    public boolean isPrivate() {
    359        return isPrivate;
    360    }
    361
    362    public String getCommand() {
    363        return command;
    364    }
    365
    366    public void addSubcommand(String cmd) {
    367        this.registeredSubcommands.add(cmd);
    368    }
    369
    370    public void addSubcommands(Collection<String> cmd) {
    371        this.registeredSubcommands.addAll(cmd);
    372    }
    373
    374    public <T extends Annotation> T getAnnotation(Class<T> annotation) {
    375        return method.getAnnotation(annotation);
    376    }
    377}