Returns an array containing the constants of this enum type, in
diff --git a/docs/acf-core/src-html/co/aikar/commands/BaseCommand.html b/docs/acf-core/src-html/co/aikar/commands/BaseCommand.html
index 79192c88..8b4254ff 100644
--- a/docs/acf-core/src-html/co/aikar/commands/BaseCommand.html
+++ b/docs/acf-core/src-html/co/aikar/commands/BaseCommand.html
@@ -50,660 +50,970 @@
042import com.google.common.collect.Maps;
043import com.google.common.collect.SetMultimap;
044import com.google.common.collect.Sets;
-
045
-
046import java.lang.reflect.Constructor;
-
047import java.lang.reflect.InvocationTargetException;
-
048import java.lang.reflect.Method;
-
049import java.lang.reflect.Parameter;
-
050import java.util.ArrayList;
-
051import java.util.Arrays;
-
052import java.util.Collections;
-
053import java.util.HashMap;
-
054import java.util.HashSet;
-
055import java.util.List;
-
056import java.util.Map;
-
057import java.util.Objects;
-
058import java.util.Optional;
-
059import java.util.Set;
-
060import java.util.Stack;
-
061import java.util.stream.Collectors;
-
062import java.util.stream.Stream;
-
063
-
064@SuppressWarnings("unused")
-
065public abstract class BaseCommand {
-
066
-
067 public static final String CATCHUNKNOWN = "__catchunknown";
-
068 public static final String DEFAULT = "__default";
-
069 final SetMultimap<String, RegisteredCommand> subCommands = HashMultimap.create();
-
070 final Map<Class<?>, String> contextFlags = Maps.newHashMap();
-
071 private Method preCommandHandler;
-
072
-
073 @SuppressWarnings("WeakerAccess")
-
074 private String execLabel;
-
075 @SuppressWarnings("WeakerAccess")
-
076 private String execSubcommand;
-
077 @SuppressWarnings("WeakerAccess")
-
078 private String[] origArgs;
-
079 CommandManager<?, ?, ?, ?, ?, ?> manager = null;
-
080 BaseCommand parentCommand;
-
081 Map<String, RootCommand> registeredCommands = new HashMap<>();
-
082 String description;
-
083 String commandName;
-
084 String permission;
-
085 String conditions;
-
086
-
087 private ExceptionHandler exceptionHandler = null;
-
088 CommandOperationContext lastCommandOperationContext;
-
089 private String parentSubcommand;
-
090
-
091 public BaseCommand() {}
-
092 public BaseCommand(String cmd) {
-
093 this.commandName = cmd;
-
094 }
-
095
-
096 /**
-
097 * Gets the root command name that the user actually typed
-
098 * @return Name
-
099 */
-
100 public String getExecCommandLabel() {
-
101 return execLabel;
-
102 }
-
103
-
104 /**
-
105 * Gets the actual sub command name the user typed
-
106 * @return Name
+
045import org.jetbrains.annotations.Nullable;
+
046
+
047import java.lang.reflect.Constructor;
+
048import java.lang.reflect.InvocationTargetException;
+
049import java.lang.reflect.Method;
+
050import java.lang.reflect.Parameter;
+
051import java.util.ArrayList;
+
052import java.util.Arrays;
+
053import java.util.Collections;
+
054import java.util.HashMap;
+
055import java.util.HashSet;
+
056import java.util.List;
+
057import java.util.Map;
+
058import java.util.Objects;
+
059import java.util.Optional;
+
060import java.util.Set;
+
061import java.util.Stack;
+
062import java.util.stream.Collectors;
+
063import java.util.stream.Stream;
+
064
+
065/**
+
066 * A Base command is defined as a command group of related commands.
+
067 * A BaseCommand does not imply nor enforce that they use the same root command.
+
068 *
+
069 * It is up to the end user how to organize their command. you could use 1 base command per
+
070 * command in your application.
+
071 *
+
072 * Optionally (and encouraged), you can use the base command to represent a root command, and
+
073 * then each actionable command is a sub command
+
074 */
+
075
+
076@SuppressWarnings("unused")
+
077public abstract class BaseCommand {
+
078
+
079 /**
+
080 * This is a field which contains the magic key in the {@link #subCommands} map for the method to catch any unknown
+
081 * argument to command states.
+
082 */
+
083 static final String CATCHUNKNOWN = "__catchunknown";
+
084 /**
+
085 * This is a field which contains the magic key in the {@link #subCommands} map for the method which is default for the
+
086 * entire base command.
+
087 */
+
088 static final String DEFAULT = "__default";
+
089
+
090 /**
+
091 * A map of all the registered commands for this base command, keyed to each potential subcommand to access it.
+
092 */
+
093 final SetMultimap<String, RegisteredCommand> subCommands = HashMultimap.create();
+
094
+
095 /**
+
096 * A map of flags to pass to Context Resolution for every parameter of the type. This is like an automatic @Flags on each.
+
097 */
+
098 final Map<Class<?>, String> contextFlags = Maps.newHashMap();
+
099
+
100 /**
+
101 * What method was annoated with {@link PreCommand} to execute before commands.
+
102 */
+
103 @Nullable private Method preCommandHandler;
+
104
+
105 /**
+
106 * What root command the user actually entered to access the currently executing command
107 */
-
108 public String getExecSubcommand() {
-
109 return execSubcommand;
-
110 }
-
111
-
112 /**
-
113 * Gets the actual args in string form the user typed
-
114 * @return Args
-
115 */
-
116 public String[] getOrigArgs() {
-
117 return origArgs;
-
118 }
-
119
-
120 void setParentCommand(BaseCommand command) {
-
121 this.parentCommand = command;
-
122 }
-
123 void onRegister(CommandManager manager) {
-
124 onRegister(manager, this.commandName);
-
125 }
-
126 void onRegister(CommandManager manager, String cmd) {
-
127 manager.injectDependencies(this);
-
128 this.manager = manager;
-
129
-
130 final Annotations annotations = manager.getAnnotations();
-
131 final Class<? extends BaseCommand> self = this.getClass();
-
132
-
133 String[] cmdAliases = annotations.getAnnotationValues(self, CommandAlias.class, Annotations.REPLACEMENTS | Annotations.LOWERCASE | Annotations.NO_EMPTY);
-
134
-
135 if (cmd == null && cmdAliases != null) {
-
136 cmd = cmdAliases[0];
-
137 }
-
138
-
139 this.commandName = cmd != null ? cmd : self.getSimpleName().toLowerCase();
-
140 this.permission = annotations.getAnnotationValue(self, CommandPermission.class, Annotations.REPLACEMENTS);
-
141 this.description = this.commandName + " commands";
-
142 this.parentSubcommand = getParentSubcommand(self);
-
143 this.conditions = annotations.getAnnotationValue(self, Conditions.class, Annotations.REPLACEMENTS | Annotations.NO_EMPTY);
-
144
-
145 registerSubcommands();
-
146
-
147 if (cmdAliases != null) {
-
148 Set<String> cmdList = new HashSet<>();
-
149 Collections.addAll(cmdList, cmdAliases);
-
150 cmdList.remove(cmd);
-
151 for (String cmdAlias : cmdList) {
-
152 register(cmdAlias, this);
-
153 }
-
154 }
-
155
-
156 if (cmd != null) {
-
157 register(cmd, this);
-
158 }
-
159 registerSubclasses(cmd);
-
160
-
161 }
-
162
-
163 private void registerSubclasses(String cmd) {
-
164 for (Class<?> clazz : this.getClass().getDeclaredClasses()) {
-
165 if (BaseCommand.class.isAssignableFrom(clazz)) {
-
166 try {
-
167 BaseCommand subCommand = null;
-
168 Constructor<?>[] declaredConstructors = clazz.getDeclaredConstructors();
-
169 for (Constructor<?> declaredConstructor : declaredConstructors) {
-
170
-
171 declaredConstructor.setAccessible(true);
-
172 Parameter[] parameters = declaredConstructor.getParameters();
-
173 if (parameters.length == 1) {
-
174 subCommand = (BaseCommand) declaredConstructor.newInstance(this);
-
175 } else {
-
176 manager.log(LogLevel.INFO, "Found unusable constructor: " + declaredConstructor.getName() + "(" + Stream.of(parameters).map(p -> p.getType().getSimpleName() + " " + p.getName()).collect(Collectors.joining("<c2>,</c2> ")) + ")");
-
177 }
-
178 }
-
179 if (subCommand != null) {
-
180 subCommand.setParentCommand(this);
-
181 subCommand.onRegister(manager, cmd);
-
182 this.subCommands.putAll(subCommand.subCommands);
-
183 this.registeredCommands.putAll(subCommand.registeredCommands);
-
184 } else {
-
185 this.manager.log(LogLevel.ERROR, "Could not find a subcommand ctor for " + clazz.getName());
-
186 }
-
187 } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
-
188 this.manager.log(LogLevel.ERROR, "Error registering subclass", e);
-
189 }
-
190 }
-
191 }
-
192 }
-
193
-
194 private void registerSubcommands() {
-
195 final Annotations annotations = manager.getAnnotations();
-
196 boolean foundDefault = false;
-
197 boolean foundCatchUnknown = false;
-
198 boolean isParentEmpty = parentSubcommand.isEmpty();
-
199
-
200 for (Method method : this.getClass().getMethods()) {
-
201 method.setAccessible(true);
-
202 String sublist = null;
-
203 String sub = getSubcommandValue(method);
-
204 final boolean def = annotations.hasAnnotation(method, Default.class);
-
205 final String helpCommand = annotations.getAnnotationValue(method, HelpCommand.class, Annotations.NOTHING);
-
206 final String commandAliases = annotations.getAnnotationValue(method, CommandAlias.class, Annotations.NOTHING);
-
207
-
208 if (!isParentEmpty && def) {
-
209 sub = parentSubcommand;
-
210 }
-
211 if (isParentEmpty && (def || (!foundDefault && helpCommand != null))) {
-
212 if (!foundDefault) {
-
213 if (def) {
-
214 this.subCommands.get(DEFAULT).clear();
-
215 foundDefault = true;
-
216 }
-
217 registerSubcommand(method, DEFAULT);
-
218 } else {
-
219 ACFUtil.sneaky(new IllegalStateException("Multiple @Default/@HelpCommand commands, duplicate on " + method.getDeclaringClass().getName() + "#" + method.getName()));
-
220 }
-
221 }
-
222
-
223 if (sub != null) {
-
224 sublist = sub;
-
225 } else if (commandAliases != null) {
-
226 sublist = commandAliases;
-
227 } else if (helpCommand != null) {
-
228 sublist = helpCommand;
-
229 }
+
108 @SuppressWarnings("WeakerAccess")
+
109 private String execLabel;
+
110 /**
+
111 * What subcommand the user actually entered to access the currently executing command
+
112 */
+
113 @SuppressWarnings("WeakerAccess")
+
114 private String execSubcommand;
+
115 /**
+
116 * What arguments the user actually entered after the root command to access the currently executing command
+
117 */
+
118 @SuppressWarnings("WeakerAccess")
+
119 private String[] origArgs;
+
120
+
121 /**
+
122 * The manager this is registered to
+
123 */
+
124 CommandManager<?, ?, ?, ?, ?, ?> manager = null;
+
125
+
126 /**
+
127 * The command which owns this. This may be null if there are no owners.
+
128 */
+
129 BaseCommand parentCommand;
+
130 Map<String, RootCommand> registeredCommands = new HashMap<>();
+
131 /**
+
132 * The description of the command. This may be null if no description has been provided.
+
133 * Used for help documentation
+
134 */
+
135 @Nullable String description;
+
136 /**
+
137 * The name of the command. This may be null if no name has been provided.
+
138 */
+
139 @Nullable String commandName;
+
140 /**
+
141 * The permission of the command. This may be null if no permission has been provided.
+
142 */
+
143 @Nullable String permission;
+
144 /**
+
145 * The conditions of the command. This may be null if no conditions has been provided.
+
146 */
+
147 @Nullable String conditions;
+
148
+
149 /**
+
150 * The handler of all uncaught exceptions thrown by the user's command implementation.
+
151 */
+
152 private ExceptionHandler exceptionHandler = null;
+
153 /**
+
154 * The last operative context data of this command. This may be null if this command hasn't been run yet.
+
155 */
+
156 @Nullable CommandOperationContext lastCommandOperationContext;
+
157 /**
+
158 * If a parent exists to this command, and it has a Subcommand annotation, prefix all subcommands in this class with this
+
159 */
+
160 @Nullable private String parentSubcommand;
+
161
+
162 public BaseCommand() {}
+
163
+
164 /**
+
165 * Constructor based defining of commands will be removed in the next version bump.
+
166 * @deprecated Please switch to {@link CommandAlias} for defining all root commands.
+
167 * @param cmd
+
168 */
+
169 @Deprecated
+
170 public BaseCommand(@Nullable String cmd) {
+
171 this.commandName = cmd;
+
172 }
+
173
+
174 /**
+
175 * Gets the root command name that the user actually typed
+
176 * @return Name
+
177 */
+
178 public String getExecCommandLabel() {
+
179 return execLabel;
+
180 }
+
181
+
182 /**
+
183 * Gets the actual sub command name the user typed
+
184 * @return Name
+
185 */
+
186 public String getExecSubcommand() {
+
187 return execSubcommand;
+
188 }
+
189
+
190 /**
+
191 * Gets the actual args in string form the user typed
+
192 * @return Args
+
193 */
+
194 public String[] getOrigArgs() {
+
195 return origArgs;
+
196 }
+
197
+
198 /**
+
199 * This should be called whenever the command gets registered.
+
200 * It sets all required fields correctly and injects dependencies.
+
201 *
+
202 * @param manager
+
203 * The manager to register as this command's owner and handler.
+
204 */
+
205 void onRegister(CommandManager manager) {
+
206 onRegister(manager, this.commandName);
+
207 }
+
208
+
209 /**
+
210 * This should be called whenever the command gets registered.
+
211 * It sets all required fields correctly and injects dependencies.
+
212 *
+
213 * @param manager
+
214 * The manager to register as this command's owner and handler.
+
215 * @param cmd
+
216 * The command name to use register with.
+
217 */
+
218 private void onRegister(CommandManager manager, String cmd) {
+
219 manager.injectDependencies(this);
+
220 this.manager = manager;
+
221
+
222 final Annotations annotations = manager.getAnnotations();
+
223 final Class<? extends BaseCommand> self = this.getClass();
+
224
+
225 String[] cmdAliases = annotations.getAnnotationValues(self, CommandAlias.class, Annotations.REPLACEMENTS | Annotations.LOWERCASE | Annotations.NO_EMPTY);
+
226
+
227 if (cmd == null && cmdAliases != null) {
+
228 cmd = cmdAliases[0];
+
229 }
230
-
231 boolean preCommand = annotations.hasAnnotation(method, PreCommand.class);
-
232 boolean hasCatchUnknown = annotations.hasAnnotation(method, CatchUnknown.class) ||
-
233 annotations.hasAnnotation(method, CatchAll.class) ||
-
234 annotations.hasAnnotation(method, UnknownHandler.class);
-
235
-
236 if (hasCatchUnknown || (!foundCatchUnknown && helpCommand != null)) {
-
237 if (!foundCatchUnknown) {
-
238 if (hasCatchUnknown) {
-
239 this.subCommands.get(CATCHUNKNOWN).clear();
-
240 foundCatchUnknown = true;
-
241 }
-
242 registerSubcommand(method, CATCHUNKNOWN);
-
243 } else {
-
244 ACFUtil.sneaky(new IllegalStateException("Multiple @UnknownHandler/@HelpCommand commands, duplicate on " + method.getDeclaringClass().getName() + "#" + method.getName()));
-
245 }
-
246 } else if (preCommand) {
-
247 if (this.preCommandHandler == null) {
-
248 this.preCommandHandler = method;
-
249 } else {
-
250 ACFUtil.sneaky(new IllegalStateException("Multiple @PreCommand commands, duplicate on " + method.getDeclaringClass().getName() + "#" + method.getName()));
-
251 }
-
252 }
-
253 if (Objects.equals(method.getDeclaringClass(), this.getClass()) && sublist != null) {
-
254 registerSubcommand(method, sublist);
-
255 }
-
256 }
-
257 }
-
258
-
259 private String getSubcommandValue(Method method) {
-
260 final String sub = manager.getAnnotations().getAnnotationValue(method, Subcommand.class, Annotations.NOTHING);
-
261 if (sub == null) {
-
262 return null;
-
263 }
-
264 Class<?> clazz = method.getDeclaringClass();
-
265 String parent = getParentSubcommand(clazz);
-
266 return parent == null || parent.isEmpty() ? sub : parent + " " + sub;
-
267 }
+
231 this.commandName = cmd != null ? cmd : self.getSimpleName().toLowerCase();
+
232 this.permission = annotations.getAnnotationValue(self, CommandPermission.class, Annotations.REPLACEMENTS);
+
233 this.description = this.commandName + " commands";
+
234 this.parentSubcommand = getParentSubcommand(self);
+
235 this.conditions = annotations.getAnnotationValue(self, Conditions.class, Annotations.REPLACEMENTS | Annotations.NO_EMPTY);
+
236
+
237 registerSubcommands();
+
238
+
239 if (cmdAliases != null) {
+
240 Set<String> cmdList = new HashSet<>();
+
241 Collections.addAll(cmdList, cmdAliases);
+
242 cmdList.remove(cmd);
+
243 for (String cmdAlias : cmdList) {
+
244 register(cmdAlias, this);
+
245 }
+
246 }
+
247
+
248 if (cmd != null) {
+
249 register(cmd, this);
+
250 }
+
251 registerSubclasses(cmd);
+
252
+
253 }
+
254
+
255 /**
+
256 * This recursively registers all subclasses of the command as subcommands, if they are of type {@link BaseCommand}.
+
257 *
+
258 * @param cmd
+
259 * The command name of this command.
+
260 */
+
261 private void registerSubclasses(String cmd) {
+
262 for (Class<?> clazz : this.getClass().getDeclaredClasses()) {
+
263 if (BaseCommand.class.isAssignableFrom(clazz)) {
+
264 try {
+
265 BaseCommand subCommand = null;
+
266 Constructor<?>[] declaredConstructors = clazz.getDeclaredConstructors();
+
267 for (Constructor<?> declaredConstructor : declaredConstructors) {
268
-
269 private String getParentSubcommand(Class<?> clazz) {
-
270 List<String> subList = new ArrayList<>();
-
271 while (clazz != null) {
-
272 String sub = manager.getAnnotations().getAnnotationValue(clazz, Subcommand.class, Annotations.NOTHING);
-
273 if (sub != null) {
-
274 subList.add(sub);
-
275 }
-
276 clazz = clazz.getEnclosingClass();
-
277 }
-
278 Collections.reverse(subList);
-
279 return ACFUtil.join(subList, " ");
-
280 }
-
281
-
282 private void register(String name, BaseCommand cmd) {
-
283 String nameLower = name.toLowerCase();
-
284 RootCommand rootCommand = manager.obtainRootCommand(nameLower);
-
285 rootCommand.addChild(cmd);
-
286
-
287 this.registeredCommands.put(nameLower, rootCommand);
-
288 }
-
289
-
290 private void registerSubcommand(Method method, String subCommand) {
-
291 subCommand = manager.getCommandReplacements().replace(subCommand.toLowerCase());
-
292 final String[] subCommandParts = ACFPatterns.SPACE.split(subCommand);
-
293 // Must run getSubcommandPossibility BEFORE we rewrite it just after this.
-
294 Set<String> cmdList = getSubCommandPossibilityList(subCommandParts);
-
295
-
296 // Strip pipes off for auto complete addition
-
297 for (int i = 0; i < subCommandParts.length; i++) {
-
298 String[] split = ACFPatterns.PIPE.split(subCommandParts[i]);
-
299 if (split.length == 0 || split[0].isEmpty()) {
-
300 throw new IllegalArgumentException("Invalid @Subcommand configuration for " + method.getName() + " - parts can not start with | or be empty");
-
301 }
-
302 subCommandParts[i] = split[0];
-
303 }
-
304 String prefSubCommand = ApacheCommonsLangUtil.join(subCommandParts, " ");
-
305 final String[] aliasNames = manager.getAnnotations().getAnnotationValues(method, CommandAlias.class, Annotations.REPLACEMENTS | Annotations.LOWERCASE);
-
306
-
307 String cmdName = aliasNames != null ? aliasNames[0] : this.commandName + " ";
-
308 RegisteredCommand cmd = manager.createRegisteredCommand(this, cmdName, method, prefSubCommand);
-
309
-
310 for (String subcmd : cmdList) {
-
311 subCommands.put(subcmd, cmd);
-
312 }
-
313 cmd.addSubcommands(cmdList);
-
314
-
315 if (aliasNames != null) {
-
316 for (String name : aliasNames) {
-
317 register(name, new ForwardingCommand(this, subCommandParts));
-
318 }
-
319 }
-
320 }
-
321
-
322 /**
-
323 * Takes a string like "foo|bar baz|qux" and generates a list of
-
324 * - foo baz
-
325 * - foo qux
-
326 * - bar baz
-
327 * - bar qux
-
328 *
-
329 * For every possible sub command combination
-
330 *
-
331 * @param subCommandParts
-
332 * @return List of all sub command possibilities
-
333 */
-
334 private static Set<String> getSubCommandPossibilityList(String[] subCommandParts) {
-
335 int i = 0;
-
336 Set<String> current = null;
-
337 while (true) {
-
338 Set<String> newList = new HashSet<>();
-
339
-
340 if (i < subCommandParts.length) {
-
341 for (String s1 : ACFPatterns.PIPE.split(subCommandParts[i])) {
-
342 if (current != null) {
-
343 newList.addAll(current.stream().map(s -> s + " " + s1).collect(Collectors.toList()));
-
344 } else {
-
345 newList.add(s1);
-
346 }
-
347 }
-
348 }
-
349
-
350 if (i + 1 < subCommandParts.length) {
-
351 current = newList;
-
352 i = i + 1;
-
353 continue;
-
354 }
-
355
-
356 return newList;
+
269 declaredConstructor.setAccessible(true);
+
270 Parameter[] parameters = declaredConstructor.getParameters();
+
271 if (parameters.length == 1) {
+
272 subCommand = (BaseCommand) declaredConstructor.newInstance(this);
+
273 } else {
+
274 manager.log(LogLevel.INFO, "Found unusable constructor: " + declaredConstructor.getName() + "(" + Stream.of(parameters).map(p -> p.getType().getSimpleName() + " " + p.getName()).collect(Collectors.joining("<c2>,</c2> ")) + ")");
+
275 }
+
276 }
+
277 if (subCommand != null) {
+
278 subCommand.parentCommand = this;
+
279 subCommand.onRegister(manager, cmd);
+
280 this.subCommands.putAll(subCommand.subCommands);
+
281 this.registeredCommands.putAll(subCommand.registeredCommands);
+
282 } else {
+
283 this.manager.log(LogLevel.ERROR, "Could not find a subcommand ctor for " + clazz.getName());
+
284 }
+
285 } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
+
286 this.manager.log(LogLevel.ERROR, "Error registering subclass", e);
+
287 }
+
288 }
+
289 }
+
290 }
+
291
+
292 /**
+
293 * This registers all subcommands of the command.
+
294 */
+
295 private void registerSubcommands() {
+
296 final Annotations annotations = manager.getAnnotations();
+
297 boolean foundDefault = false;
+
298 boolean foundCatchUnknown = false;
+
299 boolean isParentEmpty = parentSubcommand == null || parentSubcommand.isEmpty();
+
300
+
301 for (Method method : this.getClass().getMethods()) {
+
302 method.setAccessible(true);
+
303 String sublist = null;
+
304 String sub = getSubcommandValue(method);
+
305 final boolean def = annotations.hasAnnotation(method, Default.class);
+
306 final String helpCommand = annotations.getAnnotationValue(method, HelpCommand.class, Annotations.NOTHING);
+
307 final String commandAliases = annotations.getAnnotationValue(method, CommandAlias.class, Annotations.NOTHING);
+
308
+
309 if (!isParentEmpty && def) {
+
310 sub = parentSubcommand;
+
311 }
+
312 if (isParentEmpty && (def || (!foundDefault && helpCommand != null))) {
+
313 if (!foundDefault) {
+
314 if (def) {
+
315 this.subCommands.get(DEFAULT).clear();
+
316 foundDefault = true;
+
317 }
+
318 registerSubcommand(method, DEFAULT);
+
319 } else {
+
320 ACFUtil.sneaky(new IllegalStateException("Multiple @Default/@HelpCommand commands, duplicate on " + method.getDeclaringClass().getName() + "#" + method.getName()));
+
321 }
+
322 }
+
323
+
324 if (sub != null) {
+
325 sublist = sub;
+
326 } else if (commandAliases != null) {
+
327 sublist = commandAliases;
+
328 } else if (helpCommand != null) {
+
329 sublist = helpCommand;
+
330 }
+
331
+
332 boolean preCommand = annotations.hasAnnotation(method, PreCommand.class);
+
333 boolean hasCatchUnknown = annotations.hasAnnotation(method, CatchUnknown.class) ||
+
334 annotations.hasAnnotation(method, CatchAll.class) ||
+
335 annotations.hasAnnotation(method, UnknownHandler.class);
+
336
+
337 if (hasCatchUnknown || (!foundCatchUnknown && helpCommand != null)) {
+
338 if (!foundCatchUnknown) {
+
339 if (hasCatchUnknown) {
+
340 this.subCommands.get(CATCHUNKNOWN).clear();
+
341 foundCatchUnknown = true;
+
342 }
+
343 registerSubcommand(method, CATCHUNKNOWN);
+
344 } else {
+
345 ACFUtil.sneaky(new IllegalStateException("Multiple @UnknownHandler/@HelpCommand commands, duplicate on " + method.getDeclaringClass().getName() + "#" + method.getName()));
+
346 }
+
347 } else if (preCommand) {
+
348 if (this.preCommandHandler == null) {
+
349 this.preCommandHandler = method;
+
350 } else {
+
351 ACFUtil.sneaky(new IllegalStateException("Multiple @PreCommand commands, duplicate on " + method.getDeclaringClass().getName() + "#" + method.getName()));
+
352 }
+
353 }
+
354 if (Objects.equals(method.getDeclaringClass(), this.getClass()) && sublist != null) {
+
355 registerSubcommand(method, sublist);
+
356 }
357 }
358 }
359
-
360 public void execute(CommandIssuer issuer, String commandLabel, String[] args) {
-
361 commandLabel = commandLabel.toLowerCase();
-
362 try {
-
363 CommandOperationContext commandContext = preCommandOperation(issuer, commandLabel, args, false);
-
364
-
365 if (args.length > 0) {
-
366 CommandSearch cmd = findSubCommand(args);
-
367 if (cmd != null) {
-
368 execSubcommand = cmd.getCheckSub();
-
369 final String[] execargs = Arrays.copyOfRange(args, cmd.argIndex, args.length);
-
370 executeCommand(commandContext, issuer, execargs, cmd.cmd);
-
371 return;
-
372 }
-
373 }
-
374
-
375 if (subCommands.get(DEFAULT) != null && args.length == 0) {
-
376 executeSubcommand(commandContext, DEFAULT, issuer, args);
-
377 } else if (subCommands.get(CATCHUNKNOWN) != null) {
-
378 if (!executeSubcommand(commandContext, CATCHUNKNOWN, issuer, args)) {
-
379 help(issuer, args);
-
380 }
-
381 } else if (subCommands.get(DEFAULT) != null) {
-
382 executeSubcommand(commandContext, DEFAULT, issuer, args);
-
383 }
-
384
-
385 } finally {
-
386 postCommandOperation();
-
387 }
-
388 }
-
389
-
390 RegisteredCommand<?> getRegisteredCommand(String[] args) {
-
391 final CommandSearch cmd = findSubCommand(args);
-
392 return cmd != null ? cmd.cmd : null;
-
393 }
-
394
-
395 private void postCommandOperation() {
-
396 CommandManager.commandOperationContext.get().pop();
-
397 execSubcommand = null;
-
398 execLabel = null;
-
399 origArgs = new String[]{};
-
400 }
-
401
-
402 private CommandOperationContext preCommandOperation(CommandIssuer issuer, String commandLabel, String[] args, boolean isAsync) {
-
403 Stack<CommandOperationContext> contexts = CommandManager.commandOperationContext.get();
-
404 CommandOperationContext context = this.manager.createCommandOperationContext(this, issuer, commandLabel, args, isAsync);
-
405 contexts.push(context);
-
406 lastCommandOperationContext = context;
-
407 execSubcommand = null;
-
408 execLabel = commandLabel;
-
409 origArgs = args;
-
410 return context;
-
411 }
-
412
-
413 public CommandIssuer getCurrentCommandIssuer() {
-
414 return CommandManager.getCurrentCommandIssuer();
-
415 }
-
416 public CommandManager getCurrentCommandManager() {
-
417 return CommandManager.getCurrentCommandManager();
-
418 }
-
419
-
420 private CommandSearch findSubCommand(String[] args) {
-
421 return findSubCommand(args, false);
-
422 }
-
423 private CommandSearch findSubCommand(String[] args, boolean completion) {
-
424 for (int i = args.length; i >= 0; i--) {
-
425 String checkSub = ApacheCommonsLangUtil.join(args, " ", 0, i).toLowerCase();
-
426 Set<RegisteredCommand> cmds = subCommands.get(checkSub);
-
427
-
428 final int extraArgs = args.length - i;
-
429 if (!cmds.isEmpty()) {
-
430 RegisteredCommand cmd = null;
-
431 if (cmds.size() == 1) {
-
432 cmd = Iterables.getOnlyElement(cmds);
-
433 } else {
-
434 Optional<RegisteredCommand> optCmd = cmds.stream().filter(c -> {
-
435 int required = c.requiredResolvers;
-
436 int optional = c.optionalResolvers;
-
437 return extraArgs <= required + optional && (completion || extraArgs >= required);
-
438 }).sorted((c1, c2) -> {
-
439 int a = c1.consumeInputResolvers;
-
440 int b = c2.consumeInputResolvers;
-
441
-
442 if (a == b) {
-
443 return 0;
-
444 }
-
445 return a < b ? 1 : -1;
-
446 }).findFirst();
-
447 if (optCmd.isPresent()) {
-
448 cmd = optCmd.get();
-
449 }
-
450 }
-
451 if (cmd != null) {
-
452 return new CommandSearch(cmd, i, checkSub);
-
453 }
-
454 }
-
455 }
-
456 return null;
-
457 }
-
458
-
459 private void executeCommand(CommandOperationContext commandOperationContext,
-
460 CommandIssuer issuer, String[] args, RegisteredCommand cmd) {
-
461 if (cmd.hasPermission(issuer)) {
-
462 commandOperationContext.setRegisteredCommand(cmd);
-
463 if (checkPrecommand(commandOperationContext, cmd, issuer, args)) {
-
464 return;
-
465 }
-
466 List<String> sargs = Lists.newArrayList(args);
-
467 cmd.invoke(issuer, sargs, commandOperationContext);
-
468 } else {
-
469 issuer.sendMessage(MessageType.ERROR, MessageKeys.PERMISSION_DENIED);
-
470 }
-
471 }
-
472
-
473 public boolean canExecute(CommandIssuer issuer, RegisteredCommand<?> cmd) {
-
474 return true;
-
475 }
-
476
-
477 public List<String> tabComplete(CommandIssuer issuer, String commandLabel, String[] args) {
-
478 return tabComplete(issuer, commandLabel, args, false);
-
479 }
-
480 public List<String> tabComplete(CommandIssuer issuer, String commandLabel, String[] args, boolean isAsync)
-
481 throws IllegalArgumentException {
-
482
-
483 commandLabel = commandLabel.toLowerCase();
-
484 if (args.length == 0) {
-
485 args = new String[]{""};
-
486 }
+
360 /**
+
361 * Gets the subcommand name of the method given.
+
362 *
+
363 * @param method
+
364 * The method to check.
+
365 *
+
366 * @return The name of the subcommand. It returns null if the input doesn't have {@link Subcommand} attached.
+
367 */
+
368 private String getSubcommandValue(Method method) {
+
369 final String sub = manager.getAnnotations().getAnnotationValue(method, Subcommand.class, Annotations.NOTHING);
+
370 if (sub == null) {
+
371 return null;
+
372 }
+
373 Class<?> clazz = method.getDeclaringClass();
+
374 String parent = getParentSubcommand(clazz);
+
375 return parent == null || parent.isEmpty() ? sub : parent + " " + sub;
+
376 }
+
377
+
378 private String getParentSubcommand(Class<?> clazz) {
+
379 List<String> subList = new ArrayList<>();
+
380 while (clazz != null) {
+
381 String sub = manager.getAnnotations().getAnnotationValue(clazz, Subcommand.class, Annotations.NOTHING);
+
382 if (sub != null) {
+
383 subList.add(sub);
+
384 }
+
385 clazz = clazz.getEnclosingClass();
+
386 }
+
387 Collections.reverse(subList);
+
388 return ACFUtil.join(subList, " ");
+
389 }
+
390
+
391 /**
+
392 * Registers the given {@link BaseCommand cmd} as a child of the {@link RootCommand} linked to the name given.
+
393 *
+
394 * @param name
+
395 * Name of the parent to cmd.
+
396 * @param cmd
+
397 * The {@link BaseCommand} to add as a child to the {@link RootCommand} owned name field.
+
398 */
+
399 private void register(String name, BaseCommand cmd) {
+
400 String nameLower = name.toLowerCase();
+
401 RootCommand rootCommand = manager.obtainRootCommand(nameLower);
+
402 rootCommand.addChild(cmd);
+
403
+
404 this.registeredCommands.put(nameLower, rootCommand);
+
405 }
+
406
+
407 /**
+
408 * Registers the given {@link Method} as a subcommand.
+
409 *
+
410 * @param method
+
411 * The method to register as a subcommand.
+
412 * @param subCommand
+
413 * The subcommand's name(s).
+
414 */
+
415 private void registerSubcommand(Method method, String subCommand) {
+
416 subCommand = manager.getCommandReplacements().replace(subCommand.toLowerCase());
+
417 final String[] subCommandParts = ACFPatterns.SPACE.split(subCommand);
+
418 // Must run getSubcommandPossibility BEFORE we rewrite it just after this.
+
419 Set<String> cmdList = getSubCommandPossibilityList(subCommandParts);
+
420
+
421 // Strip pipes off for auto complete addition
+
422 for (int i = 0; i < subCommandParts.length; i++) {
+
423 String[] split = ACFPatterns.PIPE.split(subCommandParts[i]);
+
424 if (split.length == 0 || split[0].isEmpty()) {
+
425 throw new IllegalArgumentException("Invalid @Subcommand configuration for " + method.getName() + " - parts can not start with | or be empty");
+
426 }
+
427 subCommandParts[i] = split[0];
+
428 }
+
429 String prefSubCommand = ApacheCommonsLangUtil.join(subCommandParts, " ");
+
430 final String[] aliasNames = manager.getAnnotations().getAnnotationValues(method, CommandAlias.class, Annotations.REPLACEMENTS | Annotations.LOWERCASE);
+
431
+
432 String cmdName = aliasNames != null ? aliasNames[0] : this.commandName + " ";
+
433 RegisteredCommand cmd = manager.createRegisteredCommand(this, cmdName, method, prefSubCommand);
+
434
+
435 for (String subcmd : cmdList) {
+
436 subCommands.put(subcmd, cmd);
+
437 }
+
438 cmd.addSubcommands(cmdList);
+
439
+
440 if (aliasNames != null) {
+
441 for (String name : aliasNames) {
+
442 register(name, new ForwardingCommand(this, subCommandParts));
+
443 }
+
444 }
+
445 }
+
446
+
447 /**
+
448 * Takes a string like "foo|bar baz|qux" and generates a list of
+
449 * - foo baz
+
450 * - foo qux
+
451 * - bar baz
+
452 * - bar qux
+
453 *
+
454 * For every possible sub command combination
+
455 *
+
456 * @param subCommandParts
+
457 * @return List of all sub command possibilities
+
458 */
+
459 private static Set<String> getSubCommandPossibilityList(String[] subCommandParts) {
+
460 int i = 0;
+
461 Set<String> current = null;
+
462 while (true) {
+
463 Set<String> newList = new HashSet<>();
+
464
+
465 if (i < subCommandParts.length) {
+
466 for (String s1 : ACFPatterns.PIPE.split(subCommandParts[i])) {
+
467 if (current != null) {
+
468 newList.addAll(current.stream().map(s -> s + " " + s1).collect(Collectors.toList()));
+
469 } else {
+
470 newList.add(s1);
+
471 }
+
472 }
+
473 }
+
474
+
475 if (i + 1 < subCommandParts.length) {
+
476 current = newList;
+
477 i = i + 1;
+
478 continue;
+
479 }
+
480
+
481 return newList;
+
482 }
+
483 }
+
484
+
485 public void execute(CommandIssuer issuer, String commandLabel, String[] args) {
+
486 commandLabel = commandLabel.toLowerCase();
487 try {
-
488 CommandOperationContext commandOperationContext = preCommandOperation(issuer, commandLabel, args, isAsync);
+
488 CommandOperationContext commandContext = preCommandOperation(issuer, commandLabel, args, false);
489
-
490 final CommandSearch search = findSubCommand(args, true);
-
491
-
492
-
493 final List<String> cmds = new ArrayList<>();
-
494
-
495 if (search != null) {
-
496 cmds.addAll(completeCommand(issuer, search.cmd, Arrays.copyOfRange(args, search.argIndex, args.length), commandLabel, isAsync));
-
497 } else if (subCommands.get(CATCHUNKNOWN).size() == 1) {
-
498 cmds.addAll(completeCommand(issuer, Iterables.getOnlyElement(subCommands.get(CATCHUNKNOWN)), args, commandLabel, isAsync));
-
499 } else if (subCommands.get(DEFAULT).size() == 1) {
-
500 cmds.addAll(completeCommand(issuer, Iterables.getOnlyElement(subCommands.get(DEFAULT)), args, commandLabel, isAsync));
-
501 }
-
502
-
503 return filterTabComplete(args[args.length - 1], cmds);
-
504 } finally {
-
505 postCommandOperation();
-
506 }
-
507 }
-
508
-
509 List<String> getCommandsForCompletion(CommandIssuer issuer, String[] args) {
-
510 final Set<String> cmds = new HashSet<>();
-
511 final int cmdIndex = Math.max(0, args.length - 1);
-
512 String argString = ApacheCommonsLangUtil.join(args, " ").toLowerCase();
-
513 for (Map.Entry<String, RegisteredCommand> entry : subCommands.entries()) {
-
514 final String key = entry.getKey();
-
515 if (key.startsWith(argString) && !CATCHUNKNOWN.equals(key) && !DEFAULT.equals(key)) {
-
516 final RegisteredCommand value = entry.getValue();
-
517 if (!value.hasPermission(issuer) || value.isPrivate) {
-
518 continue;
-
519 }
-
520
-
521 String[] split = ACFPatterns.SPACE.split(value.prefSubCommand);
-
522 cmds.add(split[cmdIndex]);
-
523 }
-
524 }
-
525 return new ArrayList<>(cmds);
-
526 }
-
527
-
528 private List<String> completeCommand(CommandIssuer issuer, RegisteredCommand cmd, String[] args, String commandLabel, boolean isAsync) {
-
529 if (!cmd.hasPermission(issuer) || args.length > cmd.consumeInputResolvers || args.length == 0 || cmd.complete == null) {
-
530 return ImmutableList.of();
-
531 }
-
532
-
533 List<String> cmds = manager.getCommandCompletions().of(cmd, issuer, args, isAsync);
-
534 return filterTabComplete(args[args.length-1], cmds);
-
535 }
-
536
-
537 private static List<String> filterTabComplete(String arg, List<String> cmds) {
-
538 return cmds.stream()
-
539 .distinct()
-
540 .filter(cmd -> cmd != null && (arg.isEmpty() || ApacheCommonsLangUtil.startsWithIgnoreCase(cmd, arg)))
-
541 .collect(Collectors.toList());
-
542 }
-
543
-
544 RegisteredCommand getSubcommand(String subcommand) {
-
545 return getSubcommand(subcommand, false);
-
546 }
-
547
-
548 RegisteredCommand getSubcommand(String subcommand, boolean requireOne) {
-
549 final Set<RegisteredCommand> commands = subCommands.get(subcommand);
-
550 if (!commands.isEmpty() && (!requireOne || commands.size() == 1)) {
-
551 return commands.iterator().next();
-
552 }
-
553 return null;
-
554 }
-
555
-
556 private boolean executeSubcommand(CommandOperationContext commandContext, String subcommand, CommandIssuer issuer, String... args) {
-
557 final RegisteredCommand cmd = this.getSubcommand(subcommand);
-
558 if (cmd != null) {
-
559 executeCommand(commandContext, issuer, args, cmd);
-
560 return true;
-
561 }
-
562
-
563 return false;
+
490 if (args.length > 0) {
+
491 CommandSearch cmd = findSubCommand(args);
+
492 if (cmd != null) {
+
493 execSubcommand = cmd.getCheckSub();
+
494 final String[] execargs = Arrays.copyOfRange(args, cmd.argIndex, args.length);
+
495 executeCommand(commandContext, issuer, execargs, cmd.cmd);
+
496 return;
+
497 }
+
498 }
+
499
+
500 if (subCommands.get(DEFAULT) != null && args.length == 0) {
+
501 findAndExecuteCommand(commandContext, DEFAULT, issuer, args);
+
502 } else if (subCommands.get(CATCHUNKNOWN) != null) {
+
503 if (!findAndExecuteCommand(commandContext, CATCHUNKNOWN, issuer, args)) {
+
504 help(issuer, args);
+
505 }
+
506 } else if (subCommands.get(DEFAULT) != null) {
+
507 findAndExecuteCommand(commandContext, DEFAULT, issuer, args);
+
508 }
+
509
+
510 } finally {
+
511 postCommandOperation();
+
512 }
+
513 }
+
514
+
515 /**
+
516 * Gets the registered command of the given arguments.
+
517 * @param args
+
518 * The arguments given by the user.
+
519 *
+
520 * @return The subcommand or null if none were found.
+
521 *
+
522 * @see #findSubCommand(String[])
+
523 */
+
524 RegisteredCommand<?> getRegisteredCommand(String[] args) {
+
525 final CommandSearch cmd = findSubCommand(args);
+
526 return cmd != null ? cmd.cmd : null;
+
527 }
+
528
+
529 /**
+
530 * This is ran after any command operation has been performed.
+
531 */
+
532 private void postCommandOperation() {
+
533 CommandManager.commandOperationContext.get().pop();
+
534 execSubcommand = null;
+
535 execLabel = null;
+
536 origArgs = new String[]{};
+
537 }
+
538
+
539 /**
+
540 * This is ran before any command operation has been performed.
+
541 *
+
542 * @param issuer
+
543 * The user who executed the command.
+
544 * @param commandLabel
+
545 * The label the user used to execute the command. This is not the command name, but their input.
+
546 * When there is multiple aliases, this is which alias was used
+
547 * @param args
+
548 * The arguments passed to the command when executing it.
+
549 * @param isAsync
+
550 * Whether the command is executed off of the main thread.
+
551 *
+
552 * @return The context which is being registered to the {@link CommandManager}'s {@link
+
553 * CommandManager#commandOperationContext thread local stack}.
+
554 */
+
555 private CommandOperationContext preCommandOperation(CommandIssuer issuer, String commandLabel, String[] args, boolean isAsync) {
+
556 Stack<CommandOperationContext> contexts = CommandManager.commandOperationContext.get();
+
557 CommandOperationContext context = this.manager.createCommandOperationContext(this, issuer, commandLabel, args, isAsync);
+
558 contexts.push(context);
+
559 lastCommandOperationContext = context;
+
560 execSubcommand = null;
+
561 execLabel = commandLabel;
+
562 origArgs = args;
+
563 return context;
564 }
565
-
566 private boolean checkPrecommand(CommandOperationContext commandOperationContext, RegisteredCommand cmd, CommandIssuer issuer, String[] args) {
-
567 Method pre = this.preCommandHandler;
-
568 if (pre != null) {
-
569 try {
-
570 Class<?>[] types = pre.getParameterTypes();
-
571 Object[] parameters = new Object[pre.getParameterCount()];
-
572 for (int i = 0; i < parameters.length; i++) {
-
573 Class<?> type = types[i];
-
574 Object issuerObject = issuer.getIssuer();
-
575 if (manager.isCommandIssuer(type) && type.isAssignableFrom(issuerObject.getClass())) {
-
576 parameters[i] = issuerObject;
-
577 } else if (CommandIssuer.class.isAssignableFrom(type)) {
-
578 parameters[i] = issuer;
-
579 } else if (RegisteredCommand.class.isAssignableFrom(type)) {
-
580 parameters[i] = cmd;
-
581 } else if (String[].class.isAssignableFrom((type))) {
-
582 parameters[i] = args;
-
583 } else {
-
584 parameters[i] = null;
-
585 }
-
586 }
-
587
-
588 return (boolean) pre.invoke(this, parameters);
-
589 } catch (IllegalAccessException | InvocationTargetException e) {
-
590 this.manager.log(LogLevel.ERROR, "Exception encountered while command pre-processing", e);
-
591 }
-
592 }
-
593 return false;
-
594 }
-
595
-
596 /** @deprecated Unstable API */ @Deprecated @UnstableAPI
-
597 public CommandHelp getCommandHelp() {
-
598 return manager.generateCommandHelp();
-
599 }
-
600
-
601 /** @deprecated Unstable API */ @Deprecated @UnstableAPI
-
602 public void showCommandHelp() {
-
603 getCommandHelp().showHelp();
-
604 }
-
605
-
606 public void help(Object issuer, String[] args) {
-
607 help(manager.getCommandIssuer(issuer), args);
-
608 }
-
609 public void help(CommandIssuer issuer, String[] args) {
-
610 issuer.sendMessage(MessageType.ERROR, MessageKeys.UNKNOWN_COMMAND);
-
611 }
-
612 public void doHelp(Object issuer, String... args) {
-
613 doHelp(manager.getCommandIssuer(issuer), args);
-
614 }
-
615 public void doHelp(CommandIssuer issuer, String... args) {
-
616 help(issuer, args);
-
617 }
-
618
-
619 public void showSyntax(CommandIssuer issuer, RegisteredCommand<?> cmd) {
-
620 issuer.sendMessage(MessageType.SYNTAX, MessageKeys.INVALID_SYNTAX,
-
621 "{command}", manager.getCommandPrefix(issuer) + cmd.command,
-
622 "{syntax}", cmd.syntaxText
-
623 );
-
624 }
-
625
-
626 public boolean hasPermission(Object issuer) {
-
627 return hasPermission(manager.getCommandIssuer(issuer));
-
628 }
-
629
-
630 public boolean hasPermission(CommandIssuer issuer) {
-
631 return permission == null || permission.isEmpty() || (manager.hasPermission(issuer, permission) && (parentCommand == null || parentCommand.hasPermission(issuer)));
-
632 }
-
633
-
634
-
635 public Set<String> getRequiredPermissions() {
-
636 if (this.permission == null || this.permission.isEmpty()) {
-
637 return ImmutableSet.of();
-
638 }
-
639 return Sets.newHashSet(ACFPatterns.COMMA.split(this.permission));
-
640 }
-
641
-
642 public boolean requiresPermission(String permission) {
-
643 return getRequiredPermissions().contains(permission) || this.parentCommand != null && parentCommand.requiresPermission(permission);
-
644 }
-
645
-
646 public String getName() {
-
647 return commandName;
-
648 }
-
649
-
650 public ExceptionHandler getExceptionHandler() {
-
651 return exceptionHandler;
-
652 }
-
653
-
654 public BaseCommand setExceptionHandler(ExceptionHandler exceptionHandler) {
-
655 this.exceptionHandler = exceptionHandler;
-
656 return this;
-
657 }
-
658
-
659 public RegisteredCommand getDefaultRegisteredCommand() {
-
660 return this.getSubcommand(DEFAULT);
-
661 }
-
662
-
663 public String setContextFlags(Class<?> cls, String flags) {
-
664 return this.contextFlags.put(cls, flags);
-
665 }
-
666
-
667 public String getContextFlags(Class<?> cls) {
-
668 return this.contextFlags.get(cls);
+
566 /**
+
567 * Gets the current command issuer.
+
568 *
+
569 * @return The current command issuer.
+
570 */
+
571 public CommandIssuer getCurrentCommandIssuer() {
+
572 return CommandManager.getCurrentCommandIssuer();
+
573 }
+
574
+
575 /**
+
576 * Gets the current command manager.
+
577 *
+
578 * @return The current command manager.
+
579 */
+
580 public CommandManager getCurrentCommandManager() {
+
581 return CommandManager.getCurrentCommandManager();
+
582 }
+
583
+
584 /**
+
585 * Finds a subcommand of the given arguments.
+
586 *
+
587 * @param args
+
588 * The arguments the user input.
+
589 *
+
590 * @return The identified subcommand.
+
591 *
+
592 * @see #findSubCommand(String[], boolean)
+
593 */
+
594 private CommandSearch findSubCommand(String[] args) {
+
595 return findSubCommand(args, false);
+
596 }
+
597
+
598 /**
+
599 * Finds a subcommand of the given arguments.
+
600 *
+
601 * @param args
+
602 * The arguments the user input.
+
603 * @param completion
+
604 * Whether or not completion of arguments should kick in. This may end up with worse than wanted results.
+
605 *
+
606 * @return The identified subcommand.
+
607 */
+
608 private CommandSearch findSubCommand(String[] args, boolean completion) {
+
609 for (int i = args.length; i >= 0; i--) {
+
610 String checkSub = ApacheCommonsLangUtil.join(args, " ", 0, i).toLowerCase();
+
611 Set<RegisteredCommand> cmds = subCommands.get(checkSub);
+
612
+
613 final int extraArgs = args.length - i;
+
614 if (!cmds.isEmpty()) {
+
615 RegisteredCommand cmd = null;
+
616 if (cmds.size() == 1) {
+
617 cmd = Iterables.getOnlyElement(cmds);
+
618 } else {
+
619 Optional<RegisteredCommand> optCmd = cmds.stream().filter(c -> {
+
620 int required = c.requiredResolvers;
+
621 int optional = c.optionalResolvers;
+
622 return extraArgs <= required + optional && (completion || extraArgs >= required);
+
623 }).min((c1, c2) -> {
+
624 int a = c1.consumeInputResolvers;
+
625 int b = c2.consumeInputResolvers;
+
626
+
627 if (a == b) {
+
628 return 0;
+
629 }
+
630 return a < b ? 1 : -1;
+
631 });
+
632 if (optCmd.isPresent()) {
+
633 cmd = optCmd.get();
+
634 }
+
635 }
+
636 if (cmd != null) {
+
637 return new CommandSearch(cmd, i, checkSub);
+
638 }
+
639 }
+
640 }
+
641 return null;
+
642 }
+
643
+
644 private void executeCommand(CommandOperationContext commandOperationContext,
+
645 CommandIssuer issuer, String[] args, RegisteredCommand cmd) {
+
646 if (cmd.hasPermission(issuer)) {
+
647 commandOperationContext.setRegisteredCommand(cmd);
+
648 if (checkPrecommand(commandOperationContext, cmd, issuer, args)) {
+
649 return;
+
650 }
+
651 List<String> sargs = Lists.newArrayList(args);
+
652 cmd.invoke(issuer, sargs, commandOperationContext);
+
653 } else {
+
654 issuer.sendMessage(MessageType.ERROR, MessageKeys.PERMISSION_DENIED);
+
655 }
+
656 }
+
657
+
658 /**
+
659 * Please use command conditions for restricting execution
+
660 * @deprecated See {@link CommandConditions}
+
661 * @param issuer
+
662 * @param cmd
+
663 * @return
+
664 */
+
665 @SuppressWarnings("DeprecatedIsStillUsed")
+
666 @Deprecated
+
667 public boolean canExecute(CommandIssuer issuer, RegisteredCommand<?> cmd) {
+
668 return true;
669 }
670
-
671 private static class CommandSearch { RegisteredCommand cmd; int argIndex; String checkSub;
-
672
-
673 CommandSearch(RegisteredCommand cmd, int argIndex, String checkSub) {
-
674 this.cmd = cmd;
-
675 this.argIndex = argIndex;
-
676 this.checkSub = checkSub;
-
677 }
-
678
-
679 String getCheckSub() {
-
680 return this.checkSub;
-
681 }
-
682
-
683 @Override
-
684 public boolean equals(Object o) {
-
685 if (this == o) return true;
-
686 if (o == null || getClass() != o.getClass()) return false;
-
687 CommandSearch that = (CommandSearch) o;
-
688 return argIndex == that.argIndex &&
-
689 Objects.equals(cmd, that.cmd) &&
-
690 Objects.equals(checkSub, that.checkSub);
-
691 }
-
692
-
693 @Override
-
694 public int hashCode() {
-
695 return Objects.hash(cmd, argIndex, checkSub);
-
696 }
-
697 }
-
698}
+
671 /**
+
672 * Gets tab completed data from the given command from the user.
+
673 *
+
674 * @param issuer
+
675 * The user who executed the tabcomplete.
+
676 * @param commandLabel
+
677 * The label which is being used by the user.
+
678 * @param args
+
679 * The arguments the user has typed so far.
+
680 *
+
681 * @return All possibilities in the tab complete.
+
682 */
+
683 public List<String> tabComplete(CommandIssuer issuer, String commandLabel, String[] args) {
+
684 return tabComplete(issuer, commandLabel, args, false);
+
685 }
+
686
+
687 /**
+
688 * Gets the tab complete suggestions from a given command. This will automatically find anything
+
689 * which is valid for the specified command through the command's implementation.
+
690 *
+
691 * @param issuer
+
692 * The issuer of the command.
+
693 * @param commandLabel
+
694 * The command name as entered by the user instead of the ACF registered name.
+
695 * @param args
+
696 * All arguments entered by the user.
+
697 * @param isAsync
+
698 * Whether this is run off of the main thread.
+
699 *
+
700 * @return The possibilities to tab complete in no particular order.
+
701 */
+
702 @SuppressWarnings("WeakerAccess")
+
703 public List<String> tabComplete(CommandIssuer issuer, String commandLabel, String[] args, boolean isAsync)
+
704 throws IllegalArgumentException {
+
705
+
706 commandLabel = commandLabel.toLowerCase();
+
707 if (args.length == 0) {
+
708 args = new String[]{""};
+
709 }
+
710 try {
+
711 CommandOperationContext commandOperationContext = preCommandOperation(issuer, commandLabel, args, isAsync);
+
712
+
713 final CommandSearch search = findSubCommand(args, true);
+
714
+
715
+
716 final List<String> cmds = new ArrayList<>();
+
717
+
718 if (search != null) {
+
719 cmds.addAll(completeCommand(issuer, search.cmd, Arrays.copyOfRange(args, search.argIndex, args.length), commandLabel, isAsync));
+
720 } else if (subCommands.get(CATCHUNKNOWN).size() == 1) {
+
721 cmds.addAll(completeCommand(issuer, Iterables.getOnlyElement(subCommands.get(CATCHUNKNOWN)), args, commandLabel, isAsync));
+
722 } else if (subCommands.get(DEFAULT).size() == 1) {
+
723 cmds.addAll(completeCommand(issuer, Iterables.getOnlyElement(subCommands.get(DEFAULT)), args, commandLabel, isAsync));
+
724 }
+
725
+
726 return filterTabComplete(args[args.length - 1], cmds);
+
727 } finally {
+
728 postCommandOperation();
+
729 }
+
730 }
+
731
+
732 /**
+
733 * Gets all subcommands which are possible to tabcomplete.
+
734 *
+
735 * @param issuer
+
736 * The command issuer.
+
737 * @param args
+
738 *
+
739 * @return
+
740 */
+
741 List<String> getCommandsForCompletion(CommandIssuer issuer, String[] args) {
+
742 final Set<String> cmds = new HashSet<>();
+
743 final int cmdIndex = Math.max(0, args.length - 1);
+
744 String argString = ApacheCommonsLangUtil.join(args, " ").toLowerCase();
+
745 for (Map.Entry<String, RegisteredCommand> entry : subCommands.entries()) {
+
746 final String key = entry.getKey();
+
747 if (key.startsWith(argString) && !CATCHUNKNOWN.equals(key) && !DEFAULT.equals(key)) {
+
748 final RegisteredCommand value = entry.getValue();
+
749 if (!value.hasPermission(issuer) || value.isPrivate) {
+
750 continue;
+
751 }
+
752
+
753 String[] split = ACFPatterns.SPACE.split(value.prefSubCommand);
+
754 cmds.add(split[cmdIndex]);
+
755 }
+
756 }
+
757 return new ArrayList<>(cmds);
+
758 }
+
759
+
760 /**
+
761 * Complete a command properly per issuer and input.
+
762 *
+
763 * @param issuer
+
764 * The user who executed this.
+
765 * @param cmd
+
766 * The command to be completed.
+
767 * @param args
+
768 * All arguments given by the user.
+
769 * @param commandLabel
+
770 * The command name the user used.
+
771 * @param isAsync
+
772 * Whether the command was executed async.
+
773 *
+
774 * @return All results to complete the command.
+
775 */
+
776 private List<String> completeCommand(CommandIssuer issuer, RegisteredCommand cmd, String[] args, String commandLabel, boolean isAsync) {
+
777 if (!cmd.hasPermission(issuer) || args.length > cmd.consumeInputResolvers || args.length == 0 || cmd.complete == null) {
+
778 return ImmutableList.of();
+
779 }
+
780
+
781 List<String> cmds = manager.getCommandCompletions().of(cmd, issuer, args, isAsync);
+
782 return filterTabComplete(args[args.length-1], cmds);
+
783 }
+
784
+
785 /**
+
786 * Gets the actual args in string form the user typed
+
787 * This returns a list of all tab complete options which are possible with the given argument and commands.
+
788 * @param arg
+
789 * Argument which was pressed tab on.
+
790 * @param cmds
+
791 * The possibilities to return.
+
792 *
+
793 * @return All possible options. This may be empty.
+
794 */
+
795 private static List<String> filterTabComplete(String arg, List<String> cmds) {
+
796 return cmds.stream()
+
797 .distinct()
+
798 .filter(cmd -> cmd != null && (arg.isEmpty() || ApacheCommonsLangUtil.startsWithIgnoreCase(cmd, arg)))
+
799 .collect(Collectors.toList());
+
800 }
+
801
+
802 /**
+
803 * Gets a registered command under the given subcommand name.
+
804 *
+
805 * @param subcommand
+
806 * The name of the subcommand requested.
+
807 *
+
808 * @return The subcommand found or null if none.
+
809 */
+
810 private RegisteredCommand getCommandBySubcommand(String subcommand) {
+
811 return getCommandBySubcommand(subcommand, false);
+
812 }
+
813
+
814 /**
+
815 * Gets a registered command under the given name.
+
816 * If requireOne is true, it won't accept more than a single matching subcommand.
+
817 *
+
818 * @param subcommand
+
819 * Name of the subcommand wanted.
+
820 * @param requireOne
+
821 * Whether to only accept 1 result.
+
822 *
+
823 * @return The subcommand found, or null if none/too many.
+
824 */
+
825 private RegisteredCommand getCommandBySubcommand(String subcommand, boolean requireOne) {
+
826 final Set<RegisteredCommand> commands = subCommands.get(subcommand);
+
827 if (!commands.isEmpty() && (!requireOne || commands.size() == 1)) {
+
828 return commands.iterator().next();
+
829 }
+
830 return null;
+
831 }
+
832
+
833 /**
+
834 * Internally calls {@link #executeCommand(CommandOperationContext, CommandIssuer, String[], RegisteredCommand)}
+
835 * and gets through {@link #getCommandBySubcommand(String)}.
+
836 *
+
837 * @param commandContext
+
838 * The command context to use.
+
839 * @param subcommand
+
840 * The subcommand to find the executor of.
+
841 * @param issuer
+
842 * The issuer who executed the subcommand.
+
843 * @param args
+
844 * All arguments given by the issuer.
+
845 *
+
846 * @return Whether it found a command or not.
+
847 *
+
848 * @see #executeCommand(CommandOperationContext, CommandIssuer, String[], RegisteredCommand)
+
849 * @see #getCommandBySubcommand(String)
+
850 * @see RegisteredCommand#invoke(CommandIssuer, List, CommandOperationContext)
+
851 */
+
852 private boolean findAndExecuteCommand(CommandOperationContext commandContext, String subcommand, CommandIssuer issuer, String... args) {
+
853 final RegisteredCommand cmd = this.getCommandBySubcommand(subcommand);
+
854 if (cmd != null) {
+
855 executeCommand(commandContext, issuer, args, cmd);
+
856 return true;
+
857 }
+
858
+
859 return false;
+
860 }
+
861
+
862 /**
+
863 * Executes the precommand and sees whether something is wrong. Ideally, you get false from this.
+
864 *
+
865 * @param commandOperationContext
+
866 * The context to use.
+
867 * @param cmd
+
868 * The command executed.
+
869 * @param issuer
+
870 * The issuer who executed the command.
+
871 * @param args
+
872 * The arguments the issuer provided.
+
873 *
+
874 * @return Whether something went wrong.
+
875 */
+
876 private boolean checkPrecommand(CommandOperationContext commandOperationContext, RegisteredCommand cmd, CommandIssuer issuer, String[] args) {
+
877 Method pre = this.preCommandHandler;
+
878 if (pre != null) {
+
879 try {
+
880 Class<?>[] types = pre.getParameterTypes();
+
881 Object[] parameters = new Object[pre.getParameterCount()];
+
882 for (int i = 0; i < parameters.length; i++) {
+
883 Class<?> type = types[i];
+
884 Object issuerObject = issuer.getIssuer();
+
885 if (manager.isCommandIssuer(type) && type.isAssignableFrom(issuerObject.getClass())) {
+
886 parameters[i] = issuerObject;
+
887 } else if (CommandIssuer.class.isAssignableFrom(type)) {
+
888 parameters[i] = issuer;
+
889 } else if (RegisteredCommand.class.isAssignableFrom(type)) {
+
890 parameters[i] = cmd;
+
891 } else if (String[].class.isAssignableFrom((type))) {
+
892 parameters[i] = args;
+
893 } else {
+
894 parameters[i] = null;
+
895 }
+
896 }
+
897
+
898 return (boolean) pre.invoke(this, parameters);
+
899 } catch (IllegalAccessException | InvocationTargetException e) {
+
900 this.manager.log(LogLevel.ERROR, "Exception encountered while command pre-processing", e);
+
901 }
+
902 }
+
903 return false;
+
904 }
+
905
+
906 /** @deprecated Unstable API */ @Deprecated @UnstableAPI
+
907 public CommandHelp getCommandHelp() {
+
908 return manager.generateCommandHelp();
+
909 }
+
910
+
911 /** @deprecated Unstable API */ @Deprecated @UnstableAPI
+
912 public void showCommandHelp() {
+
913 getCommandHelp().showHelp();
+
914 }
+
915
+
916 public void help(Object issuer, String[] args) {
+
917 help(manager.getCommandIssuer(issuer), args);
+
918 }
+
919 public void help(CommandIssuer issuer, String[] args) {
+
920 issuer.sendMessage(MessageType.ERROR, MessageKeys.UNKNOWN_COMMAND);
+
921 }
+
922 public void doHelp(Object issuer, String... args) {
+
923 doHelp(manager.getCommandIssuer(issuer), args);
+
924 }
+
925 public void doHelp(CommandIssuer issuer, String... args) {
+
926 help(issuer, args);
+
927 }
+
928
+
929 public void showSyntax(CommandIssuer issuer, RegisteredCommand<?> cmd) {
+
930 issuer.sendMessage(MessageType.SYNTAX, MessageKeys.INVALID_SYNTAX,
+
931 "{command}", manager.getCommandPrefix(issuer) + cmd.command,
+
932 "{syntax}", cmd.syntaxText
+
933 );
+
934 }
+
935
+
936 public boolean hasPermission(Object issuer) {
+
937 return hasPermission(manager.getCommandIssuer(issuer));
+
938 }
+
939
+
940 public boolean hasPermission(CommandIssuer issuer) {
+
941 return permission == null || permission.isEmpty() || (manager.hasPermission(issuer, permission) && (parentCommand == null || parentCommand.hasPermission(issuer)));
+
942 }
+
943
+
944
+
945 public Set<String> getRequiredPermissions() {
+
946 if (this.permission == null || this.permission.isEmpty()) {
+
947 return ImmutableSet.of();
+
948 }
+
949 return Sets.newHashSet(ACFPatterns.COMMA.split(this.permission));
+
950 }
+
951
+
952 public boolean requiresPermission(String permission) {
+
953 return getRequiredPermissions().contains(permission) || this.parentCommand != null && parentCommand.requiresPermission(permission);
+
954 }
+
955
+
956 public String getName() {
+
957 return commandName;
+
958 }
+
959
+
960 public ExceptionHandler getExceptionHandler() {
+
961 return exceptionHandler;
+
962 }
+
963
+
964 public BaseCommand setExceptionHandler(ExceptionHandler exceptionHandler) {
+
965 this.exceptionHandler = exceptionHandler;
+
966 return this;
+
967 }
+
968
+
969 public RegisteredCommand getDefaultRegisteredCommand() {
+
970 return this.getCommandBySubcommand(DEFAULT);
+
971 }
+
972
+
973 public String setContextFlags(Class<?> cls, String flags) {
+
974 return this.contextFlags.put(cls, flags);
+
975 }
+
976
+
977 public String getContextFlags(Class<?> cls) {
+
978 return this.contextFlags.get(cls);
+
979 }
+
980
+
981 private static class CommandSearch { RegisteredCommand cmd; int argIndex; String checkSub;
+
982
+
983 CommandSearch(RegisteredCommand cmd, int argIndex, String checkSub) {
+
984 this.cmd = cmd;
+
985 this.argIndex = argIndex;
+
986 this.checkSub = checkSub;
+
987 }
+
988
+
989 String getCheckSub() {
+
990 return this.checkSub;
+
991 }
+
992
+
993 @Override
+
994 public boolean equals(Object o) {
+
995 if (this == o) return true;
+
996 if (o == null || getClass() != o.getClass()) return false;
+
997 CommandSearch that = (CommandSearch) o;
+
998 return argIndex == that.argIndex &&
+
999 Objects.equals(cmd, that.cmd) &&
+
1000 Objects.equals(checkSub, that.checkSub);
+
1001 }
+
1002
+
1003 @Override
+
1004 public int hashCode() {
+
1005 return Objects.hash(cmd, argIndex, checkSub);
+
1006 }
+
1007 }
+
1008}
diff --git a/docs/acf-core/src-html/co/aikar/commands/CommandCompletions.AsyncCommandCompletionHandler.html b/docs/acf-core/src-html/co/aikar/commands/CommandCompletions.AsyncCommandCompletionHandler.html
index d9ff5a50..ca4306a7 100644
--- a/docs/acf-core/src-html/co/aikar/commands/CommandCompletions.AsyncCommandCompletionHandler.html
+++ b/docs/acf-core/src-html/co/aikar/commands/CommandCompletions.AsyncCommandCompletionHandler.html
@@ -39,138 +39,209 @@
031import java.util.HashMap;
032import java.util.List;
033import java.util.Map;
-
034import java.util.stream.Collectors;
-
035import java.util.stream.IntStream;
-
036
+
034import java.util.function.Supplier;
+
035import java.util.stream.Collectors;
+
036import java.util.stream.IntStream;
037
-
038@SuppressWarnings({"WeakerAccess", "UnusedReturnValue"})
-
039public class CommandCompletions <C extends CommandCompletionContext> {
-
040 private final CommandManager manager;
-
041 private Map<String, CommandCompletionHandler> completionMap = new HashMap<>();
-
042 private Map<Class, String> defaultCompletions = new HashMap<>();
-
043
-
044 public CommandCompletions(CommandManager manager) {
-
045 this.manager = manager;
-
046 registerAsyncCompletion("nothing", c -> ImmutableList.of());
-
047 registerAsyncCompletion("range", (c) -> {
-
048 String config = c.getConfig();
-
049 if (config == null) {
-
050 return ImmutableList.of();
-
051 }
-
052 final String[] ranges = ACFPatterns.DASH.split(config);
-
053 int start;
-
054 int end;
-
055 if (ranges.length != 2) {
-
056 start = 0;
-
057 end = ACFUtil.parseInt(ranges[0], 0);
-
058 } else {
-
059 start = ACFUtil.parseInt(ranges[0], 0);
-
060 end = ACFUtil.parseInt(ranges[1], 0);
-
061 }
-
062 return IntStream.rangeClosed(start, end).mapToObj(Integer::toString).collect(Collectors.toList());
-
063 });
-
064 registerAsyncCompletion("timeunits", (c) -> ImmutableList.of("minutes", "hours", "days", "weeks", "months", "years"));
-
065 }
-
066
-
067 public CommandCompletionHandler registerCompletion(String id, CommandCompletionHandler<C> handler) {
-
068 return this.completionMap.put("@" + id.toLowerCase(), handler);
-
069 }
-
070
-
071 public CommandCompletionHandler registerAsyncCompletion(String id, AsyncCommandCompletionHandler<C> handler) {
-
072 return this.completionMap.put("@" + id.toLowerCase(), handler);
-
073 }
-
074
-
075 /**
-
076 * @deprecated Feature Not done yet
-
077 * @param id
-
078 * @param classes
-
079 * @return
-
080 */
-
081 CommandCompletionHandler setDefaultCompletion(String id, Class... classes) {
-
082 // get completion with specified id
-
083 id = id.toLowerCase();
-
084 CommandCompletionHandler completion = completionMap.get(id);
-
085
-
086 if(completion == null) {
-
087 // Throw something because no completion with specified id
-
088 ACFUtil.sneaky(new CommandCompletionTextLookupException());
-
089 }
-
090
-
091 for(Class clazz : classes) {
-
092 defaultCompletions.put(clazz, id);
-
093 }
-
094
-
095 return completion;
+
038
+
039@SuppressWarnings({"WeakerAccess", "UnusedReturnValue"})
+
040public class CommandCompletions <C extends CommandCompletionContext> {
+
041 private final CommandManager manager;
+
042 private Map<String, CommandCompletionHandler> completionMap = new HashMap<>();
+
043 private Map<Class, String> defaultCompletions = new HashMap<>();
+
044
+
045 public CommandCompletions(CommandManager manager) {
+
046 this.manager = manager;
+
047 registerAsyncCompletion("nothing", c -> ImmutableList.of());
+
048 registerAsyncCompletion("range", (c) -> {
+
049 String config = c.getConfig();
+
050 if (config == null) {
+
051 return ImmutableList.of();
+
052 }
+
053 final String[] ranges = ACFPatterns.DASH.split(config);
+
054 int start;
+
055 int end;
+
056 if (ranges.length != 2) {
+
057 start = 0;
+
058 end = ACFUtil.parseInt(ranges[0], 0);
+
059 } else {
+
060 start = ACFUtil.parseInt(ranges[0], 0);
+
061 end = ACFUtil.parseInt(ranges[1], 0);
+
062 }
+
063 return IntStream.rangeClosed(start, end).mapToObj(Integer::toString).collect(Collectors.toList());
+
064 });
+
065 registerAsyncCompletion("timeunits", (c) -> ImmutableList.of("minutes", "hours", "days", "weeks", "months", "years"));
+
066 }
+
067
+
068 /**
+
069 * Registr a completion handler to provide command completions based on the user input.
+
070 *
+
071 * @param id
+
072 * @param handler
+
073 * @return
+
074 */
+
075 public CommandCompletionHandler registerCompletion(String id, CommandCompletionHandler<C> handler) {
+
076 return this.completionMap.put("@" + id.toLowerCase(), handler);
+
077 }
+
078
+
079 /**
+
080 * Registr a completion handler to provide command completions based on the user input.
+
081 * This handler is declared to be safe to be executed asynchronously.
+
082 * <p>
+
083 * Not all platforms support this, so if the platform does not support asynchronous execution,
+
084 * your handler will be executed on the main thread.
+
085 * <p>
+
086 * Use this anytime your handler does not need to access state that is not considered thread safe.
+
087 * <p>
+
088 * Use context.isAsync() to determine if you are async or not.
+
089 *
+
090 * @param id
+
091 * @param handler
+
092 * @return
+
093 */
+
094 public CommandCompletionHandler registerAsyncCompletion(String id, AsyncCommandCompletionHandler<C> handler) {
+
095 return this.completionMap.put("@" + id.toLowerCase(), handler);
096 }
097
-
098 @NotNull
-
099 List<String> of(RegisteredCommand cmd, CommandIssuer sender, String[] args, boolean isAsync) {
-
100 String[] completions = ACFPatterns.SPACE.split(cmd.complete);
-
101 final int argIndex = args.length - 1;
-
102
-
103 String input = args[argIndex];
-
104
-
105 String completion = argIndex < completions.length ? completions[argIndex] : null;
-
106 if (completion == null && completions.length > 0) {
-
107 completion = completions[completions.length - 1];
-
108 }
-
109 if (completion == null) {
-
110 return ImmutableList.of(input);
-
111 }
-
112
-
113 return getCompletionValues(cmd, sender, completion, args, isAsync);
-
114 }
-
115
-
116 List<String> getCompletionValues(RegisteredCommand command, CommandIssuer sender, String completion, String[] args, boolean isAsync) {
-
117 completion = manager.getCommandReplacements().replace(completion);
-
118
-
119 List<String> allCompletions = Lists.newArrayList();
-
120 String input = args.length > 0 ? args[args.length - 1] : "";
-
121
-
122 for (String value : ACFPatterns.PIPE.split(completion)) {
-
123 String[] complete = ACFPatterns.COLONEQUALS.split(value, 2);
-
124 CommandCompletionHandler handler = this.completionMap.get(complete[0].toLowerCase());
-
125 if (handler != null) {
-
126 if (isAsync && !(handler instanceof AsyncCommandCompletionHandler)) {
-
127 ACFUtil.sneaky(new SyncCompletionRequired());
-
128 return null;
-
129 }
-
130 String config = complete.length == 1 ? null : complete[1];
-
131 CommandCompletionContext context = manager.createCompletionContext(command, sender, input, config, args);
-
132
-
133 try {
-
134 //noinspection unchecked
-
135 Collection<String> completions = handler.getCompletions(context);
-
136 if (completions != null) {
-
137 allCompletions.addAll(completions);
-
138 continue;
-
139 }
-
140 //noinspection ConstantIfStatement,ConstantConditions
-
141 if (false) { // Hack to fool compiler. since its sneakily thrown.
-
142 throw new CommandCompletionTextLookupException();
-
143 }
-
144 } catch (CommandCompletionTextLookupException ignored) {
-
145 // This should only happen if some other feedback error occured.
-
146 } catch (Exception e) {
-
147 command.handleException(sender, Lists.newArrayList(args), e);
-
148 }
-
149 // Something went wrong in lookup, fall back to input
-
150 return ImmutableList.of(input);
-
151 } else {
-
152 // Plaintext value
-
153 allCompletions.add(value);
-
154 }
-
155 }
-
156 return allCompletions;
-
157 }
-
158
-
159 public interface CommandCompletionHandler <C extends CommandCompletionContext> {
-
160 Collection<String> getCompletions(C context) throws InvalidCommandArgument;
-
161 }
-
162 public interface AsyncCommandCompletionHandler <C extends CommandCompletionContext> extends CommandCompletionHandler <C> {}
-
163 public static class SyncCompletionRequired extends Exception {}
-
164
-
165}
+
098 /**
+
099 * Register a static list of command completions that will never change.
+
100 * Like @CommandCompletion, values are | (PIPE) separated.
+
101 * <p>
+
102 * Example: foo|bar|baz
+
103 *
+
104 * @param id
+
105 * @param list
+
106 * @return
+
107 */
+
108 public CommandCompletionHandler registerStaticCompletion(String id, String list) {
+
109 return registerStaticCompletion(id, ACFPatterns.PIPE.split(list));
+
110 }
+
111
+
112 /**
+
113 * Register a static list of command completions that will never change
+
114 *
+
115 * @param id
+
116 * @param completions
+
117 * @return
+
118 */
+
119 public CommandCompletionHandler registerStaticCompletion(String id, String[] completions) {
+
120 return registerStaticCompletion(id, Lists.newArrayList(completions));
+
121 }
+
122
+
123 /**
+
124 * Register a static list of command completions that will never change. The list is obtained from the supplier
+
125 * immediately as part of this method call.
+
126 *
+
127 * @param id
+
128 * @param supplier
+
129 * @return
+
130 */
+
131 public CommandCompletionHandler registerStaticCompletion(String id, Supplier<List<String>> supplier) {
+
132 return registerStaticCompletion(id, supplier.get());
+
133 }
+
134
+
135 /**
+
136 * Register a static list of command completions that will never change
+
137 *
+
138 * @param id
+
139 * @param completions
+
140 * @return
+
141 */
+
142 public CommandCompletionHandler registerStaticCompletion(String id, List<String> completions) {
+
143 return registerAsyncCompletion(id, x -> completions);
+
144 }
+
145
+
146 /**
+
147 * @deprecated Feature Not done yet
+
148 * @param id
+
149 * @param classes
+
150 * @return
+
151 */
+
152 CommandCompletionHandler setDefaultCompletion(String id, Class... classes) {
+
153 // get completion with specified id
+
154 id = id.toLowerCase();
+
155 CommandCompletionHandler completion = completionMap.get(id);
+
156
+
157 if(completion == null) {
+
158 // Throw something because no completion with specified id
+
159 ACFUtil.sneaky(new CommandCompletionTextLookupException());
+
160 }
+
161
+
162 for(Class clazz : classes) {
+
163 defaultCompletions.put(clazz, id);
+
164 }
+
165
+
166 return completion;
+
167 }
+
168
+
169 @NotNull
+
170 List<String> of(RegisteredCommand cmd, CommandIssuer sender, String[] args, boolean isAsync) {
+
171 String[] completions = ACFPatterns.SPACE.split(cmd.complete);
+
172 final int argIndex = args.length - 1;
+
173
+
174 String input = args[argIndex];
+
175
+
176 String completion = argIndex < completions.length ? completions[argIndex] : null;
+
177 if (completion == null && completions.length > 0) {
+
178 completion = completions[completions.length - 1];
+
179 }
+
180 if (completion == null) {
+
181 return ImmutableList.of(input);
+
182 }
+
183
+
184 return getCompletionValues(cmd, sender, completion, args, isAsync);
+
185 }
+
186
+
187 List<String> getCompletionValues(RegisteredCommand command, CommandIssuer sender, String completion, String[] args, boolean isAsync) {
+
188 completion = manager.getCommandReplacements().replace(completion);
+
189
+
190 List<String> allCompletions = Lists.newArrayList();
+
191 String input = args.length > 0 ? args[args.length - 1] : "";
+
192
+
193 for (String value : ACFPatterns.PIPE.split(completion)) {
+
194 String[] complete = ACFPatterns.COLONEQUALS.split(value, 2);
+
195 CommandCompletionHandler handler = this.completionMap.get(complete[0].toLowerCase());
+
196 if (handler != null) {
+
197 if (isAsync && !(handler instanceof AsyncCommandCompletionHandler)) {
+
198 ACFUtil.sneaky(new SyncCompletionRequired());
+
199 return null;
+
200 }
+
201 String config = complete.length == 1 ? null : complete[1];
+
202 CommandCompletionContext context = manager.createCompletionContext(command, sender, input, config, args);
+
203
+
204 try {
+
205 //noinspection unchecked
+
206 Collection<String> completions = handler.getCompletions(context);
+
207 if (completions != null) {
+
208 allCompletions.addAll(completions);
+
209 continue;
+
210 }
+
211 //noinspection ConstantIfStatement,ConstantConditions
+
212 if (false) { // Hack to fool compiler. since its sneakily thrown.
+
213 throw new CommandCompletionTextLookupException();
+
214 }
+
215 } catch (CommandCompletionTextLookupException ignored) {
+
216 // This should only happen if some other feedback error occured.
+
217 } catch (Exception e) {
+
218 command.handleException(sender, Lists.newArrayList(args), e);
+
219 }
+
220 // Something went wrong in lookup, fall back to input
+
221 return ImmutableList.of(input);
+
222 } else {
+
223 // Plaintext value
+
224 allCompletions.add(value);
+
225 }
+
226 }
+
227 return allCompletions;
+
228 }
+
229
+
230 public interface CommandCompletionHandler <C extends CommandCompletionContext> {
+
231 Collection<String> getCompletions(C context) throws InvalidCommandArgument;
+
232 }
+
233 public interface AsyncCommandCompletionHandler <C extends CommandCompletionContext> extends CommandCompletionHandler <C> {}
+
234 public static class SyncCompletionRequired extends Exception {}
+
235
+
236}
diff --git a/docs/acf-core/src-html/co/aikar/commands/CommandCompletions.CommandCompletionHandler.html b/docs/acf-core/src-html/co/aikar/commands/CommandCompletions.CommandCompletionHandler.html
index d9ff5a50..ca4306a7 100644
--- a/docs/acf-core/src-html/co/aikar/commands/CommandCompletions.CommandCompletionHandler.html
+++ b/docs/acf-core/src-html/co/aikar/commands/CommandCompletions.CommandCompletionHandler.html
@@ -39,138 +39,209 @@
031import java.util.HashMap;
032import java.util.List;
033import java.util.Map;
-
034import java.util.stream.Collectors;
-
035import java.util.stream.IntStream;
-
036
+
034import java.util.function.Supplier;
+
035import java.util.stream.Collectors;
+
036import java.util.stream.IntStream;
037
-
038@SuppressWarnings({"WeakerAccess", "UnusedReturnValue"})
-
039public class CommandCompletions <C extends CommandCompletionContext> {
-
040 private final CommandManager manager;
-
041 private Map<String, CommandCompletionHandler> completionMap = new HashMap<>();
-
042 private Map<Class, String> defaultCompletions = new HashMap<>();
-
043
-
044 public CommandCompletions(CommandManager manager) {
-
045 this.manager = manager;
-
046 registerAsyncCompletion("nothing", c -> ImmutableList.of());
-
047 registerAsyncCompletion("range", (c) -> {
-
048 String config = c.getConfig();
-
049 if (config == null) {
-
050 return ImmutableList.of();
-
051 }
-
052 final String[] ranges = ACFPatterns.DASH.split(config);
-
053 int start;
-
054 int end;
-
055 if (ranges.length != 2) {
-
056 start = 0;
-
057 end = ACFUtil.parseInt(ranges[0], 0);
-
058 } else {
-
059 start = ACFUtil.parseInt(ranges[0], 0);
-
060 end = ACFUtil.parseInt(ranges[1], 0);
-
061 }
-
062 return IntStream.rangeClosed(start, end).mapToObj(Integer::toString).collect(Collectors.toList());
-
063 });
-
064 registerAsyncCompletion("timeunits", (c) -> ImmutableList.of("minutes", "hours", "days", "weeks", "months", "years"));
-
065 }
-
066
-
067 public CommandCompletionHandler registerCompletion(String id, CommandCompletionHandler<C> handler) {
-
068 return this.completionMap.put("@" + id.toLowerCase(), handler);
-
069 }
-
070
-
071 public CommandCompletionHandler registerAsyncCompletion(String id, AsyncCommandCompletionHandler<C> handler) {
-
072 return this.completionMap.put("@" + id.toLowerCase(), handler);
-
073 }
-
074
-
075 /**
-
076 * @deprecated Feature Not done yet
-
077 * @param id
-
078 * @param classes
-
079 * @return
-
080 */
-
081 CommandCompletionHandler setDefaultCompletion(String id, Class... classes) {
-
082 // get completion with specified id
-
083 id = id.toLowerCase();
-
084 CommandCompletionHandler completion = completionMap.get(id);
-
085
-
086 if(completion == null) {
-
087 // Throw something because no completion with specified id
-
088 ACFUtil.sneaky(new CommandCompletionTextLookupException());
-
089 }
-
090
-
091 for(Class clazz : classes) {
-
092 defaultCompletions.put(clazz, id);
-
093 }
-
094
-
095 return completion;
+
038
+
039@SuppressWarnings({"WeakerAccess", "UnusedReturnValue"})
+
040public class CommandCompletions <C extends CommandCompletionContext> {
+
041 private final CommandManager manager;
+
042 private Map<String, CommandCompletionHandler> completionMap = new HashMap<>();
+
043 private Map<Class, String> defaultCompletions = new HashMap<>();
+
044
+
045 public CommandCompletions(CommandManager manager) {
+
046 this.manager = manager;
+
047 registerAsyncCompletion("nothing", c -> ImmutableList.of());
+
048 registerAsyncCompletion("range", (c) -> {
+
049 String config = c.getConfig();
+
050 if (config == null) {
+
051 return ImmutableList.of();
+
052 }
+
053 final String[] ranges = ACFPatterns.DASH.split(config);
+
054 int start;
+
055 int end;
+
056 if (ranges.length != 2) {
+
057 start = 0;
+
058 end = ACFUtil.parseInt(ranges[0], 0);
+
059 } else {
+
060 start = ACFUtil.parseInt(ranges[0], 0);
+
061 end = ACFUtil.parseInt(ranges[1], 0);
+
062 }
+
063 return IntStream.rangeClosed(start, end).mapToObj(Integer::toString).collect(Collectors.toList());
+
064 });
+
065 registerAsyncCompletion("timeunits", (c) -> ImmutableList.of("minutes", "hours", "days", "weeks", "months", "years"));
+
066 }
+
067
+
068 /**
+
069 * Registr a completion handler to provide command completions based on the user input.
+
070 *
+
071 * @param id
+
072 * @param handler
+
073 * @return
+
074 */
+
075 public CommandCompletionHandler registerCompletion(String id, CommandCompletionHandler<C> handler) {
+
076 return this.completionMap.put("@" + id.toLowerCase(), handler);
+
077 }
+
078
+
079 /**
+
080 * Registr a completion handler to provide command completions based on the user input.
+
081 * This handler is declared to be safe to be executed asynchronously.
+
082 * <p>
+
083 * Not all platforms support this, so if the platform does not support asynchronous execution,
+
084 * your handler will be executed on the main thread.
+
085 * <p>
+
086 * Use this anytime your handler does not need to access state that is not considered thread safe.
+
087 * <p>
+
088 * Use context.isAsync() to determine if you are async or not.
+
089 *
+
090 * @param id
+
091 * @param handler
+
092 * @return
+
093 */
+
094 public CommandCompletionHandler registerAsyncCompletion(String id, AsyncCommandCompletionHandler<C> handler) {
+
095 return this.completionMap.put("@" + id.toLowerCase(), handler);
096 }
097
-
098 @NotNull
-
099 List<String> of(RegisteredCommand cmd, CommandIssuer sender, String[] args, boolean isAsync) {
-
100 String[] completions = ACFPatterns.SPACE.split(cmd.complete);
-
101 final int argIndex = args.length - 1;
-
102
-
103 String input = args[argIndex];
-
104
-
105 String completion = argIndex < completions.length ? completions[argIndex] : null;
-
106 if (completion == null && completions.length > 0) {
-
107 completion = completions[completions.length - 1];
-
108 }
-
109 if (completion == null) {
-
110 return ImmutableList.of(input);
-
111 }
-
112
-
113 return getCompletionValues(cmd, sender, completion, args, isAsync);
-
114 }
-
115
-
116 List<String> getCompletionValues(RegisteredCommand command, CommandIssuer sender, String completion, String[] args, boolean isAsync) {
-
117 completion = manager.getCommandReplacements().replace(completion);
-
118
-
119 List<String> allCompletions = Lists.newArrayList();
-
120 String input = args.length > 0 ? args[args.length - 1] : "";
-
121
-
122 for (String value : ACFPatterns.PIPE.split(completion)) {
-
123 String[] complete = ACFPatterns.COLONEQUALS.split(value, 2);
-
124 CommandCompletionHandler handler = this.completionMap.get(complete[0].toLowerCase());
-
125 if (handler != null) {
-
126 if (isAsync && !(handler instanceof AsyncCommandCompletionHandler)) {
-
127 ACFUtil.sneaky(new SyncCompletionRequired());
-
128 return null;
-
129 }
-
130 String config = complete.length == 1 ? null : complete[1];
-
131 CommandCompletionContext context = manager.createCompletionContext(command, sender, input, config, args);
-
132
-
133 try {
-
134 //noinspection unchecked
-
135 Collection<String> completions = handler.getCompletions(context);
-
136 if (completions != null) {
-
137 allCompletions.addAll(completions);
-
138 continue;
-
139 }
-
140 //noinspection ConstantIfStatement,ConstantConditions
-
141 if (false) { // Hack to fool compiler. since its sneakily thrown.
-
142 throw new CommandCompletionTextLookupException();
-
143 }
-
144 } catch (CommandCompletionTextLookupException ignored) {
-
145 // This should only happen if some other feedback error occured.
-
146 } catch (Exception e) {
-
147 command.handleException(sender, Lists.newArrayList(args), e);
-
148 }
-
149 // Something went wrong in lookup, fall back to input
-
150 return ImmutableList.of(input);
-
151 } else {
-
152 // Plaintext value
-
153 allCompletions.add(value);
-
154 }
-
155 }
-
156 return allCompletions;
-
157 }
-
158
-
159 public interface CommandCompletionHandler <C extends CommandCompletionContext> {
-
160 Collection<String> getCompletions(C context) throws InvalidCommandArgument;
-
161 }
-
162 public interface AsyncCommandCompletionHandler <C extends CommandCompletionContext> extends CommandCompletionHandler <C> {}
-
163 public static class SyncCompletionRequired extends Exception {}
-
164
-
165}
+
098 /**
+
099 * Register a static list of command completions that will never change.
+
100 * Like @CommandCompletion, values are | (PIPE) separated.
+
101 * <p>
+
102 * Example: foo|bar|baz
+
103 *
+
104 * @param id
+
105 * @param list
+
106 * @return
+
107 */
+
108 public CommandCompletionHandler registerStaticCompletion(String id, String list) {
+
109 return registerStaticCompletion(id, ACFPatterns.PIPE.split(list));
+
110 }
+
111
+
112 /**
+
113 * Register a static list of command completions that will never change
+
114 *
+
115 * @param id
+
116 * @param completions
+
117 * @return
+
118 */
+
119 public CommandCompletionHandler registerStaticCompletion(String id, String[] completions) {
+
120 return registerStaticCompletion(id, Lists.newArrayList(completions));
+
121 }
+
122
+
123 /**
+
124 * Register a static list of command completions that will never change. The list is obtained from the supplier
+
125 * immediately as part of this method call.
+
126 *
+
127 * @param id
+
128 * @param supplier
+
129 * @return
+
130 */
+
131 public CommandCompletionHandler registerStaticCompletion(String id, Supplier<List<String>> supplier) {
+
132 return registerStaticCompletion(id, supplier.get());
+
133 }
+
134
+
135 /**
+
136 * Register a static list of command completions that will never change
+
137 *
+
138 * @param id
+
139 * @param completions
+
140 * @return
+
141 */
+
142 public CommandCompletionHandler registerStaticCompletion(String id, List<String> completions) {
+
143 return registerAsyncCompletion(id, x -> completions);
+
144 }
+
145
+
146 /**
+
147 * @deprecated Feature Not done yet
+
148 * @param id
+
149 * @param classes
+
150 * @return
+
151 */
+
152 CommandCompletionHandler setDefaultCompletion(String id, Class... classes) {
+
153 // get completion with specified id
+
154 id = id.toLowerCase();
+
155 CommandCompletionHandler completion = completionMap.get(id);
+
156
+
157 if(completion == null) {
+
158 // Throw something because no completion with specified id
+
159 ACFUtil.sneaky(new CommandCompletionTextLookupException());
+
160 }
+
161
+
162 for(Class clazz : classes) {
+
163 defaultCompletions.put(clazz, id);
+
164 }
+
165
+
166 return completion;
+
167 }
+
168
+
169 @NotNull
+
170 List<String> of(RegisteredCommand cmd, CommandIssuer sender, String[] args, boolean isAsync) {
+
171 String[] completions = ACFPatterns.SPACE.split(cmd.complete);
+
172 final int argIndex = args.length - 1;
+
173
+
174 String input = args[argIndex];
+
175
+
176 String completion = argIndex < completions.length ? completions[argIndex] : null;
+
177 if (completion == null && completions.length > 0) {
+
178 completion = completions[completions.length - 1];
+
179 }
+
180 if (completion == null) {
+
181 return ImmutableList.of(input);
+
182 }
+
183
+
184 return getCompletionValues(cmd, sender, completion, args, isAsync);
+
185 }
+
186
+
187 List<String> getCompletionValues(RegisteredCommand command, CommandIssuer sender, String completion, String[] args, boolean isAsync) {
+
188 completion = manager.getCommandReplacements().replace(completion);
+
189
+
190 List<String> allCompletions = Lists.newArrayList();
+
191 String input = args.length > 0 ? args[args.length - 1] : "";
+
192
+
193 for (String value : ACFPatterns.PIPE.split(completion)) {
+
194 String[] complete = ACFPatterns.COLONEQUALS.split(value, 2);
+
195 CommandCompletionHandler handler = this.completionMap.get(complete[0].toLowerCase());
+
196 if (handler != null) {
+
197 if (isAsync && !(handler instanceof AsyncCommandCompletionHandler)) {
+
198 ACFUtil.sneaky(new SyncCompletionRequired());
+
199 return null;
+
200 }
+
201 String config = complete.length == 1 ? null : complete[1];
+
202 CommandCompletionContext context = manager.createCompletionContext(command, sender, input, config, args);
+
203
+
204 try {
+
205 //noinspection unchecked
+
206 Collection<String> completions = handler.getCompletions(context);
+
207 if (completions != null) {
+
208 allCompletions.addAll(completions);
+
209 continue;
+
210 }
+
211 //noinspection ConstantIfStatement,ConstantConditions
+
212 if (false) { // Hack to fool compiler. since its sneakily thrown.
+
213 throw new CommandCompletionTextLookupException();
+
214 }
+
215 } catch (CommandCompletionTextLookupException ignored) {
+
216 // This should only happen if some other feedback error occured.
+
217 } catch (Exception e) {
+
218 command.handleException(sender, Lists.newArrayList(args), e);
+
219 }
+
220 // Something went wrong in lookup, fall back to input
+
221 return ImmutableList.of(input);
+
222 } else {
+
223 // Plaintext value
+
224 allCompletions.add(value);
+
225 }
+
226 }
+
227 return allCompletions;
+
228 }
+
229
+
230 public interface CommandCompletionHandler <C extends CommandCompletionContext> {
+
231 Collection<String> getCompletions(C context) throws InvalidCommandArgument;
+
232 }
+
233 public interface AsyncCommandCompletionHandler <C extends CommandCompletionContext> extends CommandCompletionHandler <C> {}
+
234 public static class SyncCompletionRequired extends Exception {}
+
235
+
236}
diff --git a/docs/acf-core/src-html/co/aikar/commands/CommandCompletions.SyncCompletionRequired.html b/docs/acf-core/src-html/co/aikar/commands/CommandCompletions.SyncCompletionRequired.html
index d9ff5a50..ca4306a7 100644
--- a/docs/acf-core/src-html/co/aikar/commands/CommandCompletions.SyncCompletionRequired.html
+++ b/docs/acf-core/src-html/co/aikar/commands/CommandCompletions.SyncCompletionRequired.html
@@ -39,138 +39,209 @@
031import java.util.HashMap;
032import java.util.List;
033import java.util.Map;
-
034import java.util.stream.Collectors;
-
035import java.util.stream.IntStream;
-
036
+
034import java.util.function.Supplier;
+
035import java.util.stream.Collectors;
+
036import java.util.stream.IntStream;
037
-
038@SuppressWarnings({"WeakerAccess", "UnusedReturnValue"})
-
039public class CommandCompletions <C extends CommandCompletionContext> {
-
040 private final CommandManager manager;
-
041 private Map<String, CommandCompletionHandler> completionMap = new HashMap<>();
-
042 private Map<Class, String> defaultCompletions = new HashMap<>();
-
043
-
044 public CommandCompletions(CommandManager manager) {
-
045 this.manager = manager;
-
046 registerAsyncCompletion("nothing", c -> ImmutableList.of());
-
047 registerAsyncCompletion("range", (c) -> {
-
048 String config = c.getConfig();
-
049 if (config == null) {
-
050 return ImmutableList.of();
-
051 }
-
052 final String[] ranges = ACFPatterns.DASH.split(config);
-
053 int start;
-
054 int end;
-
055 if (ranges.length != 2) {
-
056 start = 0;
-
057 end = ACFUtil.parseInt(ranges[0], 0);
-
058 } else {
-
059 start = ACFUtil.parseInt(ranges[0], 0);
-
060 end = ACFUtil.parseInt(ranges[1], 0);
-
061 }
-
062 return IntStream.rangeClosed(start, end).mapToObj(Integer::toString).collect(Collectors.toList());
-
063 });
-
064 registerAsyncCompletion("timeunits", (c) -> ImmutableList.of("minutes", "hours", "days", "weeks", "months", "years"));
-
065 }
-
066
-
067 public CommandCompletionHandler registerCompletion(String id, CommandCompletionHandler<C> handler) {
-
068 return this.completionMap.put("@" + id.toLowerCase(), handler);
-
069 }
-
070
-
071 public CommandCompletionHandler registerAsyncCompletion(String id, AsyncCommandCompletionHandler<C> handler) {
-
072 return this.completionMap.put("@" + id.toLowerCase(), handler);
-
073 }
-
074
-
075 /**
-
076 * @deprecated Feature Not done yet
-
077 * @param id
-
078 * @param classes
-
079 * @return
-
080 */
-
081 CommandCompletionHandler setDefaultCompletion(String id, Class... classes) {
-
082 // get completion with specified id
-
083 id = id.toLowerCase();
-
084 CommandCompletionHandler completion = completionMap.get(id);
-
085
-
086 if(completion == null) {
-
087 // Throw something because no completion with specified id
-
088 ACFUtil.sneaky(new CommandCompletionTextLookupException());
-
089 }
-
090
-
091 for(Class clazz : classes) {
-
092 defaultCompletions.put(clazz, id);
-
093 }
-
094
-
095 return completion;
+
038
+
039@SuppressWarnings({"WeakerAccess", "UnusedReturnValue"})
+
040public class CommandCompletions <C extends CommandCompletionContext> {
+
041 private final CommandManager manager;
+
042 private Map<String, CommandCompletionHandler> completionMap = new HashMap<>();
+
043 private Map<Class, String> defaultCompletions = new HashMap<>();
+
044
+
045 public CommandCompletions(CommandManager manager) {
+
046 this.manager = manager;
+
047 registerAsyncCompletion("nothing", c -> ImmutableList.of());
+
048 registerAsyncCompletion("range", (c) -> {
+
049 String config = c.getConfig();
+
050 if (config == null) {
+
051 return ImmutableList.of();
+
052 }
+
053 final String[] ranges = ACFPatterns.DASH.split(config);
+
054 int start;
+
055 int end;
+
056 if (ranges.length != 2) {
+
057 start = 0;
+
058 end = ACFUtil.parseInt(ranges[0], 0);
+
059 } else {
+
060 start = ACFUtil.parseInt(ranges[0], 0);
+
061 end = ACFUtil.parseInt(ranges[1], 0);
+
062 }
+
063 return IntStream.rangeClosed(start, end).mapToObj(Integer::toString).collect(Collectors.toList());
+
064 });
+
065 registerAsyncCompletion("timeunits", (c) -> ImmutableList.of("minutes", "hours", "days", "weeks", "months", "years"));
+
066 }
+
067
+
068 /**
+
069 * Registr a completion handler to provide command completions based on the user input.
+
070 *
+
071 * @param id
+
072 * @param handler
+
073 * @return
+
074 */
+
075 public CommandCompletionHandler registerCompletion(String id, CommandCompletionHandler<C> handler) {
+
076 return this.completionMap.put("@" + id.toLowerCase(), handler);
+
077 }
+
078
+
079 /**
+
080 * Registr a completion handler to provide command completions based on the user input.
+
081 * This handler is declared to be safe to be executed asynchronously.
+
082 * <p>
+
083 * Not all platforms support this, so if the platform does not support asynchronous execution,
+
084 * your handler will be executed on the main thread.
+
085 * <p>
+
086 * Use this anytime your handler does not need to access state that is not considered thread safe.
+
087 * <p>
+
088 * Use context.isAsync() to determine if you are async or not.
+
089 *
+
090 * @param id
+
091 * @param handler
+
092 * @return
+
093 */
+
094 public CommandCompletionHandler registerAsyncCompletion(String id, AsyncCommandCompletionHandler<C> handler) {
+
095 return this.completionMap.put("@" + id.toLowerCase(), handler);
096 }
097
-
098 @NotNull
-
099 List<String> of(RegisteredCommand cmd, CommandIssuer sender, String[] args, boolean isAsync) {
-
100 String[] completions = ACFPatterns.SPACE.split(cmd.complete);
-
101 final int argIndex = args.length - 1;
-
102
-
103 String input = args[argIndex];
-
104
-
105 String completion = argIndex < completions.length ? completions[argIndex] : null;
-
106 if (completion == null && completions.length > 0) {
-
107 completion = completions[completions.length - 1];
-
108 }
-
109 if (completion == null) {
-
110 return ImmutableList.of(input);
-
111 }
-
112
-
113 return getCompletionValues(cmd, sender, completion, args, isAsync);
-
114 }
-
115
-
116 List<String> getCompletionValues(RegisteredCommand command, CommandIssuer sender, String completion, String[] args, boolean isAsync) {
-
117 completion = manager.getCommandReplacements().replace(completion);
-
118
-
119 List<String> allCompletions = Lists.newArrayList();
-
120 String input = args.length > 0 ? args[args.length - 1] : "";
-
121
-
122 for (String value : ACFPatterns.PIPE.split(completion)) {
-
123 String[] complete = ACFPatterns.COLONEQUALS.split(value, 2);
-
124 CommandCompletionHandler handler = this.completionMap.get(complete[0].toLowerCase());
-
125 if (handler != null) {
-
126 if (isAsync && !(handler instanceof AsyncCommandCompletionHandler)) {
-
127 ACFUtil.sneaky(new SyncCompletionRequired());
-
128 return null;
-
129 }
-
130 String config = complete.length == 1 ? null : complete[1];
-
131 CommandCompletionContext context = manager.createCompletionContext(command, sender, input, config, args);
-
132
-
133 try {
-
134 //noinspection unchecked
-
135 Collection<String> completions = handler.getCompletions(context);
-
136 if (completions != null) {
-
137 allCompletions.addAll(completions);
-
138 continue;
-
139 }
-
140 //noinspection ConstantIfStatement,ConstantConditions
-
141 if (false) { // Hack to fool compiler. since its sneakily thrown.
-
142 throw new CommandCompletionTextLookupException();
-
143 }
-
144 } catch (CommandCompletionTextLookupException ignored) {
-
145 // This should only happen if some other feedback error occured.
-
146 } catch (Exception e) {
-
147 command.handleException(sender, Lists.newArrayList(args), e);
-
148 }
-
149 // Something went wrong in lookup, fall back to input
-
150 return ImmutableList.of(input);
-
151 } else {
-
152 // Plaintext value
-
153 allCompletions.add(value);
-
154 }
-
155 }
-
156 return allCompletions;
-
157 }
-
158
-
159 public interface CommandCompletionHandler <C extends CommandCompletionContext> {
-
160 Collection<String> getCompletions(C context) throws InvalidCommandArgument;
-
161 }
-
162 public interface AsyncCommandCompletionHandler <C extends CommandCompletionContext> extends CommandCompletionHandler <C> {}
-
163 public static class SyncCompletionRequired extends Exception {}
-
164
-
165}
+
098 /**
+
099 * Register a static list of command completions that will never change.
+
100 * Like @CommandCompletion, values are | (PIPE) separated.
+
101 * <p>
+
102 * Example: foo|bar|baz
+
103 *
+
104 * @param id
+
105 * @param list
+
106 * @return
+
107 */
+
108 public CommandCompletionHandler registerStaticCompletion(String id, String list) {
+
109 return registerStaticCompletion(id, ACFPatterns.PIPE.split(list));
+
110 }
+
111
+
112 /**
+
113 * Register a static list of command completions that will never change
+
114 *
+
115 * @param id
+
116 * @param completions
+
117 * @return
+
118 */
+
119 public CommandCompletionHandler registerStaticCompletion(String id, String[] completions) {
+
120 return registerStaticCompletion(id, Lists.newArrayList(completions));
+
121 }
+
122
+
123 /**
+
124 * Register a static list of command completions that will never change. The list is obtained from the supplier
+
125 * immediately as part of this method call.
+
126 *
+
127 * @param id
+
128 * @param supplier
+
129 * @return
+
130 */
+
131 public CommandCompletionHandler registerStaticCompletion(String id, Supplier<List<String>> supplier) {
+
132 return registerStaticCompletion(id, supplier.get());
+
133 }
+
134
+
135 /**
+
136 * Register a static list of command completions that will never change
+
137 *
+
138 * @param id
+
139 * @param completions
+
140 * @return
+
141 */
+
142 public CommandCompletionHandler registerStaticCompletion(String id, List<String> completions) {
+
143 return registerAsyncCompletion(id, x -> completions);
+
144 }
+
145
+
146 /**
+
147 * @deprecated Feature Not done yet
+
148 * @param id
+
149 * @param classes
+
150 * @return
+
151 */
+
152 CommandCompletionHandler setDefaultCompletion(String id, Class... classes) {
+
153 // get completion with specified id
+
154 id = id.toLowerCase();
+
155 CommandCompletionHandler completion = completionMap.get(id);
+
156
+
157 if(completion == null) {
+
158 // Throw something because no completion with specified id
+
159 ACFUtil.sneaky(new CommandCompletionTextLookupException());
+
160 }
+
161
+
162 for(Class clazz : classes) {
+
163 defaultCompletions.put(clazz, id);
+
164 }
+
165
+
166 return completion;
+
167 }
+
168
+
169 @NotNull
+
170 List<String> of(RegisteredCommand cmd, CommandIssuer sender, String[] args, boolean isAsync) {
+
171 String[] completions = ACFPatterns.SPACE.split(cmd.complete);
+
172 final int argIndex = args.length - 1;
+
173
+
174 String input = args[argIndex];
+
175
+
176 String completion = argIndex < completions.length ? completions[argIndex] : null;
+
177 if (completion == null && completions.length > 0) {
+
178 completion = completions[completions.length - 1];
+
179 }
+
180 if (completion == null) {
+
181 return ImmutableList.of(input);
+
182 }
+
183
+
184 return getCompletionValues(cmd, sender, completion, args, isAsync);
+
185 }
+
186
+
187 List<String> getCompletionValues(RegisteredCommand command, CommandIssuer sender, String completion, String[] args, boolean isAsync) {
+
188 completion = manager.getCommandReplacements().replace(completion);
+
189
+
190 List<String> allCompletions = Lists.newArrayList();
+
191 String input = args.length > 0 ? args[args.length - 1] : "";
+
192
+
193 for (String value : ACFPatterns.PIPE.split(completion)) {
+
194 String[] complete = ACFPatterns.COLONEQUALS.split(value, 2);
+
195 CommandCompletionHandler handler = this.completionMap.get(complete[0].toLowerCase());
+
196 if (handler != null) {
+
197 if (isAsync && !(handler instanceof AsyncCommandCompletionHandler)) {
+
198 ACFUtil.sneaky(new SyncCompletionRequired());
+
199 return null;
+
200 }
+
201 String config = complete.length == 1 ? null : complete[1];
+
202 CommandCompletionContext context = manager.createCompletionContext(command, sender, input, config, args);
+
203
+
204 try {
+
205 //noinspection unchecked
+
206 Collection<String> completions = handler.getCompletions(context);
+
207 if (completions != null) {
+
208 allCompletions.addAll(completions);
+
209 continue;
+
210 }
+
211 //noinspection ConstantIfStatement,ConstantConditions
+
212 if (false) { // Hack to fool compiler. since its sneakily thrown.
+
213 throw new CommandCompletionTextLookupException();
+
214 }
+
215 } catch (CommandCompletionTextLookupException ignored) {
+
216 // This should only happen if some other feedback error occured.
+
217 } catch (Exception e) {
+
218 command.handleException(sender, Lists.newArrayList(args), e);
+
219 }
+
220 // Something went wrong in lookup, fall back to input
+
221 return ImmutableList.of(input);
+
222 } else {
+
223 // Plaintext value
+
224 allCompletions.add(value);
+
225 }
+
226 }
+
227 return allCompletions;
+
228 }
+
229
+
230 public interface CommandCompletionHandler <C extends CommandCompletionContext> {
+
231 Collection<String> getCompletions(C context) throws InvalidCommandArgument;
+
232 }
+
233 public interface AsyncCommandCompletionHandler <C extends CommandCompletionContext> extends CommandCompletionHandler <C> {}
+
234 public static class SyncCompletionRequired extends Exception {}
+
235
+
236}
diff --git a/docs/acf-core/src-html/co/aikar/commands/CommandCompletions.html b/docs/acf-core/src-html/co/aikar/commands/CommandCompletions.html
index d9ff5a50..ca4306a7 100644
--- a/docs/acf-core/src-html/co/aikar/commands/CommandCompletions.html
+++ b/docs/acf-core/src-html/co/aikar/commands/CommandCompletions.html
@@ -39,138 +39,209 @@
031import java.util.HashMap;
032import java.util.List;
033import java.util.Map;
-
034import java.util.stream.Collectors;
-
035import java.util.stream.IntStream;
-
036
+
034import java.util.function.Supplier;
+
035import java.util.stream.Collectors;
+
036import java.util.stream.IntStream;
037
-
038@SuppressWarnings({"WeakerAccess", "UnusedReturnValue"})
-
039public class CommandCompletions <C extends CommandCompletionContext> {
-
040 private final CommandManager manager;
-
041 private Map<String, CommandCompletionHandler> completionMap = new HashMap<>();
-
042 private Map<Class, String> defaultCompletions = new HashMap<>();
-
043
-
044 public CommandCompletions(CommandManager manager) {
-
045 this.manager = manager;
-
046 registerAsyncCompletion("nothing", c -> ImmutableList.of());
-
047 registerAsyncCompletion("range", (c) -> {
-
048 String config = c.getConfig();
-
049 if (config == null) {
-
050 return ImmutableList.of();
-
051 }
-
052 final String[] ranges = ACFPatterns.DASH.split(config);
-
053 int start;
-
054 int end;
-
055 if (ranges.length != 2) {
-
056 start = 0;
-
057 end = ACFUtil.parseInt(ranges[0], 0);
-
058 } else {
-
059 start = ACFUtil.parseInt(ranges[0], 0);
-
060 end = ACFUtil.parseInt(ranges[1], 0);
-
061 }
-
062 return IntStream.rangeClosed(start, end).mapToObj(Integer::toString).collect(Collectors.toList());
-
063 });
-
064 registerAsyncCompletion("timeunits", (c) -> ImmutableList.of("minutes", "hours", "days", "weeks", "months", "years"));
-
065 }
-
066
-
067 public CommandCompletionHandler registerCompletion(String id, CommandCompletionHandler<C> handler) {
-
068 return this.completionMap.put("@" + id.toLowerCase(), handler);
-
069 }
-
070
-
071 public CommandCompletionHandler registerAsyncCompletion(String id, AsyncCommandCompletionHandler<C> handler) {
-
072 return this.completionMap.put("@" + id.toLowerCase(), handler);
-
073 }
-
074
-
075 /**
-
076 * @deprecated Feature Not done yet
-
077 * @param id
-
078 * @param classes
-
079 * @return
-
080 */
-
081 CommandCompletionHandler setDefaultCompletion(String id, Class... classes) {
-
082 // get completion with specified id
-
083 id = id.toLowerCase();
-
084 CommandCompletionHandler completion = completionMap.get(id);
-
085
-
086 if(completion == null) {
-
087 // Throw something because no completion with specified id
-
088 ACFUtil.sneaky(new CommandCompletionTextLookupException());
-
089 }
-
090
-
091 for(Class clazz : classes) {
-
092 defaultCompletions.put(clazz, id);
-
093 }
-
094
-
095 return completion;
+
038
+
039@SuppressWarnings({"WeakerAccess", "UnusedReturnValue"})
+
040public class CommandCompletions <C extends CommandCompletionContext> {
+
041 private final CommandManager manager;
+
042 private Map<String, CommandCompletionHandler> completionMap = new HashMap<>();
+
043 private Map<Class, String> defaultCompletions = new HashMap<>();
+
044
+
045 public CommandCompletions(CommandManager manager) {
+
046 this.manager = manager;
+
047 registerAsyncCompletion("nothing", c -> ImmutableList.of());
+
048 registerAsyncCompletion("range", (c) -> {
+
049 String config = c.getConfig();
+
050 if (config == null) {
+
051 return ImmutableList.of();
+
052 }
+
053 final String[] ranges = ACFPatterns.DASH.split(config);
+
054 int start;
+
055 int end;
+
056 if (ranges.length != 2) {
+
057 start = 0;
+
058 end = ACFUtil.parseInt(ranges[0], 0);
+
059 } else {
+
060 start = ACFUtil.parseInt(ranges[0], 0);
+
061 end = ACFUtil.parseInt(ranges[1], 0);
+
062 }
+
063 return IntStream.rangeClosed(start, end).mapToObj(Integer::toString).collect(Collectors.toList());
+
064 });
+
065 registerAsyncCompletion("timeunits", (c) -> ImmutableList.of("minutes", "hours", "days", "weeks", "months", "years"));
+
066 }
+
067
+
068 /**
+
069 * Registr a completion handler to provide command completions based on the user input.
+
070 *
+
071 * @param id
+
072 * @param handler
+
073 * @return
+
074 */
+
075 public CommandCompletionHandler registerCompletion(String id, CommandCompletionHandler<C> handler) {
+
076 return this.completionMap.put("@" + id.toLowerCase(), handler);
+
077 }
+
078
+
079 /**
+
080 * Registr a completion handler to provide command completions based on the user input.
+
081 * This handler is declared to be safe to be executed asynchronously.
+
082 * <p>
+
083 * Not all platforms support this, so if the platform does not support asynchronous execution,
+
084 * your handler will be executed on the main thread.
+
085 * <p>
+
086 * Use this anytime your handler does not need to access state that is not considered thread safe.
+
087 * <p>
+
088 * Use context.isAsync() to determine if you are async or not.
+
089 *
+
090 * @param id
+
091 * @param handler
+
092 * @return
+
093 */
+
094 public CommandCompletionHandler registerAsyncCompletion(String id, AsyncCommandCompletionHandler<C> handler) {
+
095 return this.completionMap.put("@" + id.toLowerCase(), handler);
096 }
097
-
098 @NotNull
-
099 List<String> of(RegisteredCommand cmd, CommandIssuer sender, String[] args, boolean isAsync) {
-
100 String[] completions = ACFPatterns.SPACE.split(cmd.complete);
-
101 final int argIndex = args.length - 1;
-
102
-
103 String input = args[argIndex];
-
104
-
105 String completion = argIndex < completions.length ? completions[argIndex] : null;
-
106 if (completion == null && completions.length > 0) {
-
107 completion = completions[completions.length - 1];
-
108 }
-
109 if (completion == null) {
-
110 return ImmutableList.of(input);
-
111 }
-
112
-
113 return getCompletionValues(cmd, sender, completion, args, isAsync);
-
114 }
-
115
-
116 List<String> getCompletionValues(RegisteredCommand command, CommandIssuer sender, String completion, String[] args, boolean isAsync) {
-
117 completion = manager.getCommandReplacements().replace(completion);
-
118
-
119 List<String> allCompletions = Lists.newArrayList();
-
120 String input = args.length > 0 ? args[args.length - 1] : "";
-
121
-
122 for (String value : ACFPatterns.PIPE.split(completion)) {
-
123 String[] complete = ACFPatterns.COLONEQUALS.split(value, 2);
-
124 CommandCompletionHandler handler = this.completionMap.get(complete[0].toLowerCase());
-
125 if (handler != null) {
-
126 if (isAsync && !(handler instanceof AsyncCommandCompletionHandler)) {
-
127 ACFUtil.sneaky(new SyncCompletionRequired());
-
128 return null;
-
129 }
-
130 String config = complete.length == 1 ? null : complete[1];
-
131 CommandCompletionContext context = manager.createCompletionContext(command, sender, input, config, args);
-
132
-
133 try {
-
134 //noinspection unchecked
-
135 Collection<String> completions = handler.getCompletions(context);
-
136 if (completions != null) {
-
137 allCompletions.addAll(completions);
-
138 continue;
-
139 }
-
140 //noinspection ConstantIfStatement,ConstantConditions
-
141 if (false) { // Hack to fool compiler. since its sneakily thrown.
-
142 throw new CommandCompletionTextLookupException();
-
143 }
-
144 } catch (CommandCompletionTextLookupException ignored) {
-
145 // This should only happen if some other feedback error occured.
-
146 } catch (Exception e) {
-
147 command.handleException(sender, Lists.newArrayList(args), e);
-
148 }
-
149 // Something went wrong in lookup, fall back to input
-
150 return ImmutableList.of(input);
-
151 } else {
-
152 // Plaintext value
-
153 allCompletions.add(value);
-
154 }
-
155 }
-
156 return allCompletions;
-
157 }
-
158
-
159 public interface CommandCompletionHandler <C extends CommandCompletionContext> {
-
160 Collection<String> getCompletions(C context) throws InvalidCommandArgument;
-
161 }
-
162 public interface AsyncCommandCompletionHandler <C extends CommandCompletionContext> extends CommandCompletionHandler <C> {}
-
163 public static class SyncCompletionRequired extends Exception {}
-
164
-
165}
+
098 /**
+
099 * Register a static list of command completions that will never change.
+
100 * Like @CommandCompletion, values are | (PIPE) separated.
+
101 * <p>
+
102 * Example: foo|bar|baz
+
103 *
+
104 * @param id
+
105 * @param list
+
106 * @return
+
107 */
+
108 public CommandCompletionHandler registerStaticCompletion(String id, String list) {
+
109 return registerStaticCompletion(id, ACFPatterns.PIPE.split(list));
+
110 }
+
111
+
112 /**
+
113 * Register a static list of command completions that will never change
+
114 *
+
115 * @param id
+
116 * @param completions
+
117 * @return
+
118 */
+
119 public CommandCompletionHandler registerStaticCompletion(String id, String[] completions) {
+
120 return registerStaticCompletion(id, Lists.newArrayList(completions));
+
121 }
+
122
+
123 /**
+
124 * Register a static list of command completions that will never change. The list is obtained from the supplier
+
125 * immediately as part of this method call.
+
126 *
+
127 * @param id
+
128 * @param supplier
+
129 * @return
+
130 */
+
131 public CommandCompletionHandler registerStaticCompletion(String id, Supplier<List<String>> supplier) {
+
132 return registerStaticCompletion(id, supplier.get());
+
133 }
+
134
+
135 /**
+
136 * Register a static list of command completions that will never change
+
137 *
+
138 * @param id
+
139 * @param completions
+
140 * @return
+
141 */
+
142 public CommandCompletionHandler registerStaticCompletion(String id, List<String> completions) {
+
143 return registerAsyncCompletion(id, x -> completions);
+
144 }
+
145
+
146 /**
+
147 * @deprecated Feature Not done yet
+
148 * @param id
+
149 * @param classes
+
150 * @return
+
151 */
+
152 CommandCompletionHandler setDefaultCompletion(String id, Class... classes) {
+
153 // get completion with specified id
+
154 id = id.toLowerCase();
+
155 CommandCompletionHandler completion = completionMap.get(id);
+
156
+
157 if(completion == null) {
+
158 // Throw something because no completion with specified id
+
159 ACFUtil.sneaky(new CommandCompletionTextLookupException());
+
160 }
+
161
+
162 for(Class clazz : classes) {
+
163 defaultCompletions.put(clazz, id);
+
164 }
+
165
+
166 return completion;
+
167 }
+
168
+
169 @NotNull
+
170 List<String> of(RegisteredCommand cmd, CommandIssuer sender, String[] args, boolean isAsync) {
+
171 String[] completions = ACFPatterns.SPACE.split(cmd.complete);
+
172 final int argIndex = args.length - 1;
+
173
+
174 String input = args[argIndex];
+
175
+
176 String completion = argIndex < completions.length ? completions[argIndex] : null;
+
177 if (completion == null && completions.length > 0) {
+
178 completion = completions[completions.length - 1];
+
179 }
+
180 if (completion == null) {
+
181 return ImmutableList.of(input);
+
182 }
+
183
+
184 return getCompletionValues(cmd, sender, completion, args, isAsync);
+
185 }
+
186
+
187 List<String> getCompletionValues(RegisteredCommand command, CommandIssuer sender, String completion, String[] args, boolean isAsync) {
+
188 completion = manager.getCommandReplacements().replace(completion);
+
189
+
190 List<String> allCompletions = Lists.newArrayList();
+
191 String input = args.length > 0 ? args[args.length - 1] : "";
+
192
+
193 for (String value : ACFPatterns.PIPE.split(completion)) {
+
194 String[] complete = ACFPatterns.COLONEQUALS.split(value, 2);
+
195 CommandCompletionHandler handler = this.completionMap.get(complete[0].toLowerCase());
+
196 if (handler != null) {
+
197 if (isAsync && !(handler instanceof AsyncCommandCompletionHandler)) {
+
198 ACFUtil.sneaky(new SyncCompletionRequired());
+
199 return null;
+
200 }
+
201 String config = complete.length == 1 ? null : complete[1];
+
202 CommandCompletionContext context = manager.createCompletionContext(command, sender, input, config, args);
+
203
+
204 try {
+
205 //noinspection unchecked
+
206 Collection<String> completions = handler.getCompletions(context);
+
207 if (completions != null) {
+
208 allCompletions.addAll(completions);
+
209 continue;
+
210 }
+
211 //noinspection ConstantIfStatement,ConstantConditions
+
212 if (false) { // Hack to fool compiler. since its sneakily thrown.
+
213 throw new CommandCompletionTextLookupException();
+
214 }
+
215 } catch (CommandCompletionTextLookupException ignored) {
+
216 // This should only happen if some other feedback error occured.
+
217 } catch (Exception e) {
+
218 command.handleException(sender, Lists.newArrayList(args), e);
+
219 }
+
220 // Something went wrong in lookup, fall back to input
+
221 return ImmutableList.of(input);
+
222 } else {
+
223 // Plaintext value
+
224 allCompletions.add(value);
+
225 }
+
226 }
+
227 return allCompletions;
+
228 }
+
229
+
230 public interface CommandCompletionHandler <C extends CommandCompletionContext> {
+
231 Collection<String> getCompletions(C context) throws InvalidCommandArgument;
+
232 }
+
233 public interface AsyncCommandCompletionHandler <C extends CommandCompletionContext> extends CommandCompletionHandler <C> {}
+
234 public static class SyncCompletionRequired extends Exception {}
+
235
+
236}
diff --git a/docs/acf-core/src-html/co/aikar/commands/annotation/CatchUnknown.html b/docs/acf-core/src-html/co/aikar/commands/annotation/CatchUnknown.html
index 236694c6..5a1e1298 100644
--- a/docs/acf-core/src-html/co/aikar/commands/annotation/CatchUnknown.html
+++ b/docs/acf-core/src-html/co/aikar/commands/annotation/CatchUnknown.html
@@ -36,10 +36,19 @@
028import java.lang.annotation.RetentionPolicy;
029import java.lang.annotation.Target;
030
-
031@Retention(RetentionPolicy.RUNTIME)
-
032@Target({ElementType.METHOD})
-
033public @interface CatchUnknown {
-
034}
+
031/**
+
032 * Defines a method that should receive any unknown command for the related root command.
+
033 *
+
034 * For example, if a BaseCommand /foo has a method with this, and /foo someunknowncommand is used
+
035 *
+
036 * If a method is tagged with this annotation, it will catch unknown commands and let you react to them.
+
037 *
+
038 * Only one instance of this annotation can be used per root command.
+
039 */
+
040@Retention(RetentionPolicy.RUNTIME)
+
041@Target({ElementType.METHOD})
+
042public @interface CatchUnknown {
+
043}
diff --git a/docs/acf-core/src-html/co/aikar/commands/annotation/CommandAlias.html b/docs/acf-core/src-html/co/aikar/commands/annotation/CommandAlias.html
index 898da37c..5579279e 100644
--- a/docs/acf-core/src-html/co/aikar/commands/annotation/CommandAlias.html
+++ b/docs/acf-core/src-html/co/aikar/commands/annotation/CommandAlias.html
@@ -36,11 +36,19 @@
028import java.lang.annotation.RetentionPolicy;
029import java.lang.annotation.Target;
030
-
031@Retention(RetentionPolicy.RUNTIME)
-
032@Target({ElementType.METHOD, ElementType.TYPE})
-
033public @interface CommandAlias {
-
034 String value();
-
035}
+
031/**
+
032 * Allows to add a single or several command alias(es).
+
033 * In order to add more than one in a single go, use the syntax "alias|otheralias".
+
034 * You can register as many aliases as wanted in a single value.
+
035 *
+
036 * Used on a Class, defines the root command for all subcommands in the base command.
+
037 * Used on a method, defines a root command alias to that specific command
+
038 */
+
039@Retention(RetentionPolicy.RUNTIME)
+
040@Target({ElementType.METHOD, ElementType.TYPE})
+
041public @interface CommandAlias {
+
042 String value();
+
043}
diff --git a/docs/acf-core/src-html/co/aikar/commands/annotation/CommandCompletion.html b/docs/acf-core/src-html/co/aikar/commands/annotation/CommandCompletion.html
index 7b39ea13..9c5e08b2 100644
--- a/docs/acf-core/src-html/co/aikar/commands/annotation/CommandCompletion.html
+++ b/docs/acf-core/src-html/co/aikar/commands/annotation/CommandCompletion.html
@@ -36,11 +36,21 @@
028import java.lang.annotation.RetentionPolicy;
029import java.lang.annotation.Target;
030
-
031@Retention(RetentionPolicy.RUNTIME)
-
032@Target({ElementType.METHOD})
-
033public @interface CommandCompletion {
-
034 String value();
-
035}
+
031/**
+
032 * Many implementation platforms have a concept of "Tab Completions",
+
033 * where pressing tab will give suggestions on what you can input.
+
034 *
+
035 * This annotation specifies either static completion values,
+
036 * or special @codes that let you define Completion Handlers to dynamically
+
037 * populate completion values.
+
038 *
+
039 * @see {@link co.aikar.commands.CommandCompletions}
+
040 */
+
041@Retention(RetentionPolicy.RUNTIME)
+
042@Target({ElementType.METHOD})
+
043public @interface CommandCompletion {
+
044 String value();
+
045}
diff --git a/docs/acf-core/src-html/co/aikar/commands/annotation/CommandPermission.html b/docs/acf-core/src-html/co/aikar/commands/annotation/CommandPermission.html
index c469d517..ed90fee8 100644
--- a/docs/acf-core/src-html/co/aikar/commands/annotation/CommandPermission.html
+++ b/docs/acf-core/src-html/co/aikar/commands/annotation/CommandPermission.html
@@ -36,11 +36,16 @@
028import java.lang.annotation.RetentionPolicy;
029import java.lang.annotation.Target;
030
-
031@Retention(RetentionPolicy.RUNTIME)
-
032@Target({ElementType.METHOD, ElementType.TYPE})
-
033public @interface CommandPermission {
-
034 String value();
-
035}
+
031/**
+
032 * Sets the permission required to perform this command.
+
033 *
+
034 * Permission format will vary based on implementation platform
+
035 */
+
036@Retention(RetentionPolicy.RUNTIME)
+
037@Target({ElementType.METHOD, ElementType.TYPE})
+
038public @interface CommandPermission {
+
039 String value();
+
040}
diff --git a/docs/acf-core/src-html/co/aikar/commands/annotation/Conditions.html b/docs/acf-core/src-html/co/aikar/commands/annotation/Conditions.html
index 27346d60..908b1ec5 100644
--- a/docs/acf-core/src-html/co/aikar/commands/annotation/Conditions.html
+++ b/docs/acf-core/src-html/co/aikar/commands/annotation/Conditions.html
@@ -36,11 +36,19 @@
028import java.lang.annotation.RetentionPolicy;
029import java.lang.annotation.Target;
030
-
031@Retention(RetentionPolicy.RUNTIME)
-
032@Target({ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE})
-
033public @interface Conditions {
-
034 String value();
-
035}
+
031/**
+
032 * Specifies conditions that must be met in order to execute this command.
+
033 *
+
034 * If used on a method or a class, will be checked before parameter context is resolved
+
035 * If used on a parameter, will be checked after the context is resolved
+
036 *
+
037 * @see {@link co.aikar.commands.CommandConditions}
+
038 */
+
039@Retention(RetentionPolicy.RUNTIME)
+
040@Target({ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE})
+
041public @interface Conditions {
+
042 String value();
+
043}
diff --git a/docs/acf-core/src-html/co/aikar/commands/annotation/Default.html b/docs/acf-core/src-html/co/aikar/commands/annotation/Default.html
index 59147b77..841880bf 100644
--- a/docs/acf-core/src-html/co/aikar/commands/annotation/Default.html
+++ b/docs/acf-core/src-html/co/aikar/commands/annotation/Default.html
@@ -36,11 +36,15 @@
028import java.lang.annotation.RetentionPolicy;
029import java.lang.annotation.Target;
030
-
031@Retention(RetentionPolicy.RUNTIME)
-
032@Target({ElementType.METHOD, ElementType.PARAMETER})
-
033public @interface Default {
-
034 String value() default "";
-
035}
+
031/**
+
032 * If used on a method, sets default command handler for the root command of this group
+
033 * If used on a parameter, sets the value to be used for context resolution
+
034 */
+
035@Retention(RetentionPolicy.RUNTIME)
+
036@Target({ElementType.METHOD, ElementType.PARAMETER})
+
037public @interface Default {
+
038 String value() default "";
+
039}
diff --git a/docs/acf-core/src-html/co/aikar/commands/annotation/Dependency.html b/docs/acf-core/src-html/co/aikar/commands/annotation/Dependency.html
index d8d31014..894d5472 100644
--- a/docs/acf-core/src-html/co/aikar/commands/annotation/Dependency.html
+++ b/docs/acf-core/src-html/co/aikar/commands/annotation/Dependency.html
@@ -36,15 +36,19 @@
028import java.lang.annotation.RetentionPolicy;
029import java.lang.annotation.Target;
030
-
031@Retention(RetentionPolicy.RUNTIME)
-
032@Target(ElementType.FIELD)
-
033public @interface Dependency {
-
034 /**
-
035 * The key that should be used to lookup the instances, defaults to \"\"
-
036 * @return the key
-
037 */
-
038 String value() default "";
-
039}
+
031/**
+
032 * Injects a dependency into the field this is attached to.
+
033 * Any time a new dependency is registered, this will be overwritten.
+
034 */
+
035@Retention(RetentionPolicy.RUNTIME)
+
036@Target(ElementType.FIELD)
+
037public @interface Dependency {
+
038 /**
+
039 * The key that should be used to lookup the instances, defaults to \"\"
+
040 * @return the key
+
041 */
+
042 String value() default "";
+
043}
diff --git a/docs/acf-core/src-html/co/aikar/commands/annotation/Description.html b/docs/acf-core/src-html/co/aikar/commands/annotation/Description.html
index 6fe8b19a..c4eca736 100644
--- a/docs/acf-core/src-html/co/aikar/commands/annotation/Description.html
+++ b/docs/acf-core/src-html/co/aikar/commands/annotation/Description.html
@@ -36,11 +36,15 @@
028import java.lang.annotation.RetentionPolicy;
029import java.lang.annotation.Target;
030
-
031@Retention(RetentionPolicy.RUNTIME)
-
032@Target({ElementType.METHOD, ElementType.PARAMETER})
-
033public @interface Description {
-
034 String value();
-
035}
+
031/**
+
032 * Sets a description to the parameter or method this is attached to.
+
033 * This is used in the help menus.
+
034 */
+
035@Retention(RetentionPolicy.RUNTIME)
+
036@Target({ElementType.METHOD, ElementType.PARAMETER})
+
037public @interface Description {
+
038 String value();
+
039}
diff --git a/docs/acf-core/src-html/co/aikar/commands/annotation/Flags.html b/docs/acf-core/src-html/co/aikar/commands/annotation/Flags.html
index e6fa2f0e..2305636e 100644
--- a/docs/acf-core/src-html/co/aikar/commands/annotation/Flags.html
+++ b/docs/acf-core/src-html/co/aikar/commands/annotation/Flags.html
@@ -36,11 +36,18 @@
028import java.lang.annotation.RetentionPolicy;
029import java.lang.annotation.Target;
030
-
031@Retention(RetentionPolicy.RUNTIME)
-
032@Target({ElementType.PARAMETER})
-
033public @interface Flags {
-
034 String value();
-
035}
+
031/**
+
032 * Provides configuration options for {@link co.aikar.commands.contexts.ContextResolver}'s to change how they resolve context.
+
033 *
+
034 * Example: Searching for a player, you might use @Flags("loose") to indicate a fuzzy match instead of an exact match.
+
035 *
+
036 * If you want to restrict if an issuer can use the command, please use {@link co.aikar.commands.CommandConditions.Condition} instead.
+
037 */
+
038@Retention(RetentionPolicy.RUNTIME)
+
039@Target({ElementType.PARAMETER})
+
040public @interface Flags {
+
041 String value();
+
042}
diff --git a/docs/acf-core/src-html/co/aikar/commands/annotation/HelpCommand.html b/docs/acf-core/src-html/co/aikar/commands/annotation/HelpCommand.html
index 6b2fb6e7..f76654b1 100644
--- a/docs/acf-core/src-html/co/aikar/commands/annotation/HelpCommand.html
+++ b/docs/acf-core/src-html/co/aikar/commands/annotation/HelpCommand.html
@@ -36,11 +36,21 @@
028import java.lang.annotation.RetentionPolicy;
029import java.lang.annotation.Target;
030
-
031@Retention(RetentionPolicy.RUNTIME)
-
032@Target({ElementType.METHOD})
-
033public @interface HelpCommand {
-
034 String value() default "help|?|-help|-h|-?";
-
035}
+
031/**
+
032 * A Shortcut for specifying {@link CatchUnknown}, {@link Default} and {@link Subcommand} on a method.
+
033 * Subcommand carries the same value as this annotations value to define the list of subcommands to register for.
+
034 *
+
035 * a method marked with this annotation should also use a {@link co.aikar.commands.CommandHelp} context parameter to show help.
+
036 */
+
037@Retention(RetentionPolicy.RUNTIME)
+
038@Target({ElementType.METHOD})
+
039public @interface HelpCommand {
+
040 /**
+
041 * The value to forward to the @Subcommand annotation. Lists which subcommands to register to trigger help
+
042 * @return
+
043 */
+
044 String value() default "help|?|-help|-h|-?";
+
045}
diff --git a/docs/acf-core/src-html/co/aikar/commands/annotation/HelpSearchTags.html b/docs/acf-core/src-html/co/aikar/commands/annotation/HelpSearchTags.html
index 3efeabab..e9042db1 100644
--- a/docs/acf-core/src-html/co/aikar/commands/annotation/HelpSearchTags.html
+++ b/docs/acf-core/src-html/co/aikar/commands/annotation/HelpSearchTags.html
@@ -36,11 +36,17 @@
028import java.lang.annotation.RetentionPolicy;
029import java.lang.annotation.Target;
030
-
031@Retention(RetentionPolicy.RUNTIME)
-
032@Target({ElementType.METHOD})
-
033public @interface HelpSearchTags {
-
034 String value();
-
035}
+
031/**
+
032 * Defines additional keywords to feed into the search help system.
+
033 *
+
034 * For example, if a specific word doesn't make sense to use in the command name or description, but should
+
035 * be used for help in discovering the correct command, then you can add it as a tag.
+
036 */
+
037@Retention(RetentionPolicy.RUNTIME)
+
038@Target({ElementType.METHOD})
+
039public @interface HelpSearchTags {
+
040 String value();
+
041}
diff --git a/docs/acf-core/src-html/co/aikar/commands/annotation/Optional.html b/docs/acf-core/src-html/co/aikar/commands/annotation/Optional.html
index c325d2dc..5ffd4f65 100644
--- a/docs/acf-core/src-html/co/aikar/commands/annotation/Optional.html
+++ b/docs/acf-core/src-html/co/aikar/commands/annotation/Optional.html
@@ -36,10 +36,17 @@
028import java.lang.annotation.RetentionPolicy;
029import java.lang.annotation.Target;
030
-
031@Retention(RetentionPolicy.RUNTIME)
-
032@Target({ElementType.PARAMETER})
-
033public @interface Optional {
-
034}
+
031/**
+
032 * Marks the parameter this is attached to as optional.
+
033 * This will set the parameter as null if it was not provided.
+
034 * <p>
+
035 * In the case the language used is Kotlin, Ceylon or any other null-enforcing JVM language,
+
036 * you will need to allow for a nullable value.
+
037 */
+
038@Retention(RetentionPolicy.RUNTIME)
+
039@Target({ElementType.PARAMETER})
+
040public @interface Optional {
+
041}
diff --git a/docs/acf-core/src-html/co/aikar/commands/annotation/PreCommand.html b/docs/acf-core/src-html/co/aikar/commands/annotation/PreCommand.html
index ba9ab372..a5149019 100644
--- a/docs/acf-core/src-html/co/aikar/commands/annotation/PreCommand.html
+++ b/docs/acf-core/src-html/co/aikar/commands/annotation/PreCommand.html
@@ -37,9 +37,12 @@
029import java.lang.annotation.RetentionPolicy;
030import java.lang.annotation.Target;
031
-
032@Retention(RetentionPolicy.RUNTIME)
-
033@Target({ElementType.METHOD})
-
034public @interface PreCommand {}
+
032/**
+
033 * This runs before any other command method each time it is invoked.
+
034 */
+
035@Retention(RetentionPolicy.RUNTIME)
+
036@Target({ElementType.METHOD})
+
037public @interface PreCommand {}
diff --git a/docs/acf-core/src-html/co/aikar/commands/annotation/Single.html b/docs/acf-core/src-html/co/aikar/commands/annotation/Single.html
index f60fd266..151ab4d6 100644
--- a/docs/acf-core/src-html/co/aikar/commands/annotation/Single.html
+++ b/docs/acf-core/src-html/co/aikar/commands/annotation/Single.html
@@ -37,12 +37,11 @@
029import java.lang.annotation.Target;
030
031/**
-
032 * Don't join remaining arguments
+
032 * Don't join remaining arguments. Used on String parameters, which normally would combine the remaining arguments
033 */
-
034
-
035@Retention(RetentionPolicy.RUNTIME)
-
036@Target({ElementType.PARAMETER})
-
037public @interface Single {}
+
034@Retention(RetentionPolicy.RUNTIME)
+
035@Target({ElementType.PARAMETER})
+
036public @interface Single {}
diff --git a/docs/acf-core/src-html/co/aikar/commands/annotation/Split.html b/docs/acf-core/src-html/co/aikar/commands/annotation/Split.html
index f707e7d7..97254097 100644
--- a/docs/acf-core/src-html/co/aikar/commands/annotation/Split.html
+++ b/docs/acf-core/src-html/co/aikar/commands/annotation/Split.html
@@ -37,11 +37,15 @@
029import java.lang.annotation.RetentionPolicy;
030import java.lang.annotation.Target;
031
-
032@Retention(RetentionPolicy.RUNTIME)
-
033@Target({ElementType.PARAMETER})
-
034public @interface Split {
-
035 String value() default ",";
-
036}
+
032/**
+
033 * Joins arguments into a single piece of text with the specified separator.
+
034 * For array based parameters, defines the regex pattern to split on
+
035 */
+
036@Retention(RetentionPolicy.RUNTIME)
+
037@Target({ElementType.PARAMETER})
+
038public @interface Split {
+
039 String value() default ",";
+
040}
diff --git a/docs/acf-core/src-html/co/aikar/commands/annotation/Subcommand.html b/docs/acf-core/src-html/co/aikar/commands/annotation/Subcommand.html
index 0f77bea4..1a1273e8 100644
--- a/docs/acf-core/src-html/co/aikar/commands/annotation/Subcommand.html
+++ b/docs/acf-core/src-html/co/aikar/commands/annotation/Subcommand.html
@@ -36,11 +36,18 @@
028import java.lang.annotation.RetentionPolicy;
029import java.lang.annotation.Target;
030
-
031@Retention(RetentionPolicy.RUNTIME)
-
032@Target({ElementType.METHOD, ElementType.TYPE})
-
033public @interface Subcommand {
-
034 String value();
-
035}
+
031/**
+
032 * Defines the subcommand that can be used to execute this command.
+
033 * This is appended onto the root command for the command group,
+
034 * as well as any parent command groups subcommand base.
+
035 *
+
036 * Defines the part after root command like so: "/rootcommand {@link #value()}".
+
037 */
+
038@Retention(RetentionPolicy.RUNTIME)
+
039@Target({ElementType.METHOD, ElementType.TYPE})
+
040public @interface Subcommand {
+
041 String value();
+
042}
diff --git a/docs/acf-core/src-html/co/aikar/commands/annotation/Syntax.html b/docs/acf-core/src-html/co/aikar/commands/annotation/Syntax.html
index d620bca9..610a6fc5 100644
--- a/docs/acf-core/src-html/co/aikar/commands/annotation/Syntax.html
+++ b/docs/acf-core/src-html/co/aikar/commands/annotation/Syntax.html
@@ -36,12 +36,21 @@
028import java.lang.annotation.RetentionPolicy;
029import java.lang.annotation.Target;
030
-
031@Retention(RetentionPolicy.RUNTIME)
-
032@Target({ElementType.METHOD, ElementType.PARAMETER})
-
033public @interface Syntax {
-
034 String value();
-
035}
-
036
+
031/**
+
032 * Specifies the syntax to be used when executing this command.
+
033 * It should not include any descriptions of the arguments nor when some are allowed and when they are not.
+
034 *
+
035 * Use of this annotation is not necessary. Syntax will be automatically generated for you.
+
036 * Use this annotation to override automatic syntax
+
037 *
+
038 * Use {@link Description} together with the help menu for that purpose.
+
039 **/
+
040@Retention(RetentionPolicy.RUNTIME)
+
041@Target({ElementType.METHOD, ElementType.PARAMETER})
+
042public @interface Syntax {
+
043 String value();
+
044}
+
045
diff --git a/docs/acf-core/src-html/co/aikar/commands/annotation/Values.html b/docs/acf-core/src-html/co/aikar/commands/annotation/Values.html
index 60355e8b..7212c35f 100644
--- a/docs/acf-core/src-html/co/aikar/commands/annotation/Values.html
+++ b/docs/acf-core/src-html/co/aikar/commands/annotation/Values.html
@@ -36,11 +36,16 @@
028import java.lang.annotation.RetentionPolicy;
029import java.lang.annotation.Target;
030
-
031@Retention(RetentionPolicy.RUNTIME)
-
032@Target({ElementType.PARAMETER})
-
033public @interface Values {
-
034 String value();
-
035}
+
031/**
+
032 * Specifies a list of values that the command input should be validated against, or else show an error.
+
033 *
+
034 * You may also use {@link CommandCompletion} handler codes here to feed dynamic values and avoid repetition.
+
035 */
+
036@Retention(RetentionPolicy.RUNTIME)
+
037@Target({ElementType.PARAMETER})
+
038public @interface Values {
+
039 String value();
+
040}
diff --git a/docs/acf-core/src-html/co/aikar/commands/contexts/ContextResolver.html b/docs/acf-core/src-html/co/aikar/commands/contexts/ContextResolver.html
index ebc06467..9406fb91 100644
--- a/docs/acf-core/src-html/co/aikar/commands/contexts/ContextResolver.html
+++ b/docs/acf-core/src-html/co/aikar/commands/contexts/ContextResolver.html
@@ -35,10 +35,29 @@
027import co.aikar.commands.CommandIssuer;
028import co.aikar.commands.InvalidCommandArgument;
029
-
030@FunctionalInterface
-
031public interface ContextResolver <T, C extends CommandExecutionContext<?, ? extends CommandIssuer>> {
-
032 T getContext(C c) throws InvalidCommandArgument;
-
033}
+
030/**
+
031 * This defines a context resolver, which parses {@link T} from {@link C}.
+
032 *
+
033 * @param <T>
+
034 * The type to be parsed.
+
035 * @param <C>
+
036 * The type of the context which the resolver would get its data from.
+
037 */
+
038@FunctionalInterface
+
039public interface ContextResolver <T, C extends CommandExecutionContext<?, ? extends CommandIssuer>> {
+
040 /**
+
041 * Parses the context of type {@link C} into {@link T}, or throws an exception.
+
042 *
+
043 * @param c
+
044 * The context to parse from.
+
045 *
+
046 * @return The parsed instance of the wanted type.
+
047 *
+
048 * @throws InvalidCommandArgument
+
049 * In case the context contains any discrepancies, it will throw this exception.
+
050 */
+
051 T getContext(C c) throws InvalidCommandArgument;
+
052}
diff --git a/docs/acf-core/src-html/co/aikar/commands/contexts/OptionalContextResolver.html b/docs/acf-core/src-html/co/aikar/commands/contexts/OptionalContextResolver.html
index 0acdce4f..d8f52465 100644
--- a/docs/acf-core/src-html/co/aikar/commands/contexts/OptionalContextResolver.html
+++ b/docs/acf-core/src-html/co/aikar/commands/contexts/OptionalContextResolver.html
@@ -35,9 +35,14 @@
027import co.aikar.commands.CommandIssuer;
028
029/**
-
030 * Context Resolver that can accept null input
-
031 */
-
032public interface OptionalContextResolver <T, C extends CommandExecutionContext<?, ? extends CommandIssuer>> extends ContextResolver <T, C> {}
+
030 * The same as {@link ContextResolver}, however it can accept a null context.
+
031 *
+
032 * If the parameter was marked optional, will still be called with an empty args list
+
033 *
+
034 * @param <T>
+
035 * @param <C>
+
036 */
+
037public interface OptionalContextResolver <T, C extends CommandExecutionContext<?, ? extends CommandIssuer>> extends ContextResolver <T, C> {}
diff --git a/docs/acf-jda/co/aikar/commands/JDACommandCompletions.html b/docs/acf-jda/co/aikar/commands/JDACommandCompletions.html
index e53df14b..5099a0b6 100644
--- a/docs/acf-jda/co/aikar/commands/JDACommandCompletions.html
+++ b/docs/acf-jda/co/aikar/commands/JDACommandCompletions.html
@@ -177,6 +177,13 @@ extends co.aikar.commands.CommandCompletions<co.aikar.commands.CommandComplet