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;
034
035import java.util.List;
036import java.util.Map;
037
038@SuppressWarnings("WeakerAccess")
039public class CommandContexts <R extends CommandExecutionContext<?, ? extends CommandIssuer>> {
040    protected final Map<Class<?>, ContextResolver<?, R>> contextMap = Maps.newHashMap();
041    protected final CommandManager manager;
042
043    CommandContexts(CommandManager manager) {
044        this.manager = manager;
045        registerContext(Integer.class, (c) -> {
046            try {
047                return ACFUtil.parseNumber(c.popFirstArg(), c.hasFlag("suffixes")).intValue();
048            } catch (NumberFormatException e) {
049                throw new InvalidCommandArgument(MessageKeys.MUST_BE_A_NUMBER);
050            }
051        });
052        registerContext(Long.class, (c) -> {
053            try {
054                return ACFUtil.parseNumber(c.popFirstArg(), c.hasFlag("suffixes")).longValue();
055            } catch (NumberFormatException e) {
056                throw new InvalidCommandArgument(MessageKeys.MUST_BE_A_NUMBER);
057            }
058
059        });
060        registerContext(Float.class, (c) -> {
061            try {
062                return ACFUtil.parseNumber(c.popFirstArg(), c.hasFlag("suffixes")).floatValue();
063            } catch (NumberFormatException e) {
064                throw new InvalidCommandArgument(MessageKeys.MUST_BE_A_NUMBER);
065            }
066        });
067        registerContext(Double.class, (c) -> {
068            try {
069                return ACFUtil.parseNumber(c.popFirstArg(), c.hasFlag("suffixes")).doubleValue();
070            } catch (NumberFormatException e) {
071                throw new InvalidCommandArgument(MessageKeys.MUST_BE_A_NUMBER);
072            }
073        });
074        registerContext(Number.class, (c) -> {
075            try {
076                return ACFUtil.parseNumber(c.popFirstArg(), c.hasFlag("suffixes"));
077            } catch (NumberFormatException e) {
078                throw new InvalidCommandArgument(MessageKeys.MUST_BE_A_NUMBER);
079            }
080        });
081        registerContext(Boolean.class, (c) -> {
082            String test = c.popFirstArg();
083            if (test == null) {
084                return null;
085            }
086            return ACFUtil.isTruthy(test);
087        });
088        registerContext(String.class, (c) -> {
089            final Values values = c.getParam().getAnnotation(Values.class);
090            if (values != null) {
091                return c.popFirstArg();
092            }
093            String ret = (c.isLastArg() && c.getParam().getAnnotation(Single.class) == null) ?
094                ACFUtil.join(c.getArgs())
095                :
096                c.popFirstArg();
097
098            Integer minLen = c.getFlagValue("minlen", (Integer) null);
099            Integer maxLen = c.getFlagValue("maxlen", (Integer) null);
100            if (minLen != null) {
101                if (ret.length() < minLen) {
102                    throw new InvalidCommandArgument(MessageKeys.MUST_BE_MIN_LENGTH, "{min}", String.valueOf(minLen));
103                }
104            }
105            if (maxLen != null) {
106                if (ret.length() > maxLen) {
107                    throw new InvalidCommandArgument(MessageKeys.MUST_BE_MAX_LENGTH, "{max}", String.valueOf(maxLen));
108                }
109            }
110
111            return ret;
112        });
113        registerContext(String[].class, (c) -> {
114            String val;
115            // Go home IDEA, you're drunk
116            //noinspection unchecked
117            List<String> args = c.getArgs();
118            if (c.isLastArg() && c.getParam().getAnnotation(Single.class) == null) {
119                val = ACFUtil.join(args);
120            } else {
121                val = c.popFirstArg();
122            }
123            Split split = c.getParam().getAnnotation(Split.class);
124            if (split != null) {
125                if (val.isEmpty()) {
126                    throw new InvalidCommandArgument();
127                }
128                return ACFPatterns.getPattern(split.value()).split(val);
129            } else if (!c.isLastArg()) {
130                ACFUtil.sneaky(new IllegalStateException("Weird Command signature... String[] should be last or @Split"));
131            }
132
133            String[] result = args.toArray(new String[args.size()]);
134            args.clear();
135            return result;
136        });
137
138        registerContext(Enum.class, (c) -> {
139            final String first = c.popFirstArg();
140            //noinspection unchecked
141            Class<? extends Enum<?>> enumCls = (Class<? extends Enum<?>>) c.getParam().getType();
142            Enum<?> match = ACFUtil.simpleMatch(enumCls, first);
143            if (match == null) {
144                List<String> names = ACFUtil.enumNames(enumCls);
145                throw new InvalidCommandArgument(MessageKeys.PLEASE_SPECIFY_ONE_OF, "{valid}", ACFUtil.join(names));
146            }
147            return match;
148        });
149        registerOptionalContext(CommandHelp.class, (c) -> {
150            String first = c.getFirstArg();
151            String last = c.getLastArg();
152            int page = 1;
153            List<String> search = null;
154            if (last != null && ACFUtil.isInteger(last)) {
155                c.popLastArg();
156                page = ACFUtil.parseInt(last);
157                if (!c.getArgs().isEmpty()) {
158                    search = c.getArgs();
159                }
160            } else if (first != null && ACFUtil.isInteger(first)) {
161                c.popFirstArg();
162                page = ACFUtil.parseInt(first);
163                if (!c.getArgs().isEmpty()) {
164                    search = c.getArgs();
165                }
166            } else if (!c.getArgs().isEmpty()) {
167                search = c.getArgs();
168            }
169            CommandHelp commandHelp = manager.generateCommandHelp();
170            commandHelp.setPage(page);
171            commandHelp.setSearch(search);
172            return commandHelp;
173        });
174    }
175
176    /**
177     * @deprecated Please switch to {@link #registerIssuerAwareContext(Class, IssuerAwareContextResolver)}
178     * as the core wants to use the platform agnostic term of "Issuer" instead of Sender
179     * @see #registerIssuerAwareContext(Class, IssuerAwareContextResolver)
180     */
181    @Deprecated
182    public <T> void registerSenderAwareContext(Class<T> context, IssuerAwareContextResolver<T, R> supplier) {
183        contextMap.put(context, supplier);
184    }
185
186    /**
187     * Registers a context resolver that may conditionally consume input, falling back to using the context of the
188     * issuer to potentially fulfill this context.
189     * You may call {@link CommandExecutionContext#getFirstArg()} and then conditionally call {@link CommandExecutionContext#popFirstArg()}
190     * if you want to consume that input.
191     */
192    public <T> void registerIssuerAwareContext(Class<T> context, IssuerAwareContextResolver<T, R> supplier) {
193        contextMap.put(context, supplier);
194    }
195
196    /**
197     * Registers a context resolver that will never consume input. It will always satisfy its context based on the
198     * issuer of the command, so it will not appear in syntax strings.
199     */
200    public <T> void registerIssuerOnlyContext(Class<T> context, IssuerOnlyContextResolver<T, R> supplier) {
201        contextMap.put(context, supplier);
202    }
203
204    /**
205     * Registers a context that can safely accept a null input from the command issuer to resolve. This resolver should always
206     * call {@link CommandExecutionContext#popFirstArg()}
207     */
208    public <T> void registerOptionalContext(Class<T> context, OptionalContextResolver<T, R> supplier) {
209        contextMap.put(context, supplier);
210    }
211
212    /**
213     * Registers a context that requires input from the command issuer to resolve. This resolver should always
214     * call {@link CommandExecutionContext#popFirstArg()}
215     */
216    public <T> void registerContext(Class<T> context, ContextResolver<T, R> supplier) {
217        contextMap.put(context, supplier);
218    }
219
220    public ContextResolver<?, R> getResolver(Class<?> type) {
221        Class<?> rootType = type;
222        do {
223            if (type == Object.class) {
224                break;
225            }
226
227            final ContextResolver<?, R> resolver = contextMap.get(type);
228            if (resolver != null) {
229                return resolver;
230            }
231        } while ((type = type.getSuperclass()) != null);
232
233        this.manager.log(LogLevel.ERROR, "Could not find context resolver", new IllegalStateException("No context resolver defined for " + rootType.getName()));
234        return null;
235    }
236}