001/*
002 * Copyright (c) 2016-2018 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.CommandPermission;
027import co.aikar.commands.annotation.Conditions;
028import co.aikar.commands.annotation.Default;
029import co.aikar.commands.annotation.Description;
030import co.aikar.commands.annotation.Flags;
031import co.aikar.commands.annotation.Name;
032import co.aikar.commands.annotation.Optional;
033import co.aikar.commands.annotation.Single;
034import co.aikar.commands.annotation.Syntax;
035import co.aikar.commands.annotation.Values;
036import co.aikar.commands.contexts.ContextResolver;
037import co.aikar.commands.contexts.IssuerAwareContextResolver;
038import co.aikar.commands.contexts.IssuerOnlyContextResolver;
039import co.aikar.commands.contexts.OptionalContextResolver;
040import co.aikar.locales.MessageKey;
041
042import java.lang.reflect.Parameter;
043import java.util.Arrays;
044import java.util.HashMap;
045import java.util.HashSet;
046import java.util.Map;
047import java.util.Set;
048
049public class CommandParameter<CEC extends CommandExecutionContext<CEC, ? extends CommandIssuer>> {
050    private final Parameter parameter;
051    private final Class<?> type;
052    private final String name;
053    private final CommandManager manager;
054    private final int paramIndex;
055
056    private ContextResolver<?, CEC> resolver;
057    private boolean optional;
058    private Set<String> permissions = new HashSet<>();
059    private String permission;
060    private String description;
061    private String defaultValue;
062    private String syntax;
063    private String conditions;
064    private boolean requiresInput;
065    private boolean commandIssuer;
066    private String[] values;
067    private Map<String, String> flags;
068    private boolean canConsumeInput;
069    private boolean optionalResolver;
070    boolean consumesRest;
071    private boolean isLast;
072    private boolean isOptionalInput;
073    private CommandParameter<CEC> nextParam;
074
075    public CommandParameter(RegisteredCommand<CEC> command, Parameter param, int paramIndex, boolean isLast) {
076        this.parameter = param;
077        this.isLast = isLast;
078        this.type = param.getType();
079        this.manager = command.manager;
080        this.paramIndex = paramIndex;
081        Annotations annotations = manager.getAnnotations();
082
083        String annotationName = annotations.getAnnotationValue(param, Name.class, Annotations.REPLACEMENTS);
084        this.name = annotationName != null ? annotationName : param.getName();
085        this.defaultValue = annotations.getAnnotationValue(param, Default.class, Annotations.REPLACEMENTS | (type != String.class ? Annotations.NO_EMPTY : 0));
086        this.description = annotations.getAnnotationValue(param, Description.class, Annotations.REPLACEMENTS | Annotations.DEFAULT_EMPTY);
087        this.conditions = annotations.getAnnotationValue(param, Conditions.class, Annotations.REPLACEMENTS | Annotations.NO_EMPTY);
088
089        //noinspection unchecked
090        this.resolver = manager.getCommandContexts().getResolver(type);
091        if (this.resolver == null) {
092            ACFUtil.sneaky(new InvalidCommandContextException(
093                    "Parameter " + type.getSimpleName() + " of " + command + " has no applicable context resolver"
094            ));
095        }
096
097        this.optional = annotations.hasAnnotation(param, Optional.class) || this.defaultValue != null || (isLast && type == String[].class);
098        this.permission = annotations.getAnnotationValue(param, CommandPermission.class, Annotations.REPLACEMENTS | Annotations.NO_EMPTY);
099        this.optionalResolver = isOptionalResolver(resolver);
100        this.requiresInput = !this.optional && !this.optionalResolver;
101        //noinspection unchecked
102        this.commandIssuer = paramIndex == 0 && manager.isCommandIssuer(type);
103        this.canConsumeInput = !this.commandIssuer && !(resolver instanceof IssuerOnlyContextResolver);
104        this.consumesRest = isLast && ((type == String.class && !annotations.hasAnnotation(param, Single.class)) || (type == String[].class));
105
106        this.values = annotations.getAnnotationValues(param, Values.class, Annotations.REPLACEMENTS | Annotations.NO_EMPTY);
107
108        this.syntax = null;
109        this.isOptionalInput = !requiresInput && canConsumeInput;
110
111        if (!commandIssuer) {
112            this.syntax = annotations.getAnnotationValue(param, Syntax.class);
113        }
114
115        this.flags = new HashMap<>();
116        String flags = annotations.getAnnotationValue(param, Flags.class, Annotations.REPLACEMENTS | Annotations.NO_EMPTY);
117        if (flags != null) {
118            parseFlags(flags);
119        }
120        inheritContextFlags(command.scope);
121        this.computePermissions();
122    }
123
124    private void inheritContextFlags(BaseCommand scope) {
125        if (!scope.contextFlags.isEmpty()) {
126            Class<?> pCls = this.type;
127            do {
128                parseFlags(scope.contextFlags.get(pCls));
129            } while ((pCls = pCls.getSuperclass()) != null);
130        }
131        if (scope.parentCommand != null) {
132            inheritContextFlags(scope.parentCommand);
133        }
134    }
135
136    private void parseFlags(String flags) {
137        if (flags != null) {
138            for (String s : ACFPatterns.COMMA.split(manager.getCommandReplacements().replace(flags))) {
139                String[] v = ACFPatterns.EQUALS.split(s, 2);
140                if (!this.flags.containsKey(v[0])) {
141                    this.flags.put(v[0], v.length > 1 ? v[1] : null);
142                }
143            }
144        }
145    }
146
147    private void computePermissions() {
148        this.permissions.clear();
149        if (this.permission != null && !this.permission.isEmpty()) {
150            this.permissions.addAll(Arrays.asList(ACFPatterns.COMMA.split(this.permission)));
151        }
152    }
153
154    private boolean isOptionalResolver(ContextResolver<?, CEC> resolver) {
155        return resolver instanceof IssuerAwareContextResolver
156                || resolver instanceof IssuerOnlyContextResolver
157                || resolver instanceof OptionalContextResolver;
158    }
159
160
161    public Parameter getParameter() {
162        return parameter;
163    }
164
165    public Class<?> getType() {
166        return type;
167    }
168
169    public String getName() {
170        return name;
171    }
172
173    public String getDisplayName(CommandIssuer issuer) {
174        String translated = manager.getLocales().getOptionalMessage(issuer, MessageKey.of("acf-core.parameter." + name.toLowerCase()));
175        return translated != null ? translated : name;
176    }
177
178    public CommandManager getManager() {
179        return manager;
180    }
181
182    public int getParamIndex() {
183        return paramIndex;
184    }
185
186    public ContextResolver<?, CEC> getResolver() {
187        return resolver;
188    }
189
190    public void setResolver(ContextResolver<?, CEC> resolver) {
191        this.resolver = resolver;
192    }
193
194    public boolean isOptionalInput() {
195        return isOptionalInput;
196    }
197
198    public boolean isOptional() {
199        return optional;
200    }
201
202    public void setOptional(boolean optional) {
203        this.optional = optional;
204    }
205
206    public String getDescription() {
207        return description;
208    }
209
210    public void setDescription(String description) {
211        this.description = description;
212    }
213
214    public String getDefaultValue() {
215        return defaultValue;
216    }
217
218    public void setDefaultValue(String defaultValue) {
219        this.defaultValue = defaultValue;
220    }
221
222    public boolean isCommandIssuer() {
223        return commandIssuer;
224    }
225
226    public void setCommandIssuer(boolean commandIssuer) {
227        this.commandIssuer = commandIssuer;
228    }
229
230    public String[] getValues() {
231        return values;
232    }
233
234    public void setValues(String[] values) {
235        this.values = values;
236    }
237
238    public Map<String, String> getFlags() {
239        return flags;
240    }
241
242    public void setFlags(Map<String, String> flags) {
243        this.flags = flags;
244    }
245
246    public boolean canConsumeInput() {
247        return canConsumeInput;
248    }
249
250    public void setCanConsumeInput(boolean canConsumeInput) {
251        this.canConsumeInput = canConsumeInput;
252    }
253
254    public void setOptionalResolver(boolean optionalResolver) {
255        this.optionalResolver = optionalResolver;
256    }
257
258    public boolean isOptionalResolver() {
259        return optionalResolver;
260    }
261
262    public boolean requiresInput() {
263        return requiresInput;
264    }
265
266    public void setRequiresInput(boolean requiresInput) {
267        this.requiresInput = requiresInput;
268    }
269
270    public String getSyntax() {
271        return getSyntax(null);
272    }
273
274    public String getSyntax(CommandIssuer issuer) {
275        if (commandIssuer) return null;
276        if (syntax == null) {
277            if (isOptionalInput) {
278                return "[" + getDisplayName(issuer) + "]";
279            } else if (requiresInput) {
280                return "<" + getDisplayName(issuer) + ">";
281            }
282        }
283        return syntax;
284    }
285
286    public void setSyntax(String syntax) {
287        this.syntax = syntax;
288    }
289
290    public String getConditions() {
291        return conditions;
292    }
293
294    public void setConditions(String conditions) {
295        this.conditions = conditions;
296    }
297
298    public Set<String> getRequiredPermissions() {
299        return permissions;
300    }
301
302    public void setNextParam(CommandParameter<CEC> nextParam) {
303        this.nextParam = nextParam;
304    }
305
306    public CommandParameter<CEC> getNextParam() {
307        return nextParam;
308    }
309
310    public boolean canExecuteWithoutInput() {
311        return (!canConsumeInput || isOptionalInput()) && (nextParam == null || nextParam.canExecuteWithoutInput());
312    }
313
314    public boolean isLast() {
315        return isLast;
316    }
317}