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; 034 035import java.util.List; 036import java.util.Map; 037 038@SuppressWarnings("WeakerAccess") 039public class CommandContexts <R extends CommandExecutionContext<?, ? extends CommandIssuer>> { 040 protected final Map<Class<?>, ContextResolver<?, R>> contextMap = Maps.newHashMap(); 041 protected final CommandManager manager; 042 043 CommandContexts(CommandManager manager) { 044 this.manager = manager; 045 registerContext(Integer.class, (c) -> { 046 try { 047 return ACFUtil.parseNumber(c.popFirstArg(), c.hasFlag("suffixes")).intValue(); 048 } catch (NumberFormatException e) { 049 throw new InvalidCommandArgument(MessageKeys.MUST_BE_A_NUMBER); 050 } 051 }); 052 registerContext(Long.class, (c) -> { 053 try { 054 return ACFUtil.parseNumber(c.popFirstArg(), c.hasFlag("suffixes")).longValue(); 055 } catch (NumberFormatException e) { 056 throw new InvalidCommandArgument(MessageKeys.MUST_BE_A_NUMBER); 057 } 058 059 }); 060 registerContext(Float.class, (c) -> { 061 try { 062 return ACFUtil.parseNumber(c.popFirstArg(), c.hasFlag("suffixes")).floatValue(); 063 } catch (NumberFormatException e) { 064 throw new InvalidCommandArgument(MessageKeys.MUST_BE_A_NUMBER); 065 } 066 }); 067 registerContext(Double.class, (c) -> { 068 try { 069 return ACFUtil.parseNumber(c.popFirstArg(), c.hasFlag("suffixes")).doubleValue(); 070 } catch (NumberFormatException e) { 071 throw new InvalidCommandArgument(MessageKeys.MUST_BE_A_NUMBER); 072 } 073 }); 074 registerContext(Number.class, (c) -> { 075 try { 076 return ACFUtil.parseNumber(c.popFirstArg(), c.hasFlag("suffixes")); 077 } catch (NumberFormatException e) { 078 throw new InvalidCommandArgument(MessageKeys.MUST_BE_A_NUMBER); 079 } 080 }); 081 registerContext(Boolean.class, (c) -> { 082 String test = c.popFirstArg(); 083 if (test == null) { 084 return null; 085 } 086 return ACFUtil.isTruthy(test); 087 }); 088 registerContext(String.class, (c) -> { 089 final Values values = c.getParam().getAnnotation(Values.class); 090 if (values != null) { 091 return c.popFirstArg(); 092 } 093 String ret = (c.isLastArg() && c.getParam().getAnnotation(Single.class) == null) ? 094 ACFUtil.join(c.getArgs()) 095 : 096 c.popFirstArg(); 097 098 Integer minLen = c.getFlagValue("minlen", (Integer) null); 099 Integer maxLen = c.getFlagValue("maxlen", (Integer) null); 100 if (minLen != null) { 101 if (ret.length() < minLen) { 102 throw new InvalidCommandArgument(MessageKeys.MUST_BE_MIN_LENGTH, "{min}", String.valueOf(minLen)); 103 } 104 } 105 if (maxLen != null) { 106 if (ret.length() > maxLen) { 107 throw new InvalidCommandArgument(MessageKeys.MUST_BE_MAX_LENGTH, "{max}", String.valueOf(maxLen)); 108 } 109 } 110 111 return ret; 112 }); 113 registerContext(String[].class, (c) -> { 114 String val; 115 // Go home IDEA, you're drunk 116 //noinspection unchecked 117 List<String> args = c.getArgs(); 118 if (c.isLastArg() && c.getParam().getAnnotation(Single.class) == null) { 119 val = ACFUtil.join(args); 120 } else { 121 val = c.popFirstArg(); 122 } 123 Split split = c.getParam().getAnnotation(Split.class); 124 if (split != null) { 125 if (val.isEmpty()) { 126 throw new InvalidCommandArgument(); 127 } 128 return ACFPatterns.getPattern(split.value()).split(val); 129 } else if (!c.isLastArg()) { 130 ACFUtil.sneaky(new IllegalStateException("Weird Command signature... String[] should be last or @Split")); 131 } 132 133 String[] result = args.toArray(new String[args.size()]); 134 args.clear(); 135 return result; 136 }); 137 138 registerContext(Enum.class, (c) -> { 139 final String first = c.popFirstArg(); 140 //noinspection unchecked 141 Class<? extends Enum<?>> enumCls = (Class<? extends Enum<?>>) c.getParam().getType(); 142 Enum<?> match = ACFUtil.simpleMatch(enumCls, first); 143 if (match == null) { 144 List<String> names = ACFUtil.enumNames(enumCls); 145 throw new InvalidCommandArgument(MessageKeys.PLEASE_SPECIFY_ONE_OF, "{valid}", ACFUtil.join(names)); 146 } 147 return match; 148 }); 149 registerOptionalContext(CommandHelp.class, (c) -> { 150 String first = c.getFirstArg(); 151 String last = c.getLastArg(); 152 int page = 1; 153 List<String> search = null; 154 if (last != null && ACFUtil.isInteger(last)) { 155 c.popLastArg(); 156 page = ACFUtil.parseInt(last); 157 if (!c.getArgs().isEmpty()) { 158 search = c.getArgs(); 159 } 160 } else if (first != null && ACFUtil.isInteger(first)) { 161 c.popFirstArg(); 162 page = ACFUtil.parseInt(first); 163 if (!c.getArgs().isEmpty()) { 164 search = c.getArgs(); 165 } 166 } else if (!c.getArgs().isEmpty()) { 167 search = c.getArgs(); 168 } 169 CommandHelp commandHelp = manager.generateCommandHelp(); 170 commandHelp.setPage(page); 171 commandHelp.setSearch(search); 172 return commandHelp; 173 }); 174 } 175 176 /** 177 * @deprecated Please switch to {@link #registerIssuerAwareContext(Class, IssuerAwareContextResolver)} 178 * as the core wants to use the platform agnostic term of "Issuer" instead of Sender 179 * @see #registerIssuerAwareContext(Class, IssuerAwareContextResolver) 180 */ 181 @Deprecated 182 public <T> void registerSenderAwareContext(Class<T> context, IssuerAwareContextResolver<T, R> supplier) { 183 contextMap.put(context, supplier); 184 } 185 186 /** 187 * Registers a context resolver that may conditionally consume input, falling back to using the context of the 188 * issuer to potentially fulfill this context. 189 * You may call {@link CommandExecutionContext#getFirstArg()} and then conditionally call {@link CommandExecutionContext#popFirstArg()} 190 * if you want to consume that input. 191 */ 192 public <T> void registerIssuerAwareContext(Class<T> context, IssuerAwareContextResolver<T, R> supplier) { 193 contextMap.put(context, supplier); 194 } 195 196 /** 197 * Registers a context resolver that will never consume input. It will always satisfy its context based on the 198 * issuer of the command, so it will not appear in syntax strings. 199 */ 200 public <T> void registerIssuerOnlyContext(Class<T> context, IssuerOnlyContextResolver<T, R> supplier) { 201 contextMap.put(context, supplier); 202 } 203 204 /** 205 * Registers a context that can safely accept a null input from the command issuer to resolve. This resolver should always 206 * call {@link CommandExecutionContext#popFirstArg()} 207 */ 208 public <T> void registerOptionalContext(Class<T> context, OptionalContextResolver<T, R> supplier) { 209 contextMap.put(context, supplier); 210 } 211 212 /** 213 * Registers a context that requires input from the command issuer to resolve. This resolver should always 214 * call {@link CommandExecutionContext#popFirstArg()} 215 */ 216 public <T> void registerContext(Class<T> context, ContextResolver<T, R> supplier) { 217 contextMap.put(context, supplier); 218 } 219 220 public ContextResolver<?, R> getResolver(Class<?> type) { 221 Class<?> rootType = type; 222 do { 223 if (type == Object.class) { 224 break; 225 } 226 227 final ContextResolver<?, R> resolver = contextMap.get(type); 228 if (resolver != null) { 229 return resolver; 230 } 231 } while ((type = type.getSuperclass()) != null); 232 233 this.manager.log(LogLevel.ERROR, "Could not find context resolver", new IllegalStateException("No context resolver defined for " + rootType.getName())); 234 return null; 235 } 236}