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}