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}