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