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

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