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