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