    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.Single;
    027import co.aikar.commands.annotation.Split;
    028import co.aikar.commands.annotation.Values;
    029import co.aikar.commands.contexts.ContextResolver;
    030import co.aikar.commands.contexts.IssuerAwareContextResolver;
    031import co.aikar.commands.contexts.IssuerOnlyContextResolver;
    032import co.aikar.commands.contexts.OptionalContextResolver;
    033import org.jetbrains.annotations.NotNull;
    034
    035import java.math.BigDecimal;
    036import java.math.BigInteger;
    037import java.util.HashMap;
    038import java.util.List;
    039import java.util.Map;
    040
    041@SuppressWarnings("WeakerAccess")
    042public class CommandContexts<R extends CommandExecutionContext<?, ? extends CommandIssuer>> {
    043    protected final Map<Class<?>, ContextResolver<?, R>> contextMap = new HashMap<>();
    044    protected final CommandManager manager;
    045
    046    CommandContexts(CommandManager manager) {
    047        this.manager = manager;
    048        registerIssuerOnlyContext(CommandIssuer.class, c -> c.getIssuer());
    049        registerContext(Short.class, (c) -> {
    050            try {
    051                return parseAndValidateNumber(c, Short.MIN_VALUE, Short.MAX_VALUE).shortValue();
    052            } catch (NumberFormatException e) {
    053                throw new InvalidCommandArgument(MessageKeys.MUST_BE_A_NUMBER, "{num}", c.getFirstArg());
    054            }
    055        });
    056        registerContext(short.class, (c) -> {
    057            try {
    058                return parseAndValidateNumber(c, Short.MIN_VALUE, Short.MAX_VALUE).shortValue();
    059            } catch (NumberFormatException e) {
    060                throw new InvalidCommandArgument(MessageKeys.MUST_BE_A_NUMBER, "{num}", c.getFirstArg());
    061            }
    062        });
    063        registerContext(Integer.class, (c) -> {
    064            try {
    065                return parseAndValidateNumber(c, Integer.MIN_VALUE, Integer.MAX_VALUE).intValue();
    066            } catch (NumberFormatException e) {
    067                throw new InvalidCommandArgument(MessageKeys.MUST_BE_A_NUMBER, "{num}", c.getFirstArg());
    068            }
    069        });
    070        registerContext(int.class, (c) -> {
    071            try {
    072                return parseAndValidateNumber(c, Integer.MIN_VALUE, Integer.MAX_VALUE).intValue();
    073            } catch (NumberFormatException e) {
    074                throw new InvalidCommandArgument(MessageKeys.MUST_BE_A_NUMBER, "{num}", c.getFirstArg());
    075            }
    076        });
    077        registerContext(Long.class, (c) -> {
    078            try {
    079                return parseAndValidateNumber(c, Long.MIN_VALUE, Long.MAX_VALUE).longValue();
    080            } catch (NumberFormatException e) {
    081                throw new InvalidCommandArgument(MessageKeys.MUST_BE_A_NUMBER, "{num}", c.getFirstArg());
    082            }
    083        });
    084        registerContext(long.class, (c) -> {
    085            try {
    086                return parseAndValidateNumber(c, Long.MIN_VALUE, Long.MAX_VALUE).longValue();
    087            } catch (NumberFormatException e) {
    088                throw new InvalidCommandArgument(MessageKeys.MUST_BE_A_NUMBER, "{num}", c.getFirstArg());
    089            }
    090        });
    091        registerContext(Float.class, (c) -> {
    092            try {
    093                return parseAndValidateNumber(c, -Float.MAX_VALUE, Float.MAX_VALUE).floatValue();
    094            } catch (NumberFormatException e) {
    095                throw new InvalidCommandArgument(MessageKeys.MUST_BE_A_NUMBER, "{num}", c.getFirstArg());
    096            }
    097        });
    098        registerContext(float.class, (c) -> {
    099            try {
    100                return parseAndValidateNumber(c, -Float.MAX_VALUE, Float.MAX_VALUE).floatValue();
    101            } catch (NumberFormatException e) {
    102                throw new InvalidCommandArgument(MessageKeys.MUST_BE_A_NUMBER, "{num}", c.getFirstArg());
    103            }
    104        });
    105        registerContext(Double.class, (c) -> {
    106            try {
    107                return parseAndValidateNumber(c, -Double.MAX_VALUE, Double.MAX_VALUE).doubleValue();
    108            } catch (NumberFormatException e) {
    109                throw new InvalidCommandArgument(MessageKeys.MUST_BE_A_NUMBER, "{num}", c.getFirstArg());
    110            }
    111        });
    112        registerContext(double.class, (c) -> {
    113            try {
    114                return parseAndValidateNumber(c, -Double.MAX_VALUE, Double.MAX_VALUE).doubleValue();
    115            } catch (NumberFormatException e) {
    116                throw new InvalidCommandArgument(MessageKeys.MUST_BE_A_NUMBER, "{num}", c.getFirstArg());
    117            }
    118        });
    119        registerContext(Number.class, (c) -> {
    120            try {
    121                return parseAndValidateNumber(c, -Double.MAX_VALUE, Double.MAX_VALUE);
    122            } catch (NumberFormatException e) {
    123                throw new InvalidCommandArgument(MessageKeys.MUST_BE_A_NUMBER, "{num}", c.getFirstArg());
    124            }
    125        });
    126        registerContext(BigDecimal.class, (c) -> {
    127            try {
    128                BigDecimal number = ACFUtil.parseBigNumber(c.popFirstArg(), c.hasFlag("suffixes"));
    129                validateMinMax(c, number);
    130                return number;
    131            } catch (NumberFormatException e) {
    132                throw new InvalidCommandArgument(MessageKeys.MUST_BE_A_NUMBER, "{num}", c.getFirstArg());
    133            }
    134        });
    135        registerContext(BigInteger.class, (c) -> {
    136            try {
    137                BigDecimal number = ACFUtil.parseBigNumber(c.popFirstArg(), c.hasFlag("suffixes"));
    138                validateMinMax(c, number);
    139                return number.toBigIntegerExact();
    140            } catch (NumberFormatException e) {
    141                throw new InvalidCommandArgument(MessageKeys.MUST_BE_A_NUMBER, "{num}", c.getFirstArg());
    142            }
    143        });
    144        registerContext(Boolean.class, (c) -> ACFUtil.isTruthy(c.popFirstArg()));
    145        registerContext(boolean.class, (c) -> ACFUtil.isTruthy(c.popFirstArg()));
    146        registerContext(char.class, c -> {
    147            String s = c.popFirstArg();
    148            if (s.length() > 1) {
    149                throw new InvalidCommandArgument(MessageKeys.MUST_BE_MAX_LENGTH, "{max}", String.valueOf(1));
    150            }
    151            return s.charAt(0);
    152        });
    153        registerContext(String.class, (c) -> {
    154            // This will fail fast, its either in the values or its not
    155            if (c.hasAnnotation(Values.class)) {
    156                return c.popFirstArg();
    157            }
    158            String ret = (c.isLastArg() && !c.hasAnnotation(Single.class)) ?
    159                    ACFUtil.join(c.getArgs())
    160                    :
    161                    c.popFirstArg();
    162
    163            Integer minLen = c.getFlagValue("minlen", (Integer) null);
    164            Integer maxLen = c.getFlagValue("maxlen", (Integer) null);
    165            if (minLen != null) {
    166                if (ret.length() < minLen) {
    167                    throw new InvalidCommandArgument(MessageKeys.MUST_BE_MIN_LENGTH, "{min}", String.valueOf(minLen));
    168                }
    169            }
    170            if (maxLen != null) {
    171                if (ret.length() > maxLen) {
    172                    throw new InvalidCommandArgument(MessageKeys.MUST_BE_MAX_LENGTH, "{max}", String.valueOf(maxLen));
    173                }
    174            }
    175
    176            return ret;
    177        });
    178        registerContext(String[].class, (c) -> {
    179            String val;
    180            // Go home IDEA, you're drunk
    181            //noinspection unchecked
    182            List<String> args = c.getArgs();
    183            if (c.isLastArg() && !c.hasAnnotation(Single.class)) {
    184                val = ACFUtil.join(args);
    185            } else {
    186                val = c.popFirstArg();
    187            }
    188            String split = c.getAnnotationValue(Split.class, Annotations.NOTHING | Annotations.NO_EMPTY);
    189            if (split != null) {
    190                if (val.isEmpty()) {
    191                    throw new InvalidCommandArgument();
    192                }
    193                return ACFPatterns.getPattern(split).split(val);
    194            } else if (!c.isLastArg()) {
    195                ACFUtil.sneaky(new IllegalStateException("Weird Command signature... String[] should be last or @Split"));
    196            }
    197
    198            String[] result = args.toArray(new String[args.size()]);
    199            args.clear();
    200            return result;
    201        });
    202
    203        registerContext(Enum.class, (c) -> {
    204            final String first = c.popFirstArg();
    205            //noinspection unchecked
    206            Class<? extends Enum<?>> enumCls = (Class<? extends Enum<?>>) c.getParam().getType();
    207            Enum<?> match = ACFUtil.simpleMatch(enumCls, first);
    208            if (match == null) {
    209                List<String> names = ACFUtil.enumNames(enumCls);
    210                throw new InvalidCommandArgument(MessageKeys.PLEASE_SPECIFY_ONE_OF, "{valid}", ACFUtil.join(names));
    211            }
    212            return match;
    213        });
    214        registerOptionalContext(CommandHelp.class, (c) -> {
    215            String first = c.getFirstArg();
    216            String last = c.getLastArg();
    217            int page = 1;
    218            List<String> search = null;
    219            if (last != null && ACFUtil.isInteger(last)) {
    220                c.popLastArg();
    221                page = ACFUtil.parseInt(last);
    222                if (!c.getArgs().isEmpty()) {
    223                    search = c.getArgs();
    224                }
    225            } else if (first != null && ACFUtil.isInteger(first)) {
    226                c.popFirstArg();
    227                page = ACFUtil.parseInt(first);
    228                if (!c.getArgs().isEmpty()) {
    229                    search = c.getArgs();
    230                }
    231            } else if (!c.getArgs().isEmpty()) {
    232                search = c.getArgs();
    233            }
    234            CommandHelp commandHelp = manager.generateCommandHelp();
    235            commandHelp.setPage(page);
    236            Integer perPage = c.getFlagValue("perpage", (Integer) null);
    237            if (perPage != null) {
    238                commandHelp.setPerPage(perPage);
    239            }
    240
    241            // check if we have an exact match and should display the help page for that sub command instead
    242            if (search != null) {
    243                String cmd = String.join(" ", search);
    244                if (commandHelp.isExactMatch(cmd)) {
    245                    return commandHelp;
    246                }
    247            }
    248
    249            commandHelp.setSearch(search);
    250            return commandHelp;
    251        });
    252    }
    253
    254    @NotNull
    255    private Number parseAndValidateNumber(R c, Number minValue, Number maxValue) throws InvalidCommandArgument {
    256        final Number val = ACFUtil.parseNumber(c.popFirstArg(), c.hasFlag("suffixes"));
    257        validateMinMax(c, val, minValue, maxValue);
    258        return val;
    259    }
    260
    261    private void validateMinMax(R c, Number val) throws InvalidCommandArgument {
    262        validateMinMax(c, val, null, null);
    263    }
    264
    265    private void validateMinMax(R c, Number val, Number minValue, Number maxValue) throws InvalidCommandArgument {
    266        minValue = c.getFlagValue("min", minValue);
    267        maxValue = c.getFlagValue("max", maxValue);
    268        if (maxValue != null && val.doubleValue() > maxValue.doubleValue()) {
    269            throw new InvalidCommandArgument(MessageKeys.PLEASE_SPECIFY_AT_MOST, "{max}", String.valueOf(maxValue));
    270        }
    271        if (minValue != null && val.doubleValue() < minValue.doubleValue()) {
    272            throw new InvalidCommandArgument(MessageKeys.PLEASE_SPECIFY_AT_LEAST, "{min}", String.valueOf(minValue));
    273        }
    274    }
    275
    276
    277    /**
    278     * @see #registerIssuerAwareContext(Class, IssuerAwareContextResolver)
    279     * @deprecated Please switch to {@link #registerIssuerAwareContext(Class, IssuerAwareContextResolver)}
    280     * as the core wants to use the platform agnostic term of "Issuer" instead of Sender
    281     */
    282    @Deprecated
    283    public <T> void registerSenderAwareContext(Class<T> context, IssuerAwareContextResolver<T, R> supplier) {
    284        contextMap.put(context, supplier);
    285    }
    286
    287    /**
    288     * Registers a context resolver that may conditionally consume input, falling back to using the context of the
    289     * issuer to potentially fulfill this context.
    290     * You may call {@link CommandExecutionContext#getFirstArg()} and then conditionally call {@link CommandExecutionContext#popFirstArg()}
    291     * if you want to consume that input.
    292     */
    293    public <T> void registerIssuerAwareContext(Class<T> context, IssuerAwareContextResolver<T, R> supplier) {
    294        contextMap.put(context, supplier);
    295    }
    296
    297    /**
    298     * Registers a context resolver that will never consume input. It will always satisfy its context based on the
    299     * issuer of the command, so it will not appear in syntax strings.
    300     */
    301    public <T> void registerIssuerOnlyContext(Class<T> context, IssuerOnlyContextResolver<T, R> supplier) {
    302        contextMap.put(context, supplier);
    303    }
    304
    305    /**
    306     * Registers a context that can safely accept a null input from the command issuer to resolve. This resolver should always
    307     * call {@link CommandExecutionContext#popFirstArg()}
    308     */
    309    public <T> void registerOptionalContext(Class<T> context, OptionalContextResolver<T, R> supplier) {
    310        contextMap.put(context, supplier);
    311    }
    312
    313    /**
    314     * Registers a context that requires input from the command issuer to resolve. This resolver should always
    315     * call {@link CommandExecutionContext#popFirstArg()}
    316     */
    317    public <T> void registerContext(Class<T> context, ContextResolver<T, R> supplier) {
    318        contextMap.put(context, supplier);
    319    }
    320
    321    public ContextResolver<?, R> getResolver(Class<?> type) {
    322        Class<?> rootType = type;
    323        do {
    324            if (type == Object.class) {
    325                break;
    326            }
    327
    328            final ContextResolver<?, R> resolver = contextMap.get(type);
    329            if (resolver != null) {
    330                return resolver;
    331            }
    332        } while ((type = type.getSuperclass()) != null);
    333
    334        this.manager.log(LogLevel.ERROR, "Could not find context resolver", new IllegalStateException("No context resolver defined for " + rootType.getName()));
    335        return null;
    336    }
    337}