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