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}