<commit_before>/*
 * Copyright (c) 2016-2017 <NAME> (Aikar) - MIT License
 *
 *  Permission is hereby granted, free of charge, to any person obtaining
 *  a copy of this software and associated documentation files (the
 *  "Software"), to deal in the Software without restriction, including
 *  without limitation the rights to use, copy, modify, merge, publish,
 *  distribute, sublicense, and/or sell copies of the Software, and to
 *  permit persons to whom the Software is furnished to do so, subject to
 *  the following conditions:
 *
 *  The above copyright notice and this permission notice shall be
 *  included in all copies or substantial portions of the Software.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 *  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 *  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 *  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 *  LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 *  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 *  WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

package co.aikar.commands;

import co.aikar.commands.annotation.CommandAlias;
import co.aikar.commands.annotation.CommandCompletion;
import co.aikar.commands.annotation.CommandPermission;
import co.aikar.commands.annotation.Conditions;
import co.aikar.commands.annotation.Description;
import co.aikar.commands.annotation.HelpSearchTags;
import co.aikar.commands.annotation.Private;
import co.aikar.commands.annotation.Syntax;
import co.aikar.commands.contexts.ContextResolver;
import org.jetbrains.annotations.Nullable;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

@SuppressWarnings("WeakerAccess")
public class RegisteredCommand<CEC extends CommandExecutionContext<CEC, ? extends CommandIssuer>> {
    final BaseCommand scope;
    final Method method;
    final CommandParameter<CEC>[] parameters;
    final CommandManager manager;
    final List<String> registeredSubcommands = new ArrayList<>();

    String command;
    String prefSubCommand;
    String syntaxText;
    String helpText;
    String permission;
    String complete;
    String conditions;
    public String helpSearchTags;

    boolean isPrivate;

    final int requiredResolvers;
    final int consumeInputResolvers;
    final int doesNotConsumeInputResolvers;
    final int optionalResolvers;

    final Set<String> permissions = new HashSet<>();

    RegisteredCommand(BaseCommand scope, String command, Method method, String prefSubCommand) {
        this.scope = scope;
        this.manager = this.scope.manager;
        final Annotations annotations = this.manager.getAnnotations();

        if (BaseCommand.CATCHUNKNOWN.equals(prefSubCommand) || BaseCommand.DEFAULT.equals(prefSubCommand)) {
            prefSubCommand = "";
            command = command.trim();
        }
        this.command = command + (!annotations.hasAnnotation(method, CommandAlias.class, false) && !prefSubCommand.isEmpty() ? prefSubCommand : "");
        this.method = method;
        this.prefSubCommand = prefSubCommand;

        this.permission = annotations.getAnnotationValue(method, CommandPermission.class, Annotations.REPLACEMENTS | Annotations.NO_EMPTY);
        this.complete = annotations.getAnnotationValue(method, CommandCompletion.class, Annotations.DEFAULT_EMPTY); // no replacements as it should be per-issuer
        this.helpText = annotations.getAnnotationValue(method, Description.class, Annotations.REPLACEMENTS | Annotations.DEFAULT_EMPTY);
        this.conditions = annotations.getAnnotationValue(method, Conditions.class, Annotations.REPLACEMENTS | Annotations.NO_EMPTY);
        this.helpSearchTags = annotations.getAnnotationValue(method, HelpSearchTags.class, Annotations.REPLACEMENTS | Annotations.NO_EMPTY);

        Parameter[] parameters = method.getParameters();
        //noinspection unchecked
        this.parameters = new CommandParameter[parameters.length];

        this.isPrivate = annotations.hasAnnotation(method, Private.class) || annotations.getAnnotationFromClass(scope.getClass(), Private.class) != null;

        int requiredResolvers = 0;
        int consumeInputResolvers = 0;
        int doesNotConsumeInputResolvers = 0;
        int optionalResolvers = 0;
        StringBuilder syntaxBuilder = new StringBuilder(64);

        for (int i = 0; i < parameters.length; i++) {
            CommandParameter<CEC> parameter = this.parameters[i] = new CommandParameter<>(this, parameters[i], i, i == parameters.length - 1);
            if (!parameter.isCommandIssuer()) {
                if (!parameter.requiresInput()) {
                    optionalResolvers++;
                } else {
                    requiredResolvers++;
                }
                if (parameter.canConsumeInput()) {
                    consumeInputResolvers++;
                } else {
                    doesNotConsumeInputResolvers++;
                }
            }
            if (parameter.getSyntax() != null) {
                if (syntaxBuilder.length() > 0) {
                    syntaxBuilder.append(' ');
                }
                syntaxBuilder.append(parameter.getSyntax());
            }
        }
        String syntaxText = syntaxBuilder.toString().trim();
        final String syntaxStr = annotations.getAnnotationValue(method, Syntax.class);
        this.syntaxText = syntaxStr != null ? ACFUtil.replace(syntaxStr, "@syntax", syntaxText) : syntaxText;
        this.requiredResolvers = requiredResolvers;
        this.consumeInputResolvers = consumeInputResolvers;
        this.doesNotConsumeInputResolvers = doesNotConsumeInputResolvers;
        this.optionalResolvers = optionalResolvers;
        this.computePermissions();
    }


    void invoke(CommandIssuer sender, List<String> args, CommandOperationContext context) {
        if (!scope.canExecute(sender, this)) {
            return;
        }
        preCommand();
        try {
            this.manager.conditions.validateConditions(context);
            Map<String, Object> passedArgs = resolveContexts(sender, args);
            if (passedArgs == null) return;

            method.invoke(scope, passedArgs.values().toArray());
        } catch (Exception e) {
            handleException(sender, args, e);
        } finally {
            postCommand();
        }
    }

    public void preCommand() {
    }

    public void postCommand() {
    }

    void handleException(CommandIssuer sender, List<String> args, Exception e) {
        if (e instanceof InvocationTargetException && e.getCause() instanceof InvalidCommandArgument) {
            e = (Exception) e.getCause();
        }
        if (e instanceof ShowCommandHelp) {
            ShowCommandHelp showHelp = (ShowCommandHelp) e;
            CommandHelp commandHelp = manager.generateCommandHelp();<commit_msg>Add support for CompletableFuture exception. (#206)<commit_after>/*
 * Copyright (c) 2016-2017 <NAME> (Aikar) - MIT License
 *
 *  Permission is hereby granted, free of charge, to any person obtaining
 *  a copy of this software and associated documentation files (the
 *  "Software"), to deal in the Software without restriction, including
 *  without limitation the rights to use, copy, modify, merge, publish,
 *  distribute, sublicense, and/or sell copies of the Software, and to
 *  permit persons to whom the Software is furnished to do so, subject to
 *  the following conditions:
 *
 *  The above copyright notice and this permission notice shall be
 *  included in all copies or substantial portions of the Software.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 *  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 *  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 *  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 *  LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 *  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 *  WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

package co.aikar.commands;

import co.aikar.commands.annotation.CommandAlias;
import co.aikar.commands.annotation.CommandCompletion;
import co.aikar.commands.annotation.CommandPermission;
import co.aikar.commands.annotation.Conditions;
import co.aikar.commands.annotation.Description;
import co.aikar.commands.annotation.HelpSearchTags;
import co.aikar.commands.annotation.Private;
import co.aikar.commands.annotation.Syntax;
import co.aikar.commands.contexts.ContextResolver;
import org.jetbrains.annotations.Nullable;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;

@SuppressWarnings("WeakerAccess")
public class RegisteredCommand<CEC extends CommandExecutionContext<CEC, ? extends CommandIssuer>> {
    final BaseCommand scope;
    final Method method;
    final CommandParameter<CEC>[] parameters;
    final CommandManager manager;
    final List<String> registeredSubcommands = new ArrayList<>();

    String command;
    String prefSubCommand;
    String syntaxText;
    String helpText;
    String permission;
    String complete;
    String conditions;
    public String helpSearchTags;

    boolean isPrivate;

    final int requiredResolvers;
    final int consumeInputResolvers;
    final int doesNotConsumeInputResolvers;
    final int optionalResolvers;

    final Set<String> permissions = new HashSet<>();

    RegisteredCommand(BaseCommand scope, String command, Method method, String prefSubCommand) {
        this.scope = scope;
        this.manager = this.scope.manager;
        final Annotations annotations = this.manager.getAnnotations();

        if (BaseCommand.CATCHUNKNOWN.equals(prefSubCommand) || BaseCommand.DEFAULT.equals(prefSubCommand)) {
            prefSubCommand = "";
            command = command.trim();
        }
        this.command = command + (!annotations.hasAnnotation(method, CommandAlias.class, false) && !prefSubCommand.isEmpty() ? prefSubCommand : "");
        this.method = method;
        this.prefSubCommand = prefSubCommand;

        this.permission = annotations.getAnnotationValue(method, CommandPermission.class, Annotations.REPLACEMENTS | Annotations.NO_EMPTY);
        this.complete = annotations.getAnnotationValue(method, CommandCompletion.class, Annotations.DEFAULT_EMPTY); // no replacements as it should be per-issuer
        this.helpText = annotations.getAnnotationValue(method, Description.class, Annotations.REPLACEMENTS | Annotations.DEFAULT_EMPTY);
        this.conditions = annotations.getAnnotationValue(method, Conditions.class, Annotations.REPLACEMENTS | Annotations.NO_EMPTY);
        this.helpSearchTags = annotations.getAnnotationValue(method, HelpSearchTags.class, Annotations.REPLACEMENTS | Annotations.NO_EMPTY);

        Parameter[] parameters = method.getParameters();
        //noinspection unchecked
        this.parameters = new CommandParameter[parameters.length];

        this.isPrivate = annotations.hasAnnotation(method, Private.class) || annotations.getAnnotationFromClass(scope.getClass(), Private.class) != null;

        int requiredResolvers = 0;
        int consumeInputResolvers = 0;
        int doesNotConsumeInputResolvers = 0;
        int optionalResolvers = 0;
        StringBuilder syntaxBuilder = new StringBuilder(64);

        for (int i = 0; i < parameters.length; i++) {
            CommandParameter<CEC> parameter = this.parameters[i] = new CommandParameter<>(this, parameters[i], i, i == parameters.length - 1);
            if (!parameter.isCommandIssuer()) {
                if (!parameter.requiresInput()) {
                    optionalResolvers++;
                } else {
                    requiredResolvers++;
                }
                if (parameter.canConsumeInput()) {
                    consumeInputResolvers++;
                } else {
                    doesNotConsumeInputResolvers++;
                }
            }
            if (parameter.getSyntax() != null) {
                if (syntaxBuilder.length() > 0) {
                    syntaxBuilder.append(' ');
                }
                syntaxBuilder.append(parameter.getSyntax());
            }
        }
        String syntaxText = syntaxBuilder.toString().trim();
        final String syntaxStr = annotations.getAnnotationValue(method, Syntax.class);
        this.syntaxText = syntaxStr != null ? ACFUtil.replace(syntaxStr, "@syntax", syntaxText) : syntaxText;
        this.requiredResolvers = requiredResolvers;
        this.consumeInputResolvers = consumeInputResolvers;
        this.doesNotConsumeInputResolvers = doesNotConsumeInputResolvers;
        this.optionalResolvers = optionalResolvers;
        this.computePermissions();
    }


    void invoke(CommandIssuer sender, List<String> args, CommandOperationContext context) {
        if (!scope.canExecute(sender, this)) {
            return;
        }
        preCommand();
        try {
            this.manager.conditions.validateConditions(context);
            Map<String, Object> passedArgs = resolveContexts(sender, args);
            if (passedArgs == null) return;

            Object obj = method.invoke(scope, passedArgs.values().toArray());
            if (obj instanceof CompletableFuture) {
                CompletableFuture<?> future = (CompletableFuture) obj;
                future.exceptionally(t -> {
                    handleException(sender, args, t);
                    return null;
                });
            }
        } catch (Exception e) {
            handleException(sender, args, e);
        } finally {
            postCommand();
        }
    }

    public void preCommand() {
    }

    public void postCommand() {
    }

    void handleException(CommandIssuer sender, List<String> args, Throwable e) {
        while (e instanceof ExecutionException) {
            e = e.getCause();
        }
        if (e instanceof InvocationTargetException && e.getCause() instanceof InvalidCommandArgument) {
            e = e.getCause();
        }
        if (e instanceof ShowCommandHelp) {
            ShowCommandHelp showHelp = (ShowCommandHelp) e;
            CommandHelp commandHelp = manager.generateCommandHelp();
{'fraction_non_alphanumeric': 0.08958499600957702, 'fraction_numerical': 0.0024607608406491087, 'mean_word_length': 3.082812924246538, 'pattern_counts': {'":': 0, '<': 32, '<?xml version=': 0, '>': 33, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1369853', 'n_tokens_mistral': 3935, 'n_tokens_neox': 3816, 'n_words': 1031}