diff --git a/docs/acf-bukkit/co/aikar/commands/BukkitRootCommand.html b/docs/acf-bukkit/co/aikar/commands/BukkitRootCommand.html index af1fe6ec..f504a341 100644 --- a/docs/acf-bukkit/co/aikar/commands/BukkitRootCommand.html +++ b/docs/acf-bukkit/co/aikar/commands/BukkitRootCommand.html @@ -18,7 +18,7 @@ catch(err) { } //--> -var methods = {"i0":10,"i1":10,"i2":10,"i3":10,"i4":10,"i5":10,"i6":10,"i7":10,"i8":10}; +var methods = {"i0":10,"i1":10,"i2":10,"i3":10,"i4":10,"i5":10,"i6":10,"i7":10,"i8":10,"i9":10}; var tabs = {65535:["t0","All Methods"],2:["t2","Instance Methods"],8:["t4","Concrete Methods"]}; var altColor = "altColor"; var rowColor = "rowColor"; @@ -176,20 +176,24 @@ implements co.aikar.commands.RootCommand getDefCommand()  +String +getDescription()  + + co.aikar.commands.CommandManager getManager()  - + com.google.common.collect.SetMultimap<String,co.aikar.commands.RegisteredCommand> getSubCommands()  - + List<String> tabComplete(org.bukkit.command.CommandSender sender, String commandLabel, String[] args)  - + boolean testPermissionSilent(org.bukkit.command.CommandSender target)  @@ -199,7 +203,7 @@ implements co.aikar.commands.RootCommand

Methods inherited from class org.bukkit.command.Command

-broadcastCommandMessage, broadcastCommandMessage, getAliases, getDescription, getLabel, getName, getPermission, getPermissionMessage, getUsage, isRegistered, register, setAliases, setDescription, setLabel, setName, setPermission, setPermissionMessage, setUsage, tabComplete, testPermission, toString, unregister +broadcastCommandMessage, broadcastCommandMessage, getAliases, getLabel, getName, getPermission, getPermissionMessage, getUsage, isRegistered, register, setAliases, setDescription, setLabel, setName, setPermission, setPermissionMessage, setUsage, tabComplete, testPermission, toString, unregister @@ -229,13 +233,28 @@ implements co.aikar.commands.RootCommand

Method Detail

+ + + + @@ -604,7 +604,7 @@ public void 
  • hasPermission

    -
    public boolean hasPermission(CommandIssuer issuer)
    +
    public boolean hasPermission(CommandIssuer issuer)
  • @@ -613,7 +613,7 @@ public void 
  • getRequiredPermissions

    -
    public Set<StringgetRequiredPermissions()
    +
    public Set<StringgetRequiredPermissions()
  • @@ -622,7 +622,7 @@ public void 
  • requiresPermission

    -
    public boolean requiresPermission(String permission)
    +
    public boolean requiresPermission(String permission)
  • @@ -631,7 +631,7 @@ public void 
  • getName

    -
    public String getName()
    +
    public String getName()
  • @@ -640,7 +640,7 @@ public void 
  • getExceptionHandler

    -
    public ExceptionHandler getExceptionHandler()
    +
    public ExceptionHandler getExceptionHandler()
  • @@ -649,7 +649,7 @@ public void 
  • setExceptionHandler

    -
    public BaseCommand setExceptionHandler(ExceptionHandler exceptionHandler)
    +
    public BaseCommand setExceptionHandler(ExceptionHandler exceptionHandler)
  • @@ -658,7 +658,7 @@ public void 
  • getDefaultRegisteredCommand

    -
    public RegisteredCommand getDefaultRegisteredCommand()
    +
    public RegisteredCommand getDefaultRegisteredCommand()
  • @@ -667,7 +667,7 @@ public void 
  • setContextFlags

    -
    public String setContextFlags(Class<?> cls,
    +
    public String setContextFlags(Class<?> cls,
                                   String flags)
  • @@ -677,7 +677,7 @@ public void 
  • getContextFlags

    -
    public String getContextFlags(Class<?> cls)
    +
    public String getContextFlags(Class<?> cls)
  • @@ -686,7 +686,7 @@ public void 
  • getRegisteredCommands

    -
    public List<RegisteredCommandgetRegisteredCommands()
    +
    public List<RegisteredCommandgetRegisteredCommands()
  • diff --git a/docs/acf-core/co/aikar/commands/annotation/Description.html b/docs/acf-core/co/aikar/commands/annotation/Description.html index 91db5980..516e21a1 100644 --- a/docs/acf-core/co/aikar/commands/annotation/Description.html +++ b/docs/acf-core/co/aikar/commands/annotation/Description.html @@ -93,7 +93,7 @@

    @Retention(value=RUNTIME)
    - @Target(value={METHOD,PARAMETER})
    + @Target(value={METHOD,PARAMETER,TYPE})
     public @interface Description
    Sets a description to the parameter or method this is attached to. This is used in the help menus.
    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 b3fe8a64..7d8c50d8 100644 --- a/docs/acf-core/src-html/co/aikar/commands/BaseCommand.html +++ b/docs/acf-core/src-html/co/aikar/commands/BaseCommand.html @@ -38,806 +38,807 @@ 030import co.aikar.commands.annotation.CommandPermission; 031import co.aikar.commands.annotation.Conditions; 032import co.aikar.commands.annotation.Default; -033import co.aikar.commands.annotation.HelpCommand; -034import co.aikar.commands.annotation.PreCommand; -035import co.aikar.commands.annotation.Subcommand; -036import co.aikar.commands.annotation.UnknownHandler; -037import co.aikar.commands.apachecommonslang.ApacheCommonsLangUtil; -038import com.google.common.collect.HashMultimap; -039import com.google.common.collect.SetMultimap; -040import org.jetbrains.annotations.Nullable; -041 -042import java.lang.reflect.Constructor; -043import java.lang.reflect.InvocationTargetException; -044import java.lang.reflect.Method; -045import java.lang.reflect.Parameter; -046import java.util.ArrayList; -047import java.util.Arrays; -048import java.util.Collections; -049import java.util.HashMap; -050import java.util.HashSet; -051import java.util.LinkedHashSet; -052import java.util.List; -053import java.util.Map; -054import java.util.Objects; -055import java.util.Set; -056import java.util.Stack; -057import java.util.stream.Collectors; -058import java.util.stream.Stream; -059 -060/** -061 * A Base command is defined as a command group of related commands. -062 * A BaseCommand does not imply nor enforce that they use the same root command. -063 * <p> -064 * It is up to the end user how to organize their command. you could use 1 base command per -065 * command in your application. -066 * <p> -067 * Optionally (and encouraged), you can use the base command to represent a root command, and -068 * then each actionable command is a sub command -069 */ -070 -071public abstract class BaseCommand { -072 -073 /** -074 * This is a field which contains the magic key in the {@link #subCommands} map for the method to catch any unknown -075 * argument to command states. -076 */ -077 static final String CATCHUNKNOWN = "__catchunknown"; -078 /** -079 * This is a field which contains the magic key in the {@link #subCommands} map for the method which is default for the -080 * entire base command. -081 */ -082 static final String DEFAULT = "__default"; -083 -084 /** -085 * A map of all the registered commands for this base command, keyed to each potential subcommand to access it. -086 */ -087 final SetMultimap<String, RegisteredCommand> subCommands = HashMultimap.create(); -088 -089 /** -090 * A map of flags to pass to Context Resolution for every parameter of the type. This is like an automatic @Flags on each. -091 */ -092 final Map<Class<?>, String> contextFlags = new HashMap<>(); -093 -094 /** -095 * What method was annoated with {@link PreCommand} to execute before commands. -096 */ -097 @Nullable -098 private Method preCommandHandler; -099 -100 /** -101 * What root command the user actually entered to access the currently executing command -102 */ -103 @SuppressWarnings("WeakerAccess") -104 private String execLabel; -105 /** -106 * What subcommand the user actually entered to access the currently executing command -107 */ -108 @SuppressWarnings("WeakerAccess") -109 private String execSubcommand; -110 /** -111 * What arguments the user actually entered after the root command to access the currently executing command -112 */ -113 @SuppressWarnings("WeakerAccess") -114 private String[] origArgs; -115 -116 /** -117 * The manager this is registered to -118 */ -119 CommandManager<?, ?, ?, ?, ?, ?> manager = null; -120 -121 /** -122 * The command which owns this. This may be null if there are no owners. -123 */ -124 BaseCommand parentCommand; -125 Map<String, RootCommand> registeredCommands = new HashMap<>(); -126 /** -127 * The description of the command. This may be null if no description has been provided. -128 * Used for help documentation -129 */ -130 @Nullable String description; -131 /** -132 * The name of the command. This may be null if no name has been provided. -133 */ -134 @Nullable String commandName; -135 /** -136 * The permission of the command. This may be null if no permission has been provided. -137 */ -138 @Nullable String permission; -139 /** -140 * The conditions of the command. This may be null if no conditions has been provided. -141 */ -142 @Nullable String conditions; -143 /** -144 * Identifies if the command has an explicit help command annotated with {@link HelpCommand} -145 */ -146 boolean hasHelpCommand; -147 -148 /** -149 * The handler of all uncaught exceptions thrown by the user's command implementation. -150 */ -151 private ExceptionHandler exceptionHandler = null; -152 /** -153 * The last operative context data of this command. This may be null if this command hasn't been run yet. -154 */ -155 private final ThreadLocal<CommandOperationContext> lastCommandOperationContext = new ThreadLocal<>(); -156 /** -157 * If a parent exists to this command, and it has a Subcommand annotation, prefix all subcommands in this class with this -158 */ -159 @Nullable -160 private String parentSubcommand; -161 -162 /** -163 * The permissions of the command. -164 */ -165 private final Set<String> permissions = new HashSet<>(); -166 -167 public BaseCommand() { -168 } -169 -170 /** -171 * Constructor based defining of commands will be removed in the next version bump. -172 * -173 * @param cmd -174 * @deprecated Please switch to {@link CommandAlias} for defining all root commands. -175 */ -176 @Deprecated -177 public BaseCommand(@Nullable String cmd) { -178 this.commandName = cmd; -179 } -180 -181 /** -182 * Returns a reference to the last used CommandOperationContext. -183 * This method is ThreadLocal, in that it can only be used on a thread that has executed a command -184 * -185 * @return -186 */ -187 public CommandOperationContext getLastCommandOperationContext() { -188 return lastCommandOperationContext.get(); -189 } -190 -191 /** -192 * Gets the root command name that the user actually typed -193 * -194 * @return Name -195 */ -196 public String getExecCommandLabel() { -197 return execLabel; -198 } -199 -200 /** -201 * Gets the actual sub command name the user typed -202 * -203 * @return Name -204 */ -205 public String getExecSubcommand() { -206 return execSubcommand; -207 } -208 -209 /** -210 * Gets the actual args in string form the user typed -211 * -212 * @return Args -213 */ -214 public String[] getOrigArgs() { -215 return origArgs; -216 } -217 -218 /** -219 * This should be called whenever the command gets registered. -220 * It sets all required fields correctly and injects dependencies. -221 * -222 * @param manager The manager to register as this command's owner and handler. -223 */ -224 void onRegister(CommandManager manager) { -225 onRegister(manager, this.commandName); -226 } -227 -228 /** -229 * This should be called whenever the command gets registered. -230 * It sets all required fields correctly and injects dependencies. -231 * -232 * @param manager The manager to register as this command's owner and handler. -233 * @param cmd The command name to use register with. -234 */ -235 private void onRegister(CommandManager manager, String cmd) { -236 manager.injectDependencies(this); -237 this.manager = manager; -238 -239 final Annotations annotations = manager.getAnnotations(); -240 final Class<? extends BaseCommand> self = this.getClass(); -241 -242 String[] cmdAliases = annotations.getAnnotationValues(self, CommandAlias.class, Annotations.REPLACEMENTS | Annotations.LOWERCASE | Annotations.NO_EMPTY); -243 -244 if (cmd == null && cmdAliases != null) { -245 cmd = cmdAliases[0]; -246 } -247 -248 this.commandName = cmd != null ? cmd : self.getSimpleName().toLowerCase(); -249 this.permission = annotations.getAnnotationValue(self, CommandPermission.class, Annotations.REPLACEMENTS); -250 this.description = this.commandName + " commands"; -251 this.parentSubcommand = getParentSubcommand(self); -252 this.conditions = annotations.getAnnotationValue(self, Conditions.class, Annotations.REPLACEMENTS | Annotations.NO_EMPTY); -253 -254 registerSubcommands(); -255 computePermissions(); -256 registerSubclasses(cmd); -257 -258 if (cmdAliases != null) { -259 Set<String> cmdList = new HashSet<>(); -260 Collections.addAll(cmdList, cmdAliases); -261 cmdList.remove(cmd); -262 for (String cmdAlias : cmdList) { -263 register(cmdAlias, this); -264 } -265 } -266 -267 if (cmd != null) { -268 register(cmd, this); -269 } -270 } -271 -272 /** -273 * This recursively registers all subclasses of the command as subcommands, if they are of type {@link BaseCommand}. -274 * -275 * @param cmd The command name of this command. -276 */ -277 private void registerSubclasses(String cmd) { -278 for (Class<?> clazz : this.getClass().getDeclaredClasses()) { -279 if (BaseCommand.class.isAssignableFrom(clazz)) { -280 try { -281 BaseCommand subCommand = null; -282 Constructor<?>[] declaredConstructors = clazz.getDeclaredConstructors(); -283 for (Constructor<?> declaredConstructor : declaredConstructors) { -284 -285 declaredConstructor.setAccessible(true); -286 Parameter[] parameters = declaredConstructor.getParameters(); -287 if (parameters.length == 1) { -288 subCommand = (BaseCommand) declaredConstructor.newInstance(this); -289 } else { -290 manager.log(LogLevel.INFO, "Found unusable constructor: " + declaredConstructor.getName() + "(" + Stream.of(parameters).map(p -> p.getType().getSimpleName() + " " + p.getName()).collect(Collectors.joining("<c2>,</c2> ")) + ")"); -291 } -292 } -293 if (subCommand != null) { -294 subCommand.parentCommand = this; -295 subCommand.onRegister(manager, cmd); -296 this.subCommands.putAll(subCommand.subCommands); -297 this.registeredCommands.putAll(subCommand.registeredCommands); -298 } else { -299 this.manager.log(LogLevel.ERROR, "Could not find a subcommand ctor for " + clazz.getName()); -300 } -301 } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { -302 this.manager.log(LogLevel.ERROR, "Error registering subclass", e); -303 } -304 } -305 } -306 } -307 -308 /** -309 * This registers all subcommands of the command. -310 */ -311 private void registerSubcommands() { -312 final Annotations annotations = manager.getAnnotations(); -313 boolean foundCatchUnknown = false; -314 boolean isParentEmpty = parentSubcommand == null || parentSubcommand.isEmpty(); -315 Set<Method> methods = new LinkedHashSet<>(); -316 Collections.addAll(methods, this.getClass().getDeclaredMethods()); -317 Collections.addAll(methods, this.getClass().getMethods()); -318 -319 for (Method method : methods) { -320 method.setAccessible(true); -321 String sublist = null; -322 String sub = getSubcommandValue(method); -323 final String helpCommand = annotations.getAnnotationValue(method, HelpCommand.class, Annotations.NOTHING); -324 final String commandAliases = annotations.getAnnotationValue(method, CommandAlias.class, Annotations.NOTHING); -325 -326 if (annotations.hasAnnotation(method, Default.class)) { -327 if (!isParentEmpty) { -328 sub = parentSubcommand; -329 } else { -330 registerSubcommand(method, DEFAULT); -331 } -332 } -333 -334 if (sub != null) { -335 sublist = sub; -336 } else if (commandAliases != null) { -337 sublist = commandAliases; -338 } else if (helpCommand != null) { -339 sublist = helpCommand; -340 hasHelpCommand = true; -341 } -342 -343 boolean preCommand = annotations.hasAnnotation(method, PreCommand.class); -344 boolean hasCatchUnknown = annotations.hasAnnotation(method, CatchUnknown.class) || -345 annotations.hasAnnotation(method, CatchAll.class) || -346 annotations.hasAnnotation(method, UnknownHandler.class); -347 -348 if (hasCatchUnknown || (!foundCatchUnknown && helpCommand != null)) { -349 if (!foundCatchUnknown) { -350 if (hasCatchUnknown) { -351 this.subCommands.get(CATCHUNKNOWN).clear(); -352 foundCatchUnknown = true; -353 } -354 registerSubcommand(method, CATCHUNKNOWN); -355 } else { -356 ACFUtil.sneaky(new IllegalStateException("Multiple @CatchUnknown/@HelpCommand commands, duplicate on " + method.getDeclaringClass().getName() + "#" + method.getName())); -357 } -358 } else if (preCommand) { -359 if (this.preCommandHandler == null) { -360 this.preCommandHandler = method; -361 } else { -362 ACFUtil.sneaky(new IllegalStateException("Multiple @PreCommand commands, duplicate on " + method.getDeclaringClass().getName() + "#" + method.getName())); -363 } -364 } -365 if (Objects.equals(method.getDeclaringClass(), this.getClass()) && sublist != null) { -366 registerSubcommand(method, sublist); -367 } -368 } -369 } -370 -371 /** -372 * This registers all the permissions required to execute this command. -373 */ -374 private void computePermissions() { -375 this.permissions.clear(); -376 if (this.permission != null && !this.permission.isEmpty()) { -377 this.permissions.addAll(Arrays.asList(ACFPatterns.COMMA.split(this.permission))); -378 } -379 if (this.parentCommand != null) { -380 this.permissions.addAll(this.parentCommand.getRequiredPermissions()); -381 } -382 } -383 -384 /** -385 * Gets the subcommand name of the method given. -386 * -387 * @param method The method to check. -388 * @return The name of the subcommand. It returns null if the input doesn't have {@link Subcommand} attached. -389 */ -390 private String getSubcommandValue(Method method) { -391 final String sub = manager.getAnnotations().getAnnotationValue(method, Subcommand.class, Annotations.NOTHING); -392 if (sub == null) { -393 return null; -394 } -395 Class<?> clazz = method.getDeclaringClass(); -396 String parent = getParentSubcommand(clazz); -397 return parent == null || parent.isEmpty() ? sub : parent + " " + sub; -398 } -399 -400 private String getParentSubcommand(Class<?> clazz) { -401 List<String> subList = new ArrayList<>(); -402 while (clazz != null) { -403 String sub = manager.getAnnotations().getAnnotationValue(clazz, Subcommand.class, Annotations.NOTHING); -404 if (sub != null) { -405 subList.add(sub); -406 } -407 clazz = clazz.getEnclosingClass(); -408 } -409 Collections.reverse(subList); -410 return ACFUtil.join(subList, " "); -411 } -412 -413 /** -414 * Registers the given {@link BaseCommand cmd} as a child of the {@link RootCommand} linked to the name given. -415 * -416 * @param name Name of the parent to cmd. -417 * @param cmd The {@link BaseCommand} to add as a child to the {@link RootCommand} owned name field. -418 */ -419 private void register(String name, BaseCommand cmd) { -420 String nameLower = name.toLowerCase(); -421 RootCommand rootCommand = manager.obtainRootCommand(nameLower); -422 rootCommand.addChild(cmd); -423 -424 this.registeredCommands.put(nameLower, rootCommand); -425 } -426 -427 /** -428 * Registers the given {@link Method} as a subcommand. -429 * -430 * @param method The method to register as a subcommand. -431 * @param subCommand The subcommand's name(s). -432 */ -433 private void registerSubcommand(Method method, String subCommand) { -434 subCommand = manager.getCommandReplacements().replace(subCommand.toLowerCase()); -435 final String[] subCommandParts = ACFPatterns.SPACE.split(subCommand); -436 // Must run getSubcommandPossibility BEFORE we rewrite it just after this. -437 Set<String> cmdList = getSubCommandPossibilityList(subCommandParts); -438 -439 // Strip pipes off for auto complete addition -440 for (int i = 0; i < subCommandParts.length; i++) { -441 String[] split = ACFPatterns.PIPE.split(subCommandParts[i]); -442 if (split.length == 0 || split[0].isEmpty()) { -443 throw new IllegalArgumentException("Invalid @Subcommand configuration for " + method.getName() + " - parts can not start with | or be empty"); -444 } -445 subCommandParts[i] = split[0]; -446 } -447 String prefSubCommand = ApacheCommonsLangUtil.join(subCommandParts, " "); -448 final String[] aliasNames = manager.getAnnotations().getAnnotationValues(method, CommandAlias.class, Annotations.REPLACEMENTS | Annotations.LOWERCASE); -449 -450 String cmdName = aliasNames != null ? aliasNames[0] : this.commandName + " "; -451 RegisteredCommand cmd = manager.createRegisteredCommand(this, cmdName, method, prefSubCommand); -452 -453 for (String subcmd : cmdList) { -454 subCommands.put(subcmd, cmd); -455 } -456 cmd.addSubcommands(cmdList); -457 -458 if (aliasNames != null) { -459 for (String name : aliasNames) { -460 register(name, new ForwardingCommand(this, cmd, subCommandParts)); -461 } -462 } -463 } -464 -465 /** -466 * Takes a string like "foo|bar baz|qux" and generates a list of -467 * - foo baz -468 * - foo qux -469 * - bar baz -470 * - bar qux -471 * <p> -472 * For every possible sub command combination -473 * -474 * @param subCommandParts -475 * @return List of all sub command possibilities -476 */ -477 private static Set<String> getSubCommandPossibilityList(String[] subCommandParts) { -478 int i = 0; -479 Set<String> current = null; -480 while (true) { -481 Set<String> newList = new HashSet<>(); -482 -483 if (i < subCommandParts.length) { -484 for (String s1 : ACFPatterns.PIPE.split(subCommandParts[i])) { -485 if (current != null) { -486 newList.addAll(current.stream().map(s -> s + " " + s1).collect(Collectors.toList())); -487 } else { -488 newList.add(s1); -489 } -490 } -491 } -492 -493 if (i + 1 < subCommandParts.length) { -494 current = newList; -495 i = i + 1; -496 continue; -497 } -498 -499 return newList; -500 } -501 } -502 -503 void execute(CommandIssuer issuer, CommandRouter.CommandRouteResult command) { -504 try { -505 CommandOperationContext commandContext = preCommandOperation(issuer, command.commandLabel, command.args, false); -506 execSubcommand = command.subcommand; -507 executeCommand(commandContext, issuer, command.args, command.cmd); -508 } finally { -509 postCommandOperation(); -510 } -511 } -512 -513 /** -514 * This is ran after any command operation has been performed. -515 */ -516 private void postCommandOperation() { -517 CommandManager.commandOperationContext.get().pop(); -518 execSubcommand = null; -519 execLabel = null; -520 origArgs = new String[]{}; -521 } -522 -523 /** -524 * This is ran before any command operation has been performed. -525 * -526 * @param issuer The user who executed the command. -527 * @param commandLabel The label the user used to execute the command. This is not the command name, but their input. -528 * When there is multiple aliases, this is which alias was used -529 * @param args The arguments passed to the command when executing it. -530 * @param isAsync Whether the command is executed off of the main thread. -531 * @return The context which is being registered to the {@link CommandManager}'s {@link -532 * CommandManager#commandOperationContext thread local stack}. -533 */ -534 private CommandOperationContext preCommandOperation(CommandIssuer issuer, String commandLabel, String[] args, boolean isAsync) { -535 Stack<CommandOperationContext> contexts = CommandManager.commandOperationContext.get(); -536 CommandOperationContext context = this.manager.createCommandOperationContext(this, issuer, commandLabel, args, isAsync); -537 contexts.push(context); -538 lastCommandOperationContext.set(context); -539 execSubcommand = null; -540 execLabel = commandLabel; -541 origArgs = args; -542 return context; -543 } -544 -545 /** -546 * Gets the current command issuer. -547 * -548 * @return The current command issuer. -549 */ -550 public CommandIssuer getCurrentCommandIssuer() { -551 return CommandManager.getCurrentCommandIssuer(); -552 } -553 -554 /** -555 * Gets the current command manager. -556 * -557 * @return The current command manager. -558 */ -559 public CommandManager getCurrentCommandManager() { -560 return CommandManager.getCurrentCommandManager(); -561 } -562 -563 private void executeCommand(CommandOperationContext commandOperationContext, -564 CommandIssuer issuer, String[] args, RegisteredCommand cmd) { -565 if (cmd.hasPermission(issuer)) { -566 commandOperationContext.setRegisteredCommand(cmd); -567 if (checkPrecommand(commandOperationContext, cmd, issuer, args)) { -568 return; -569 } -570 List<String> sargs = Arrays.asList(args); -571 cmd.invoke(issuer, sargs, commandOperationContext); -572 } else { -573 issuer.sendMessage(MessageType.ERROR, MessageKeys.PERMISSION_DENIED); -574 } -575 } -576 -577 /** -578 * Please use command conditions for restricting execution -579 * -580 * @param issuer -581 * @param cmd -582 * @return -583 * @deprecated See {@link CommandConditions} -584 */ -585 @SuppressWarnings("DeprecatedIsStillUsed") -586 @Deprecated -587 public boolean canExecute(CommandIssuer issuer, RegisteredCommand<?> cmd) { -588 return true; -589 } -590 -591 /** -592 * Gets tab completed data from the given command from the user. -593 * -594 * @param issuer The user who executed the tabcomplete. -595 * @param commandLabel The label which is being used by the user. -596 * @param args The arguments the user has typed so far. -597 * @return All possibilities in the tab complete. -598 */ -599 public List<String> tabComplete(CommandIssuer issuer, String commandLabel, String[] args) { -600 return tabComplete(issuer, commandLabel, args, false); -601 } -602 -603 /** -604 * Gets the tab complete suggestions from a given command. This will automatically find anything -605 * which is valid for the specified command through the command's implementation. -606 * -607 * @param issuer The issuer of the command. -608 * @param commandLabel The command name as entered by the user instead of the ACF registered name. -609 * @param args All arguments entered by the user. -610 * @param isAsync Whether this is run off of the main thread. -611 * @return The possibilities to tab complete in no particular order. -612 */ -613 @SuppressWarnings("WeakerAccess") -614 public List<String> tabComplete(CommandIssuer issuer, String commandLabel, String[] args, boolean isAsync) -615 throws IllegalArgumentException { -616 return tabComplete(issuer, manager.getRootCommand(commandLabel.toLowerCase()), args, isAsync); -617 } -618 -619 List<String> tabComplete(CommandIssuer issuer, RootCommand rootCommand, String[] args, boolean isAsync) -620 throws IllegalArgumentException { -621 if (args.length == 0) { -622 args = new String[]{""}; -623 } -624 String commandLabel = rootCommand.getCommandName(); -625 try { -626 CommandRouter router = manager.getRouter(); -627 -628 preCommandOperation(issuer, commandLabel, args, isAsync); -629 -630 final RouteSearch search = router.routeCommand(rootCommand, commandLabel, args, true); -631 -632 final List<String> cmds = new ArrayList<>(); -633 if (search != null) { -634 CommandRouter.CommandRouteResult result = router.matchCommand(search, true); -635 if (result != null) { -636 cmds.addAll(completeCommand(issuer, result.cmd, result.args, commandLabel, isAsync)); -637 } -638 } -639 -640 return filterTabComplete(args[args.length - 1], cmds); -641 } finally { -642 postCommandOperation(); -643 } -644 } -645 -646 /** -647 * Gets all subcommands which are possible to tabcomplete. -648 * -649 * @param issuer The command issuer. -650 * @param args -651 * @return -652 */ -653 List<String> getCommandsForCompletion(CommandIssuer issuer, String[] args) { -654 final Set<String> cmds = new HashSet<>(); -655 final int cmdIndex = Math.max(0, args.length - 1); -656 String argString = ApacheCommonsLangUtil.join(args, " ").toLowerCase(); -657 for (Map.Entry<String, RegisteredCommand> entry : subCommands.entries()) { -658 final String key = entry.getKey(); -659 if (key.startsWith(argString) && !CATCHUNKNOWN.equals(key) && !DEFAULT.equals(key)) { -660 final RegisteredCommand value = entry.getValue(); -661 if (!value.hasPermission(issuer) || value.isPrivate) { -662 continue; -663 } -664 -665 String[] split = ACFPatterns.SPACE.split(value.prefSubCommand); -666 cmds.add(split[cmdIndex]); -667 } -668 } -669 return new ArrayList<>(cmds); -670 } -671 -672 /** -673 * Complete a command properly per issuer and input. -674 * -675 * @param issuer The user who executed this. -676 * @param cmd The command to be completed. -677 * @param args All arguments given by the user. -678 * @param commandLabel The command name the user used. -679 * @param isAsync Whether the command was executed async. -680 * @return All results to complete the command. -681 */ -682 private List<String> completeCommand(CommandIssuer issuer, RegisteredCommand cmd, String[] args, String commandLabel, boolean isAsync) { -683 if (!cmd.hasPermission(issuer) || args.length > cmd.consumeInputResolvers || args.length == 0) { -684 return Collections.emptyList(); -685 } -686 -687 List<String> cmds = manager.getCommandCompletions().of(cmd, issuer, args, isAsync); -688 return filterTabComplete(args[args.length - 1], cmds); -689 } -690 -691 /** -692 * Gets the actual args in string form the user typed -693 * This returns a list of all tab complete options which are possible with the given argument and commands. -694 * -695 * @param arg Argument which was pressed tab on. -696 * @param cmds The possibilities to return. -697 * @return All possible options. This may be empty. -698 */ -699 private static List<String> filterTabComplete(String arg, List<String> cmds) { -700 return cmds.stream() -701 .distinct() -702 .filter(cmd -> cmd != null && (arg.isEmpty() || ApacheCommonsLangUtil.startsWithIgnoreCase(cmd, arg))) -703 .collect(Collectors.toList()); -704 } -705 -706 /** -707 * Executes the precommand and sees whether something is wrong. Ideally, you get false from this. -708 * -709 * @param commandOperationContext The context to use. -710 * @param cmd The command executed. -711 * @param issuer The issuer who executed the command. -712 * @param args The arguments the issuer provided. -713 * @return Whether something went wrong. -714 */ -715 private boolean checkPrecommand(CommandOperationContext commandOperationContext, RegisteredCommand cmd, CommandIssuer issuer, String[] args) { -716 Method pre = this.preCommandHandler; -717 if (pre != null) { -718 try { -719 Class<?>[] types = pre.getParameterTypes(); -720 Object[] parameters = new Object[pre.getParameterCount()]; -721 for (int i = 0; i < parameters.length; i++) { -722 Class<?> type = types[i]; -723 Object issuerObject = issuer.getIssuer(); -724 if (manager.isCommandIssuer(type) && type.isAssignableFrom(issuerObject.getClass())) { -725 parameters[i] = issuerObject; -726 } else if (CommandIssuer.class.isAssignableFrom(type)) { -727 parameters[i] = issuer; -728 } else if (RegisteredCommand.class.isAssignableFrom(type)) { -729 parameters[i] = cmd; -730 } else if (String[].class.isAssignableFrom((type))) { -731 parameters[i] = args; -732 } else { -733 parameters[i] = null; -734 } -735 } -736 -737 return (boolean) pre.invoke(this, parameters); -738 } catch (IllegalAccessException | InvocationTargetException e) { -739 this.manager.log(LogLevel.ERROR, "Exception encountered while command pre-processing", e); -740 } -741 } -742 return false; -743 } -744 -745 /** -746 * @deprecated Unstable API -747 */ -748 @Deprecated -749 @UnstableAPI -750 public CommandHelp getCommandHelp() { -751 return manager.generateCommandHelp(); -752 } -753 -754 /** -755 * @deprecated Unstable API -756 */ -757 @Deprecated -758 @UnstableAPI -759 public void showCommandHelp() { -760 getCommandHelp().showHelp(); -761 } -762 -763 public void help(Object issuer, String[] args) { -764 help(manager.getCommandIssuer(issuer), args); -765 } -766 -767 public void help(CommandIssuer issuer, String[] args) { -768 issuer.sendMessage(MessageType.ERROR, MessageKeys.UNKNOWN_COMMAND); -769 } -770 -771 public void doHelp(Object issuer, String... args) { -772 doHelp(manager.getCommandIssuer(issuer), args); -773 } -774 -775 public void doHelp(CommandIssuer issuer, String... args) { -776 help(issuer, args); -777 } -778 -779 public void showSyntax(CommandIssuer issuer, RegisteredCommand<?> cmd) { -780 issuer.sendMessage(MessageType.SYNTAX, MessageKeys.INVALID_SYNTAX, -781 "{command}", manager.getCommandPrefix(issuer) + cmd.command, -782 "{syntax}", cmd.syntaxText -783 ); -784 } -785 -786 public boolean hasPermission(Object issuer) { -787 return hasPermission(manager.getCommandIssuer(issuer)); -788 } -789 -790 public boolean hasPermission(CommandIssuer issuer) { -791 return this.manager.hasPermission(issuer, getRequiredPermissions()); -792 } -793 -794 public Set<String> getRequiredPermissions() { -795 return this.permissions; -796 } -797 -798 public boolean requiresPermission(String permission) { -799 return getRequiredPermissions().contains(permission); -800 } -801 -802 public String getName() { -803 return commandName; -804 } -805 -806 public ExceptionHandler getExceptionHandler() { -807 return exceptionHandler; -808 } -809 -810 public BaseCommand setExceptionHandler(ExceptionHandler exceptionHandler) { -811 this.exceptionHandler = exceptionHandler; -812 return this; -813 } -814 -815 public RegisteredCommand getDefaultRegisteredCommand() { -816 return ACFUtil.getFirstElement(this.subCommands.get(DEFAULT)); -817 } -818 -819 public String setContextFlags(Class<?> cls, String flags) { -820 return this.contextFlags.put(cls, flags); -821 } -822 -823 public String getContextFlags(Class<?> cls) { -824 return this.contextFlags.get(cls); -825 } -826 -827 public List<RegisteredCommand> getRegisteredCommands() { -828 List<RegisteredCommand> registeredCommands = new ArrayList<>(); -829 registeredCommands.addAll(this.subCommands.values()); -830 return registeredCommands; -831 } -832} +033import co.aikar.commands.annotation.Description; +034import co.aikar.commands.annotation.HelpCommand; +035import co.aikar.commands.annotation.PreCommand; +036import co.aikar.commands.annotation.Subcommand; +037import co.aikar.commands.annotation.UnknownHandler; +038import co.aikar.commands.apachecommonslang.ApacheCommonsLangUtil; +039import com.google.common.collect.HashMultimap; +040import com.google.common.collect.SetMultimap; +041import org.jetbrains.annotations.Nullable; +042 +043import java.lang.reflect.Constructor; +044import java.lang.reflect.InvocationTargetException; +045import java.lang.reflect.Method; +046import java.lang.reflect.Parameter; +047import java.util.ArrayList; +048import java.util.Arrays; +049import java.util.Collections; +050import java.util.HashMap; +051import java.util.HashSet; +052import java.util.LinkedHashSet; +053import java.util.List; +054import java.util.Map; +055import java.util.Objects; +056import java.util.Set; +057import java.util.Stack; +058import java.util.stream.Collectors; +059import java.util.stream.Stream; +060 +061/** +062 * A Base command is defined as a command group of related commands. +063 * A BaseCommand does not imply nor enforce that they use the same root command. +064 * <p> +065 * It is up to the end user how to organize their command. you could use 1 base command per +066 * command in your application. +067 * <p> +068 * Optionally (and encouraged), you can use the base command to represent a root command, and +069 * then each actionable command is a sub command +070 */ +071 +072public abstract class BaseCommand { +073 +074 /** +075 * This is a field which contains the magic key in the {@link #subCommands} map for the method to catch any unknown +076 * argument to command states. +077 */ +078 static final String CATCHUNKNOWN = "__catchunknown"; +079 /** +080 * This is a field which contains the magic key in the {@link #subCommands} map for the method which is default for the +081 * entire base command. +082 */ +083 static final String DEFAULT = "__default"; +084 +085 /** +086 * A map of all the registered commands for this base command, keyed to each potential subcommand to access it. +087 */ +088 final SetMultimap<String, RegisteredCommand> subCommands = HashMultimap.create(); +089 +090 /** +091 * A map of flags to pass to Context Resolution for every parameter of the type. This is like an automatic @Flags on each. +092 */ +093 final Map<Class<?>, String> contextFlags = new HashMap<>(); +094 +095 /** +096 * What method was annoated with {@link PreCommand} to execute before commands. +097 */ +098 @Nullable +099 private Method preCommandHandler; +100 +101 /** +102 * What root command the user actually entered to access the currently executing command +103 */ +104 @SuppressWarnings("WeakerAccess") +105 private String execLabel; +106 /** +107 * What subcommand the user actually entered to access the currently executing command +108 */ +109 @SuppressWarnings("WeakerAccess") +110 private String execSubcommand; +111 /** +112 * What arguments the user actually entered after the root command to access the currently executing command +113 */ +114 @SuppressWarnings("WeakerAccess") +115 private String[] origArgs; +116 +117 /** +118 * The manager this is registered to +119 */ +120 CommandManager<?, ?, ?, ?, ?, ?> manager = null; +121 +122 /** +123 * The command which owns this. This may be null if there are no owners. +124 */ +125 BaseCommand parentCommand; +126 Map<String, RootCommand> registeredCommands = new HashMap<>(); +127 /** +128 * The description of the command. This may be null if no description has been provided. +129 * Used for help documentation +130 */ +131 @Nullable String description; +132 /** +133 * The name of the command. This may be null if no name has been provided. +134 */ +135 @Nullable String commandName; +136 /** +137 * The permission of the command. This may be null if no permission has been provided. +138 */ +139 @Nullable String permission; +140 /** +141 * The conditions of the command. This may be null if no conditions has been provided. +142 */ +143 @Nullable String conditions; +144 /** +145 * Identifies if the command has an explicit help command annotated with {@link HelpCommand} +146 */ +147 boolean hasHelpCommand; +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 private final ThreadLocal<CommandOperationContext> lastCommandOperationContext = new ThreadLocal<>(); +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 +161 private String parentSubcommand; +162 +163 /** +164 * The permissions of the command. +165 */ +166 private final Set<String> permissions = new HashSet<>(); +167 +168 public BaseCommand() { +169 } +170 +171 /** +172 * Constructor based defining of commands will be removed in the next version bump. +173 * +174 * @param cmd +175 * @deprecated Please switch to {@link CommandAlias} for defining all root commands. +176 */ +177 @Deprecated +178 public BaseCommand(@Nullable String cmd) { +179 this.commandName = cmd; +180 } +181 +182 /** +183 * Returns a reference to the last used CommandOperationContext. +184 * This method is ThreadLocal, in that it can only be used on a thread that has executed a command +185 * +186 * @return +187 */ +188 public CommandOperationContext getLastCommandOperationContext() { +189 return lastCommandOperationContext.get(); +190 } +191 +192 /** +193 * Gets the root command name that the user actually typed +194 * +195 * @return Name +196 */ +197 public String getExecCommandLabel() { +198 return execLabel; +199 } +200 +201 /** +202 * Gets the actual sub command name the user typed +203 * +204 * @return Name +205 */ +206 public String getExecSubcommand() { +207 return execSubcommand; +208 } +209 +210 /** +211 * Gets the actual args in string form the user typed +212 * +213 * @return Args +214 */ +215 public String[] getOrigArgs() { +216 return origArgs; +217 } +218 +219 /** +220 * This should be called whenever the command gets registered. +221 * It sets all required fields correctly and injects dependencies. +222 * +223 * @param manager The manager to register as this command's owner and handler. +224 */ +225 void onRegister(CommandManager manager) { +226 onRegister(manager, this.commandName); +227 } +228 +229 /** +230 * This should be called whenever the command gets registered. +231 * It sets all required fields correctly and injects dependencies. +232 * +233 * @param manager The manager to register as this command's owner and handler. +234 * @param cmd The command name to use register with. +235 */ +236 private void onRegister(CommandManager manager, String cmd) { +237 manager.injectDependencies(this); +238 this.manager = manager; +239 +240 final Annotations annotations = manager.getAnnotations(); +241 final Class<? extends BaseCommand> self = this.getClass(); +242 +243 String[] cmdAliases = annotations.getAnnotationValues(self, CommandAlias.class, Annotations.REPLACEMENTS | Annotations.LOWERCASE | Annotations.NO_EMPTY); +244 +245 if (cmd == null && cmdAliases != null) { +246 cmd = cmdAliases[0]; +247 } +248 +249 this.commandName = cmd != null ? cmd : self.getSimpleName().toLowerCase(); +250 this.permission = annotations.getAnnotationValue(self, CommandPermission.class, Annotations.REPLACEMENTS); +251 this.description = annotations.getAnnotationValue(self, Description.class, Annotations.NO_EMPTY | Annotations.REPLACEMENTS); +252 this.parentSubcommand = getParentSubcommand(self); +253 this.conditions = annotations.getAnnotationValue(self, Conditions.class, Annotations.REPLACEMENTS | Annotations.NO_EMPTY); +254 +255 registerSubcommands(); +256 computePermissions(); +257 registerSubclasses(cmd); +258 +259 if (cmdAliases != null) { +260 Set<String> cmdList = new HashSet<>(); +261 Collections.addAll(cmdList, cmdAliases); +262 cmdList.remove(cmd); +263 for (String cmdAlias : cmdList) { +264 register(cmdAlias, this); +265 } +266 } +267 +268 if (cmd != null) { +269 register(cmd, this); +270 } +271 } +272 +273 /** +274 * This recursively registers all subclasses of the command as subcommands, if they are of type {@link BaseCommand}. +275 * +276 * @param cmd The command name of this command. +277 */ +278 private void registerSubclasses(String cmd) { +279 for (Class<?> clazz : this.getClass().getDeclaredClasses()) { +280 if (BaseCommand.class.isAssignableFrom(clazz)) { +281 try { +282 BaseCommand subCommand = null; +283 Constructor<?>[] declaredConstructors = clazz.getDeclaredConstructors(); +284 for (Constructor<?> declaredConstructor : declaredConstructors) { +285 +286 declaredConstructor.setAccessible(true); +287 Parameter[] parameters = declaredConstructor.getParameters(); +288 if (parameters.length == 1) { +289 subCommand = (BaseCommand) declaredConstructor.newInstance(this); +290 } else { +291 manager.log(LogLevel.INFO, "Found unusable constructor: " + declaredConstructor.getName() + "(" + Stream.of(parameters).map(p -> p.getType().getSimpleName() + " " + p.getName()).collect(Collectors.joining("<c2>,</c2> ")) + ")"); +292 } +293 } +294 if (subCommand != null) { +295 subCommand.parentCommand = this; +296 subCommand.onRegister(manager, cmd); +297 this.subCommands.putAll(subCommand.subCommands); +298 this.registeredCommands.putAll(subCommand.registeredCommands); +299 } else { +300 this.manager.log(LogLevel.ERROR, "Could not find a subcommand ctor for " + clazz.getName()); +301 } +302 } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { +303 this.manager.log(LogLevel.ERROR, "Error registering subclass", e); +304 } +305 } +306 } +307 } +308 +309 /** +310 * This registers all subcommands of the command. +311 */ +312 private void registerSubcommands() { +313 final Annotations annotations = manager.getAnnotations(); +314 boolean foundCatchUnknown = false; +315 boolean isParentEmpty = parentSubcommand == null || parentSubcommand.isEmpty(); +316 Set<Method> methods = new LinkedHashSet<>(); +317 Collections.addAll(methods, this.getClass().getDeclaredMethods()); +318 Collections.addAll(methods, this.getClass().getMethods()); +319 +320 for (Method method : methods) { +321 method.setAccessible(true); +322 String sublist = null; +323 String sub = getSubcommandValue(method); +324 final String helpCommand = annotations.getAnnotationValue(method, HelpCommand.class, Annotations.NOTHING); +325 final String commandAliases = annotations.getAnnotationValue(method, CommandAlias.class, Annotations.NOTHING); +326 +327 if (annotations.hasAnnotation(method, Default.class)) { +328 if (!isParentEmpty) { +329 sub = parentSubcommand; +330 } else { +331 registerSubcommand(method, DEFAULT); +332 } +333 } +334 +335 if (sub != null) { +336 sublist = sub; +337 } else if (commandAliases != null) { +338 sublist = commandAliases; +339 } else if (helpCommand != null) { +340 sublist = helpCommand; +341 hasHelpCommand = true; +342 } +343 +344 boolean preCommand = annotations.hasAnnotation(method, PreCommand.class); +345 boolean hasCatchUnknown = annotations.hasAnnotation(method, CatchUnknown.class) || +346 annotations.hasAnnotation(method, CatchAll.class) || +347 annotations.hasAnnotation(method, UnknownHandler.class); +348 +349 if (hasCatchUnknown || (!foundCatchUnknown && helpCommand != null)) { +350 if (!foundCatchUnknown) { +351 if (hasCatchUnknown) { +352 this.subCommands.get(CATCHUNKNOWN).clear(); +353 foundCatchUnknown = true; +354 } +355 registerSubcommand(method, CATCHUNKNOWN); +356 } else { +357 ACFUtil.sneaky(new IllegalStateException("Multiple @CatchUnknown/@HelpCommand commands, duplicate on " + method.getDeclaringClass().getName() + "#" + method.getName())); +358 } +359 } else if (preCommand) { +360 if (this.preCommandHandler == null) { +361 this.preCommandHandler = method; +362 } else { +363 ACFUtil.sneaky(new IllegalStateException("Multiple @PreCommand commands, duplicate on " + method.getDeclaringClass().getName() + "#" + method.getName())); +364 } +365 } +366 if (Objects.equals(method.getDeclaringClass(), this.getClass()) && sublist != null) { +367 registerSubcommand(method, sublist); +368 } +369 } +370 } +371 +372 /** +373 * This registers all the permissions required to execute this command. +374 */ +375 private void computePermissions() { +376 this.permissions.clear(); +377 if (this.permission != null && !this.permission.isEmpty()) { +378 this.permissions.addAll(Arrays.asList(ACFPatterns.COMMA.split(this.permission))); +379 } +380 if (this.parentCommand != null) { +381 this.permissions.addAll(this.parentCommand.getRequiredPermissions()); +382 } +383 } +384 +385 /** +386 * Gets the subcommand name of the method given. +387 * +388 * @param method The method to check. +389 * @return The name of the subcommand. It returns null if the input doesn't have {@link Subcommand} attached. +390 */ +391 private String getSubcommandValue(Method method) { +392 final String sub = manager.getAnnotations().getAnnotationValue(method, Subcommand.class, Annotations.NOTHING); +393 if (sub == null) { +394 return null; +395 } +396 Class<?> clazz = method.getDeclaringClass(); +397 String parent = getParentSubcommand(clazz); +398 return parent == null || parent.isEmpty() ? sub : parent + " " + sub; +399 } +400 +401 private String getParentSubcommand(Class<?> clazz) { +402 List<String> subList = new ArrayList<>(); +403 while (clazz != null) { +404 String sub = manager.getAnnotations().getAnnotationValue(clazz, Subcommand.class, Annotations.NOTHING); +405 if (sub != null) { +406 subList.add(sub); +407 } +408 clazz = clazz.getEnclosingClass(); +409 } +410 Collections.reverse(subList); +411 return ACFUtil.join(subList, " "); +412 } +413 +414 /** +415 * Registers the given {@link BaseCommand cmd} as a child of the {@link RootCommand} linked to the name given. +416 * +417 * @param name Name of the parent to cmd. +418 * @param cmd The {@link BaseCommand} to add as a child to the {@link RootCommand} owned name field. +419 */ +420 private void register(String name, BaseCommand cmd) { +421 String nameLower = name.toLowerCase(); +422 RootCommand rootCommand = manager.obtainRootCommand(nameLower); +423 rootCommand.addChild(cmd); +424 +425 this.registeredCommands.put(nameLower, rootCommand); +426 } +427 +428 /** +429 * Registers the given {@link Method} as a subcommand. +430 * +431 * @param method The method to register as a subcommand. +432 * @param subCommand The subcommand's name(s). +433 */ +434 private void registerSubcommand(Method method, String subCommand) { +435 subCommand = manager.getCommandReplacements().replace(subCommand.toLowerCase()); +436 final String[] subCommandParts = ACFPatterns.SPACE.split(subCommand); +437 // Must run getSubcommandPossibility BEFORE we rewrite it just after this. +438 Set<String> cmdList = getSubCommandPossibilityList(subCommandParts); +439 +440 // Strip pipes off for auto complete addition +441 for (int i = 0; i < subCommandParts.length; i++) { +442 String[] split = ACFPatterns.PIPE.split(subCommandParts[i]); +443 if (split.length == 0 || split[0].isEmpty()) { +444 throw new IllegalArgumentException("Invalid @Subcommand configuration for " + method.getName() + " - parts can not start with | or be empty"); +445 } +446 subCommandParts[i] = split[0]; +447 } +448 String prefSubCommand = ApacheCommonsLangUtil.join(subCommandParts, " "); +449 final String[] aliasNames = manager.getAnnotations().getAnnotationValues(method, CommandAlias.class, Annotations.REPLACEMENTS | Annotations.LOWERCASE); +450 +451 String cmdName = aliasNames != null ? aliasNames[0] : this.commandName + " "; +452 RegisteredCommand cmd = manager.createRegisteredCommand(this, cmdName, method, prefSubCommand); +453 +454 for (String subcmd : cmdList) { +455 subCommands.put(subcmd, cmd); +456 } +457 cmd.addSubcommands(cmdList); +458 +459 if (aliasNames != null) { +460 for (String name : aliasNames) { +461 register(name, new ForwardingCommand(this, cmd, subCommandParts)); +462 } +463 } +464 } +465 +466 /** +467 * Takes a string like "foo|bar baz|qux" and generates a list of +468 * - foo baz +469 * - foo qux +470 * - bar baz +471 * - bar qux +472 * <p> +473 * For every possible sub command combination +474 * +475 * @param subCommandParts +476 * @return List of all sub command possibilities +477 */ +478 private static Set<String> getSubCommandPossibilityList(String[] subCommandParts) { +479 int i = 0; +480 Set<String> current = null; +481 while (true) { +482 Set<String> newList = new HashSet<>(); +483 +484 if (i < subCommandParts.length) { +485 for (String s1 : ACFPatterns.PIPE.split(subCommandParts[i])) { +486 if (current != null) { +487 newList.addAll(current.stream().map(s -> s + " " + s1).collect(Collectors.toList())); +488 } else { +489 newList.add(s1); +490 } +491 } +492 } +493 +494 if (i + 1 < subCommandParts.length) { +495 current = newList; +496 i = i + 1; +497 continue; +498 } +499 +500 return newList; +501 } +502 } +503 +504 void execute(CommandIssuer issuer, CommandRouter.CommandRouteResult command) { +505 try { +506 CommandOperationContext commandContext = preCommandOperation(issuer, command.commandLabel, command.args, false); +507 execSubcommand = command.subcommand; +508 executeCommand(commandContext, issuer, command.args, command.cmd); +509 } finally { +510 postCommandOperation(); +511 } +512 } +513 +514 /** +515 * This is ran after any command operation has been performed. +516 */ +517 private void postCommandOperation() { +518 CommandManager.commandOperationContext.get().pop(); +519 execSubcommand = null; +520 execLabel = null; +521 origArgs = new String[]{}; +522 } +523 +524 /** +525 * This is ran before any command operation has been performed. +526 * +527 * @param issuer The user who executed the command. +528 * @param commandLabel The label the user used to execute the command. This is not the command name, but their input. +529 * When there is multiple aliases, this is which alias was used +530 * @param args The arguments passed to the command when executing it. +531 * @param isAsync Whether the command is executed off of the main thread. +532 * @return The context which is being registered to the {@link CommandManager}'s {@link +533 * CommandManager#commandOperationContext thread local stack}. +534 */ +535 private CommandOperationContext preCommandOperation(CommandIssuer issuer, String commandLabel, String[] args, boolean isAsync) { +536 Stack<CommandOperationContext> contexts = CommandManager.commandOperationContext.get(); +537 CommandOperationContext context = this.manager.createCommandOperationContext(this, issuer, commandLabel, args, isAsync); +538 contexts.push(context); +539 lastCommandOperationContext.set(context); +540 execSubcommand = null; +541 execLabel = commandLabel; +542 origArgs = args; +543 return context; +544 } +545 +546 /** +547 * Gets the current command issuer. +548 * +549 * @return The current command issuer. +550 */ +551 public CommandIssuer getCurrentCommandIssuer() { +552 return CommandManager.getCurrentCommandIssuer(); +553 } +554 +555 /** +556 * Gets the current command manager. +557 * +558 * @return The current command manager. +559 */ +560 public CommandManager getCurrentCommandManager() { +561 return CommandManager.getCurrentCommandManager(); +562 } +563 +564 private void executeCommand(CommandOperationContext commandOperationContext, +565 CommandIssuer issuer, String[] args, RegisteredCommand cmd) { +566 if (cmd.hasPermission(issuer)) { +567 commandOperationContext.setRegisteredCommand(cmd); +568 if (checkPrecommand(commandOperationContext, cmd, issuer, args)) { +569 return; +570 } +571 List<String> sargs = Arrays.asList(args); +572 cmd.invoke(issuer, sargs, commandOperationContext); +573 } else { +574 issuer.sendMessage(MessageType.ERROR, MessageKeys.PERMISSION_DENIED); +575 } +576 } +577 +578 /** +579 * Please use command conditions for restricting execution +580 * +581 * @param issuer +582 * @param cmd +583 * @return +584 * @deprecated See {@link CommandConditions} +585 */ +586 @SuppressWarnings("DeprecatedIsStillUsed") +587 @Deprecated +588 public boolean canExecute(CommandIssuer issuer, RegisteredCommand<?> cmd) { +589 return true; +590 } +591 +592 /** +593 * Gets tab completed data from the given command from the user. +594 * +595 * @param issuer The user who executed the tabcomplete. +596 * @param commandLabel The label which is being used by the user. +597 * @param args The arguments the user has typed so far. +598 * @return All possibilities in the tab complete. +599 */ +600 public List<String> tabComplete(CommandIssuer issuer, String commandLabel, String[] args) { +601 return tabComplete(issuer, commandLabel, args, false); +602 } +603 +604 /** +605 * Gets the tab complete suggestions from a given command. This will automatically find anything +606 * which is valid for the specified command through the command's implementation. +607 * +608 * @param issuer The issuer of the command. +609 * @param commandLabel The command name as entered by the user instead of the ACF registered name. +610 * @param args All arguments entered by the user. +611 * @param isAsync Whether this is run off of the main thread. +612 * @return The possibilities to tab complete in no particular order. +613 */ +614 @SuppressWarnings("WeakerAccess") +615 public List<String> tabComplete(CommandIssuer issuer, String commandLabel, String[] args, boolean isAsync) +616 throws IllegalArgumentException { +617 return tabComplete(issuer, manager.getRootCommand(commandLabel.toLowerCase()), args, isAsync); +618 } +619 +620 List<String> tabComplete(CommandIssuer issuer, RootCommand rootCommand, String[] args, boolean isAsync) +621 throws IllegalArgumentException { +622 if (args.length == 0) { +623 args = new String[]{""}; +624 } +625 String commandLabel = rootCommand.getCommandName(); +626 try { +627 CommandRouter router = manager.getRouter(); +628 +629 preCommandOperation(issuer, commandLabel, args, isAsync); +630 +631 final RouteSearch search = router.routeCommand(rootCommand, commandLabel, args, true); +632 +633 final List<String> cmds = new ArrayList<>(); +634 if (search != null) { +635 CommandRouter.CommandRouteResult result = router.matchCommand(search, true); +636 if (result != null) { +637 cmds.addAll(completeCommand(issuer, result.cmd, result.args, commandLabel, isAsync)); +638 } +639 } +640 +641 return filterTabComplete(args[args.length - 1], cmds); +642 } finally { +643 postCommandOperation(); +644 } +645 } +646 +647 /** +648 * Gets all subcommands which are possible to tabcomplete. +649 * +650 * @param issuer The command issuer. +651 * @param args +652 * @return +653 */ +654 List<String> getCommandsForCompletion(CommandIssuer issuer, String[] args) { +655 final Set<String> cmds = new HashSet<>(); +656 final int cmdIndex = Math.max(0, args.length - 1); +657 String argString = ApacheCommonsLangUtil.join(args, " ").toLowerCase(); +658 for (Map.Entry<String, RegisteredCommand> entry : subCommands.entries()) { +659 final String key = entry.getKey(); +660 if (key.startsWith(argString) && !CATCHUNKNOWN.equals(key) && !DEFAULT.equals(key)) { +661 final RegisteredCommand value = entry.getValue(); +662 if (!value.hasPermission(issuer) || value.isPrivate) { +663 continue; +664 } +665 +666 String[] split = ACFPatterns.SPACE.split(value.prefSubCommand); +667 cmds.add(split[cmdIndex]); +668 } +669 } +670 return new ArrayList<>(cmds); +671 } +672 +673 /** +674 * Complete a command properly per issuer and input. +675 * +676 * @param issuer The user who executed this. +677 * @param cmd The command to be completed. +678 * @param args All arguments given by the user. +679 * @param commandLabel The command name the user used. +680 * @param isAsync Whether the command was executed async. +681 * @return All results to complete the command. +682 */ +683 private List<String> completeCommand(CommandIssuer issuer, RegisteredCommand cmd, String[] args, String commandLabel, boolean isAsync) { +684 if (!cmd.hasPermission(issuer) || args.length > cmd.consumeInputResolvers || args.length == 0) { +685 return Collections.emptyList(); +686 } +687 +688 List<String> cmds = manager.getCommandCompletions().of(cmd, issuer, args, isAsync); +689 return filterTabComplete(args[args.length - 1], cmds); +690 } +691 +692 /** +693 * Gets the actual args in string form the user typed +694 * This returns a list of all tab complete options which are possible with the given argument and commands. +695 * +696 * @param arg Argument which was pressed tab on. +697 * @param cmds The possibilities to return. +698 * @return All possible options. This may be empty. +699 */ +700 private static List<String> filterTabComplete(String arg, List<String> cmds) { +701 return cmds.stream() +702 .distinct() +703 .filter(cmd -> cmd != null && (arg.isEmpty() || ApacheCommonsLangUtil.startsWithIgnoreCase(cmd, arg))) +704 .collect(Collectors.toList()); +705 } +706 +707 /** +708 * Executes the precommand and sees whether something is wrong. Ideally, you get false from this. +709 * +710 * @param commandOperationContext The context to use. +711 * @param cmd The command executed. +712 * @param issuer The issuer who executed the command. +713 * @param args The arguments the issuer provided. +714 * @return Whether something went wrong. +715 */ +716 private boolean checkPrecommand(CommandOperationContext commandOperationContext, RegisteredCommand cmd, CommandIssuer issuer, String[] args) { +717 Method pre = this.preCommandHandler; +718 if (pre != null) { +719 try { +720 Class<?>[] types = pre.getParameterTypes(); +721 Object[] parameters = new Object[pre.getParameterCount()]; +722 for (int i = 0; i < parameters.length; i++) { +723 Class<?> type = types[i]; +724 Object issuerObject = issuer.getIssuer(); +725 if (manager.isCommandIssuer(type) && type.isAssignableFrom(issuerObject.getClass())) { +726 parameters[i] = issuerObject; +727 } else if (CommandIssuer.class.isAssignableFrom(type)) { +728 parameters[i] = issuer; +729 } else if (RegisteredCommand.class.isAssignableFrom(type)) { +730 parameters[i] = cmd; +731 } else if (String[].class.isAssignableFrom((type))) { +732 parameters[i] = args; +733 } else { +734 parameters[i] = null; +735 } +736 } +737 +738 return (boolean) pre.invoke(this, parameters); +739 } catch (IllegalAccessException | InvocationTargetException e) { +740 this.manager.log(LogLevel.ERROR, "Exception encountered while command pre-processing", e); +741 } +742 } +743 return false; +744 } +745 +746 /** +747 * @deprecated Unstable API +748 */ +749 @Deprecated +750 @UnstableAPI +751 public CommandHelp getCommandHelp() { +752 return manager.generateCommandHelp(); +753 } +754 +755 /** +756 * @deprecated Unstable API +757 */ +758 @Deprecated +759 @UnstableAPI +760 public void showCommandHelp() { +761 getCommandHelp().showHelp(); +762 } +763 +764 public void help(Object issuer, String[] args) { +765 help(manager.getCommandIssuer(issuer), args); +766 } +767 +768 public void help(CommandIssuer issuer, String[] args) { +769 issuer.sendMessage(MessageType.ERROR, MessageKeys.UNKNOWN_COMMAND); +770 } +771 +772 public void doHelp(Object issuer, String... args) { +773 doHelp(manager.getCommandIssuer(issuer), args); +774 } +775 +776 public void doHelp(CommandIssuer issuer, String... args) { +777 help(issuer, args); +778 } +779 +780 public void showSyntax(CommandIssuer issuer, RegisteredCommand<?> cmd) { +781 issuer.sendMessage(MessageType.SYNTAX, MessageKeys.INVALID_SYNTAX, +782 "{command}", manager.getCommandPrefix(issuer) + cmd.command, +783 "{syntax}", cmd.syntaxText +784 ); +785 } +786 +787 public boolean hasPermission(Object issuer) { +788 return hasPermission(manager.getCommandIssuer(issuer)); +789 } +790 +791 public boolean hasPermission(CommandIssuer issuer) { +792 return this.manager.hasPermission(issuer, getRequiredPermissions()); +793 } +794 +795 public Set<String> getRequiredPermissions() { +796 return this.permissions; +797 } +798 +799 public boolean requiresPermission(String permission) { +800 return getRequiredPermissions().contains(permission); +801 } +802 +803 public String getName() { +804 return commandName; +805 } +806 +807 public ExceptionHandler getExceptionHandler() { +808 return exceptionHandler; +809 } +810 +811 public BaseCommand setExceptionHandler(ExceptionHandler exceptionHandler) { +812 this.exceptionHandler = exceptionHandler; +813 return this; +814 } +815 +816 public RegisteredCommand getDefaultRegisteredCommand() { +817 return ACFUtil.getFirstElement(this.subCommands.get(DEFAULT)); +818 } +819 +820 public String setContextFlags(Class<?> cls, String flags) { +821 return this.contextFlags.put(cls, flags); +822 } +823 +824 public String getContextFlags(Class<?> cls) { +825 return this.contextFlags.get(cls); +826 } +827 +828 public List<RegisteredCommand> getRegisteredCommands() { +829 List<RegisteredCommand> registeredCommands = new ArrayList<>(); +830 registeredCommands.addAll(this.subCommands.values()); +831 return registeredCommands; +832 } +833} 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 616f583a..124cf0d9 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 @@ -55,26 +55,26 @@ 047 048 public CommandCompletions(CommandManager manager) { 049 this.manager = manager; -050 registerAsyncCompletion("nothing", c -> Collections.emptyList()); -051 registerAsyncCompletion("range", (c) -> { -052 String config = c.getConfig(); -053 if (config == null) { -054 return Collections.emptyList(); -055 } -056 final String[] ranges = ACFPatterns.DASH.split(config); -057 int start; -058 int end; -059 if (ranges.length != 2) { -060 start = 0; -061 end = ACFUtil.parseInt(ranges[0], 0); -062 } else { -063 start = ACFUtil.parseInt(ranges[0], 0); -064 end = ACFUtil.parseInt(ranges[1], 0); -065 } -066 return IntStream.rangeClosed(start, end).mapToObj(Integer::toString).collect(Collectors.toList()); -067 }); -068 List<String> timeunits = Arrays.asList("minutes", "hours", "days", "weeks", "months", "years"); -069 registerAsyncCompletion("timeunits", (c) -> timeunits); +050 registerStaticCompletion("empty", Collections.emptyList()); +051 registerStaticCompletion("nothing", Collections.emptyList()); +052 registerStaticCompletion("timeunits", Arrays.asList("minutes", "hours", "days", "weeks", "months", "years")); +053 registerAsyncCompletion("range", (c) -> { +054 String config = c.getConfig(); +055 if (config == null) { +056 return Collections.emptyList(); +057 } +058 final String[] ranges = ACFPatterns.DASH.split(config); +059 int start; +060 int end; +061 if (ranges.length != 2) { +062 start = 0; +063 end = ACFUtil.parseInt(ranges[0], 0); +064 } else { +065 start = ACFUtil.parseInt(ranges[0], 0); +066 end = ACFUtil.parseInt(ranges[1], 0); +067 } +068 return IntStream.rangeClosed(start, end).mapToObj(Integer::toString).collect(Collectors.toList()); +069 }); 070 } 071 072 /** 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 616f583a..124cf0d9 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 @@ -55,26 +55,26 @@ 047 048 public CommandCompletions(CommandManager manager) { 049 this.manager = manager; -050 registerAsyncCompletion("nothing", c -> Collections.emptyList()); -051 registerAsyncCompletion("range", (c) -> { -052 String config = c.getConfig(); -053 if (config == null) { -054 return Collections.emptyList(); -055 } -056 final String[] ranges = ACFPatterns.DASH.split(config); -057 int start; -058 int end; -059 if (ranges.length != 2) { -060 start = 0; -061 end = ACFUtil.parseInt(ranges[0], 0); -062 } else { -063 start = ACFUtil.parseInt(ranges[0], 0); -064 end = ACFUtil.parseInt(ranges[1], 0); -065 } -066 return IntStream.rangeClosed(start, end).mapToObj(Integer::toString).collect(Collectors.toList()); -067 }); -068 List<String> timeunits = Arrays.asList("minutes", "hours", "days", "weeks", "months", "years"); -069 registerAsyncCompletion("timeunits", (c) -> timeunits); +050 registerStaticCompletion("empty", Collections.emptyList()); +051 registerStaticCompletion("nothing", Collections.emptyList()); +052 registerStaticCompletion("timeunits", Arrays.asList("minutes", "hours", "days", "weeks", "months", "years")); +053 registerAsyncCompletion("range", (c) -> { +054 String config = c.getConfig(); +055 if (config == null) { +056 return Collections.emptyList(); +057 } +058 final String[] ranges = ACFPatterns.DASH.split(config); +059 int start; +060 int end; +061 if (ranges.length != 2) { +062 start = 0; +063 end = ACFUtil.parseInt(ranges[0], 0); +064 } else { +065 start = ACFUtil.parseInt(ranges[0], 0); +066 end = ACFUtil.parseInt(ranges[1], 0); +067 } +068 return IntStream.rangeClosed(start, end).mapToObj(Integer::toString).collect(Collectors.toList()); +069 }); 070 } 071 072 /** 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 616f583a..124cf0d9 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 @@ -55,26 +55,26 @@ 047 048 public CommandCompletions(CommandManager manager) { 049 this.manager = manager; -050 registerAsyncCompletion("nothing", c -> Collections.emptyList()); -051 registerAsyncCompletion("range", (c) -> { -052 String config = c.getConfig(); -053 if (config == null) { -054 return Collections.emptyList(); -055 } -056 final String[] ranges = ACFPatterns.DASH.split(config); -057 int start; -058 int end; -059 if (ranges.length != 2) { -060 start = 0; -061 end = ACFUtil.parseInt(ranges[0], 0); -062 } else { -063 start = ACFUtil.parseInt(ranges[0], 0); -064 end = ACFUtil.parseInt(ranges[1], 0); -065 } -066 return IntStream.rangeClosed(start, end).mapToObj(Integer::toString).collect(Collectors.toList()); -067 }); -068 List<String> timeunits = Arrays.asList("minutes", "hours", "days", "weeks", "months", "years"); -069 registerAsyncCompletion("timeunits", (c) -> timeunits); +050 registerStaticCompletion("empty", Collections.emptyList()); +051 registerStaticCompletion("nothing", Collections.emptyList()); +052 registerStaticCompletion("timeunits", Arrays.asList("minutes", "hours", "days", "weeks", "months", "years")); +053 registerAsyncCompletion("range", (c) -> { +054 String config = c.getConfig(); +055 if (config == null) { +056 return Collections.emptyList(); +057 } +058 final String[] ranges = ACFPatterns.DASH.split(config); +059 int start; +060 int end; +061 if (ranges.length != 2) { +062 start = 0; +063 end = ACFUtil.parseInt(ranges[0], 0); +064 } else { +065 start = ACFUtil.parseInt(ranges[0], 0); +066 end = ACFUtil.parseInt(ranges[1], 0); +067 } +068 return IntStream.rangeClosed(start, end).mapToObj(Integer::toString).collect(Collectors.toList()); +069 }); 070 } 071 072 /** 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 616f583a..124cf0d9 100644 --- a/docs/acf-core/src-html/co/aikar/commands/CommandCompletions.html +++ b/docs/acf-core/src-html/co/aikar/commands/CommandCompletions.html @@ -55,26 +55,26 @@ 047 048 public CommandCompletions(CommandManager manager) { 049 this.manager = manager; -050 registerAsyncCompletion("nothing", c -> Collections.emptyList()); -051 registerAsyncCompletion("range", (c) -> { -052 String config = c.getConfig(); -053 if (config == null) { -054 return Collections.emptyList(); -055 } -056 final String[] ranges = ACFPatterns.DASH.split(config); -057 int start; -058 int end; -059 if (ranges.length != 2) { -060 start = 0; -061 end = ACFUtil.parseInt(ranges[0], 0); -062 } else { -063 start = ACFUtil.parseInt(ranges[0], 0); -064 end = ACFUtil.parseInt(ranges[1], 0); -065 } -066 return IntStream.rangeClosed(start, end).mapToObj(Integer::toString).collect(Collectors.toList()); -067 }); -068 List<String> timeunits = Arrays.asList("minutes", "hours", "days", "weeks", "months", "years"); -069 registerAsyncCompletion("timeunits", (c) -> timeunits); +050 registerStaticCompletion("empty", Collections.emptyList()); +051 registerStaticCompletion("nothing", Collections.emptyList()); +052 registerStaticCompletion("timeunits", Arrays.asList("minutes", "hours", "days", "weeks", "months", "years")); +053 registerAsyncCompletion("range", (c) -> { +054 String config = c.getConfig(); +055 if (config == null) { +056 return Collections.emptyList(); +057 } +058 final String[] ranges = ACFPatterns.DASH.split(config); +059 int start; +060 int end; +061 if (ranges.length != 2) { +062 start = 0; +063 end = ACFUtil.parseInt(ranges[0], 0); +064 } else { +065 start = ACFUtil.parseInt(ranges[0], 0); +066 end = ACFUtil.parseInt(ranges[1], 0); +067 } +068 return IntStream.rangeClosed(start, end).mapToObj(Integer::toString).collect(Collectors.toList()); +069 }); 070 } 071 072 /** diff --git a/docs/acf-core/src-html/co/aikar/commands/CommandHelpFormatter.html b/docs/acf-core/src-html/co/aikar/commands/CommandHelpFormatter.html index 2f21344c..aac51610 100644 --- a/docs/acf-core/src-html/co/aikar/commands/CommandHelpFormatter.html +++ b/docs/acf-core/src-html/co/aikar/commands/CommandHelpFormatter.html @@ -201,14 +201,15 @@ 193 //{name} {description} 194 return new String[]{ 195 "{name}", param.getName(), -196 "{syntax}", ACFUtil.nullDefault(param.getSyntax(), ""), -197 "{description}", ACFUtil.nullDefault(param.getDescription(), ""), -198 "{command}", help.getCommandName(), -199 "{fullcommand}", entry.getCommand(), -200 "{commandprefix}", help.getCommandPrefix() -201 }; -202 } -203} +196 "{syntaxorname}", ACFUtil.nullDefault(param.getSyntax(), param.getName()), +197 "{syntax}", ACFUtil.nullDefault(param.getSyntax(), ""), +198 "{description}", ACFUtil.nullDefault(param.getDescription(), ""), +199 "{command}", help.getCommandName(), +200 "{fullcommand}", entry.getCommand(), +201 "{commandprefix}", help.getCommandPrefix() +202 }; +203 } +204} 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 c4eca736..7d208160 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 @@ -41,7 +41,7 @@ 033 * This is used in the help menus. 034 */ 035@Retention(RetentionPolicy.RUNTIME) -036@Target({ElementType.METHOD, ElementType.PARAMETER}) +036@Target({ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE}) 037public @interface Description { 038 String value(); 039}