001/* 002 * Copyright (c) 2016-2017 Daniel Ennis (Aikar) - MIT License 003 * 004 * Permission is hereby granted, free of charge, to any person obtaining 005 * a copy of this software and associated documentation files (the 006 * "Software"), to deal in the Software without restriction, including 007 * without limitation the rights to use, copy, modify, merge, publish, 008 * distribute, sublicense, and/or sell copies of the Software, and to 009 * permit persons to whom the Software is furnished to do so, subject to 010 * the following conditions: 011 * 012 * The above copyright notice and this permission notice shall be 013 * included in all copies or substantial portions of the Software. 014 * 015 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 016 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 017 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 018 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 019 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 020 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 021 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 022 */ 023 024package co.aikar.commands; 025 026import co.aikar.commands.annotation.CatchAll; 027import co.aikar.commands.annotation.CatchUnknown; 028import co.aikar.commands.annotation.CommandAlias; 029import co.aikar.commands.annotation.CommandPermission; 030import co.aikar.commands.annotation.Conditions; 031import co.aikar.commands.annotation.Default; 032import co.aikar.commands.annotation.HelpCommand; 033import co.aikar.commands.annotation.PreCommand; 034import co.aikar.commands.annotation.Subcommand; 035import co.aikar.commands.annotation.UnknownHandler; 036import co.aikar.commands.apachecommonslang.ApacheCommonsLangUtil; 037import com.google.common.collect.HashMultimap; 038import com.google.common.collect.SetMultimap; 039import org.jetbrains.annotations.Nullable; 040 041import java.lang.reflect.Constructor; 042import java.lang.reflect.InvocationTargetException; 043import java.lang.reflect.Method; 044import java.lang.reflect.Parameter; 045import java.util.ArrayList; 046import java.util.Arrays; 047import java.util.Collections; 048import java.util.HashMap; 049import java.util.HashSet; 050import java.util.List; 051import java.util.Map; 052import java.util.Objects; 053import java.util.Optional; 054import java.util.Set; 055import java.util.Stack; 056import java.util.stream.Collectors; 057import java.util.stream.Stream; 058 059/** 060 * A Base command is defined as a command group of related commands. 061 * A BaseCommand does not imply nor enforce that they use the same root command. 062 * <p> 063 * It is up to the end user how to organize their command. you could use 1 base command per 064 * command in your application. 065 * <p> 066 * Optionally (and encouraged), you can use the base command to represent a root command, and 067 * then each actionable command is a sub command 068 */ 069 070@SuppressWarnings("unused") 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 foundDefault = false; 314 boolean foundCatchUnknown = false; 315 boolean isParentEmpty = parentSubcommand == null || parentSubcommand.isEmpty(); 316 317 for (Method method : this.getClass().getMethods()) { 318 method.setAccessible(true); 319 String sublist = null; 320 String sub = getSubcommandValue(method); 321 final boolean def = annotations.hasAnnotation(method, Default.class); 322 final String helpCommand = annotations.getAnnotationValue(method, HelpCommand.class, Annotations.NOTHING); 323 final String commandAliases = annotations.getAnnotationValue(method, CommandAlias.class, Annotations.NOTHING); 324 325 if (!isParentEmpty && def) { 326 sub = parentSubcommand; 327 } 328 if (isParentEmpty && (def || (!foundDefault && helpCommand != null))) { 329 if (!foundDefault) { 330 if (def) { 331 this.subCommands.get(DEFAULT).clear(); 332 foundDefault = true; 333 } 334 registerSubcommand(method, DEFAULT); 335 } else { 336 ACFUtil.sneaky(new IllegalStateException("Multiple @Default/@HelpCommand commands, duplicate on " + method.getDeclaringClass().getName() + "#" + method.getName())); 337 } 338 } 339 340 if (sub != null) { 341 sublist = sub; 342 } else if (commandAliases != null) { 343 sublist = commandAliases; 344 } else if (helpCommand != null) { 345 sublist = helpCommand; 346 hasHelpCommand = true; 347 } 348 349 boolean preCommand = annotations.hasAnnotation(method, PreCommand.class); 350 boolean hasCatchUnknown = annotations.hasAnnotation(method, CatchUnknown.class) || 351 annotations.hasAnnotation(method, CatchAll.class) || 352 annotations.hasAnnotation(method, UnknownHandler.class); 353 354 if (hasCatchUnknown || (!foundCatchUnknown && helpCommand != null)) { 355 if (!foundCatchUnknown) { 356 if (hasCatchUnknown) { 357 this.subCommands.get(CATCHUNKNOWN).clear(); 358 foundCatchUnknown = true; 359 } 360 registerSubcommand(method, CATCHUNKNOWN); 361 } else { 362 ACFUtil.sneaky(new IllegalStateException("Multiple @UnknownHandler/@HelpCommand commands, duplicate on " + method.getDeclaringClass().getName() + "#" + method.getName())); 363 } 364 } else if (preCommand) { 365 if (this.preCommandHandler == null) { 366 this.preCommandHandler = method; 367 } else { 368 ACFUtil.sneaky(new IllegalStateException("Multiple @PreCommand commands, duplicate on " + method.getDeclaringClass().getName() + "#" + method.getName())); 369 } 370 } 371 if (Objects.equals(method.getDeclaringClass(), this.getClass()) && sublist != null) { 372 registerSubcommand(method, sublist); 373 } 374 } 375 } 376 377 /** 378 * This registers all the permissions required to execute this command. 379 */ 380 private void computePermissions() { 381 this.permissions.clear(); 382 if (this.permission != null && !this.permission.isEmpty()) { 383 this.permissions.addAll(Arrays.asList(ACFPatterns.COMMA.split(this.permission))); 384 } 385 if (this.parentCommand != null) { 386 this.permissions.addAll(this.parentCommand.getRequiredPermissions()); 387 } 388 } 389 390 /** 391 * Gets the subcommand name of the method given. 392 * 393 * @param method The method to check. 394 * @return The name of the subcommand. It returns null if the input doesn't have {@link Subcommand} attached. 395 */ 396 private String getSubcommandValue(Method method) { 397 final String sub = manager.getAnnotations().getAnnotationValue(method, Subcommand.class, Annotations.NOTHING); 398 if (sub == null) { 399 return null; 400 } 401 Class<?> clazz = method.getDeclaringClass(); 402 String parent = getParentSubcommand(clazz); 403 return parent == null || parent.isEmpty() ? sub : parent + " " + sub; 404 } 405 406 private String getParentSubcommand(Class<?> clazz) { 407 List<String> subList = new ArrayList<>(); 408 while (clazz != null) { 409 String sub = manager.getAnnotations().getAnnotationValue(clazz, Subcommand.class, Annotations.NOTHING); 410 if (sub != null) { 411 subList.add(sub); 412 } 413 clazz = clazz.getEnclosingClass(); 414 } 415 Collections.reverse(subList); 416 return ACFUtil.join(subList, " "); 417 } 418 419 /** 420 * Registers the given {@link BaseCommand cmd} as a child of the {@link RootCommand} linked to the name given. 421 * 422 * @param name Name of the parent to cmd. 423 * @param cmd The {@link BaseCommand} to add as a child to the {@link RootCommand} owned name field. 424 */ 425 private void register(String name, BaseCommand cmd) { 426 String nameLower = name.toLowerCase(); 427 RootCommand rootCommand = manager.obtainRootCommand(nameLower); 428 rootCommand.addChild(cmd); 429 430 this.registeredCommands.put(nameLower, rootCommand); 431 } 432 433 /** 434 * Registers the given {@link Method} as a subcommand. 435 * 436 * @param method The method to register as a subcommand. 437 * @param subCommand The subcommand's name(s). 438 */ 439 private void registerSubcommand(Method method, String subCommand) { 440 subCommand = manager.getCommandReplacements().replace(subCommand.toLowerCase()); 441 final String[] subCommandParts = ACFPatterns.SPACE.split(subCommand); 442 // Must run getSubcommandPossibility BEFORE we rewrite it just after this. 443 Set<String> cmdList = getSubCommandPossibilityList(subCommandParts); 444 445 // Strip pipes off for auto complete addition 446 for (int i = 0; i < subCommandParts.length; i++) { 447 String[] split = ACFPatterns.PIPE.split(subCommandParts[i]); 448 if (split.length == 0 || split[0].isEmpty()) { 449 throw new IllegalArgumentException("Invalid @Subcommand configuration for " + method.getName() + " - parts can not start with | or be empty"); 450 } 451 subCommandParts[i] = split[0]; 452 } 453 String prefSubCommand = ApacheCommonsLangUtil.join(subCommandParts, " "); 454 final String[] aliasNames = manager.getAnnotations().getAnnotationValues(method, CommandAlias.class, Annotations.REPLACEMENTS | Annotations.LOWERCASE); 455 456 String cmdName = aliasNames != null ? aliasNames[0] : this.commandName + " "; 457 RegisteredCommand cmd = manager.createRegisteredCommand(this, cmdName, method, prefSubCommand); 458 459 for (String subcmd : cmdList) { 460 subCommands.put(subcmd, cmd); 461 } 462 cmd.addSubcommands(cmdList); 463 464 if (aliasNames != null) { 465 for (String name : aliasNames) { 466 register(name, new ForwardingCommand(this, cmd, subCommandParts)); 467 } 468 } 469 } 470 471 /** 472 * Takes a string like "foo|bar baz|qux" and generates a list of 473 * - foo baz 474 * - foo qux 475 * - bar baz 476 * - bar qux 477 * <p> 478 * For every possible sub command combination 479 * 480 * @param subCommandParts 481 * @return List of all sub command possibilities 482 */ 483 private static Set<String> getSubCommandPossibilityList(String[] subCommandParts) { 484 int i = 0; 485 Set<String> current = null; 486 while (true) { 487 Set<String> newList = new HashSet<>(); 488 489 if (i < subCommandParts.length) { 490 for (String s1 : ACFPatterns.PIPE.split(subCommandParts[i])) { 491 if (current != null) { 492 newList.addAll(current.stream().map(s -> s + " " + s1).collect(Collectors.toList())); 493 } else { 494 newList.add(s1); 495 } 496 } 497 } 498 499 if (i + 1 < subCommandParts.length) { 500 current = newList; 501 i = i + 1; 502 continue; 503 } 504 505 return newList; 506 } 507 } 508 509 public void execute(CommandIssuer issuer, String commandLabel, String[] args) { 510 commandLabel = commandLabel.toLowerCase(); 511 try { 512 CommandOperationContext commandContext = preCommandOperation(issuer, commandLabel, args, false); 513 514 if (args.length > 0) { 515 CommandSearch cmd = findSubCommand(args); 516 if (cmd != null) { 517 execSubcommand = cmd.getCheckSub(); 518 final String[] execargs = Arrays.copyOfRange(args, cmd.argIndex, args.length); 519 executeCommand(commandContext, issuer, execargs, cmd.cmd); 520 return; 521 } 522 } 523 524 Set<RegisteredCommand> defaultCommands = subCommands.get(DEFAULT); 525 RegisteredCommand defCommand = ACFUtil.getFirstElement(defaultCommands); 526 if (defCommand != null && (args.length == 0 || defCommand.consumeInputResolvers > 0)) { 527 findAndExecuteCommand(commandContext, DEFAULT, issuer, args); 528 } else if (subCommands.get(CATCHUNKNOWN) != null) { 529 if (!findAndExecuteCommand(commandContext, CATCHUNKNOWN, issuer, args)) { 530 help(issuer, args); 531 } 532 } 533 534 } finally { 535 postCommandOperation(); 536 } 537 } 538 539 /** 540 * Gets the registered command of the given arguments. 541 * 542 * @param args The arguments given by the user. 543 * @return The subcommand or null if none were found. 544 * @see #findSubCommand(String[]) 545 */ 546 RegisteredCommand<?> getRegisteredCommand(String[] args) { 547 final CommandSearch cmd = findSubCommand(args); 548 return cmd != null ? cmd.cmd : null; 549 } 550 551 /** 552 * This is ran after any command operation has been performed. 553 */ 554 private void postCommandOperation() { 555 CommandManager.commandOperationContext.get().pop(); 556 execSubcommand = null; 557 execLabel = null; 558 origArgs = new String[]{}; 559 } 560 561 /** 562 * This is ran before any command operation has been performed. 563 * 564 * @param issuer The user who executed the command. 565 * @param commandLabel The label the user used to execute the command. This is not the command name, but their input. 566 * When there is multiple aliases, this is which alias was used 567 * @param args The arguments passed to the command when executing it. 568 * @param isAsync Whether the command is executed off of the main thread. 569 * @return The context which is being registered to the {@link CommandManager}'s {@link 570 * CommandManager#commandOperationContext thread local stack}. 571 */ 572 private CommandOperationContext preCommandOperation(CommandIssuer issuer, String commandLabel, String[] args, boolean isAsync) { 573 Stack<CommandOperationContext> contexts = CommandManager.commandOperationContext.get(); 574 CommandOperationContext context = this.manager.createCommandOperationContext(this, issuer, commandLabel, args, isAsync); 575 contexts.push(context); 576 lastCommandOperationContext.set(context); 577 execSubcommand = null; 578 execLabel = commandLabel; 579 origArgs = args; 580 return context; 581 } 582 583 /** 584 * Gets the current command issuer. 585 * 586 * @return The current command issuer. 587 */ 588 public CommandIssuer getCurrentCommandIssuer() { 589 return CommandManager.getCurrentCommandIssuer(); 590 } 591 592 /** 593 * Gets the current command manager. 594 * 595 * @return The current command manager. 596 */ 597 public CommandManager getCurrentCommandManager() { 598 return CommandManager.getCurrentCommandManager(); 599 } 600 601 /** 602 * Finds a subcommand of the given arguments. 603 * 604 * @param args The arguments the user input. 605 * @return The identified subcommand. 606 * @see #findSubCommand(String[], boolean) 607 */ 608 private CommandSearch findSubCommand(String[] args) { 609 return findSubCommand(args, false); 610 } 611 612 /** 613 * Finds a subcommand of the given arguments. 614 * 615 * @param args The arguments the user input. 616 * @param completion Whether or not completion of arguments should kick in. This may end up with worse than wanted results. 617 * @return The identified subcommand. 618 */ 619 private CommandSearch findSubCommand(String[] args, boolean completion) { 620 for (int i = args.length; i >= 0; i--) { 621 String checkSub = ApacheCommonsLangUtil.join(args, " ", 0, i).toLowerCase(); 622 Set<RegisteredCommand> cmds = subCommands.get(checkSub); 623 624 final int extraArgs = args.length - i; 625 if (!cmds.isEmpty()) { 626 RegisteredCommand cmd = null; 627 if (cmds.size() == 1) { 628 cmd = ACFUtil.getFirstElement(cmds); 629 } else { 630 Optional<RegisteredCommand> optCmd = cmds.stream().filter(c -> { 631 int required = c.requiredResolvers; 632 int optional = c.optionalResolvers; 633 return extraArgs <= required + optional && (completion || extraArgs >= required); 634 }).min((c1, c2) -> { 635 int a = c1.consumeInputResolvers; 636 int b = c2.consumeInputResolvers; 637 638 if (a == b) { 639 return 0; 640 } 641 return a < b ? 1 : -1; 642 }); 643 if (optCmd.isPresent()) { 644 cmd = optCmd.get(); 645 } 646 } 647 if (cmd != null) { 648 return new CommandSearch(cmd, i, checkSub); 649 } 650 } 651 } 652 return null; 653 } 654 655 private void executeCommand(CommandOperationContext commandOperationContext, 656 CommandIssuer issuer, String[] args, RegisteredCommand cmd) { 657 if (cmd.hasPermission(issuer)) { 658 commandOperationContext.setRegisteredCommand(cmd); 659 if (checkPrecommand(commandOperationContext, cmd, issuer, args)) { 660 return; 661 } 662 List<String> sargs = Arrays.asList(args); 663 cmd.invoke(issuer, sargs, commandOperationContext); 664 } else { 665 issuer.sendMessage(MessageType.ERROR, MessageKeys.PERMISSION_DENIED); 666 } 667 } 668 669 /** 670 * Please use command conditions for restricting execution 671 * 672 * @param issuer 673 * @param cmd 674 * @return 675 * @deprecated See {@link CommandConditions} 676 */ 677 @SuppressWarnings("DeprecatedIsStillUsed") 678 @Deprecated 679 public boolean canExecute(CommandIssuer issuer, RegisteredCommand<?> cmd) { 680 return true; 681 } 682 683 /** 684 * Gets tab completed data from the given command from the user. 685 * 686 * @param issuer The user who executed the tabcomplete. 687 * @param commandLabel The label which is being used by the user. 688 * @param args The arguments the user has typed so far. 689 * @return All possibilities in the tab complete. 690 */ 691 public List<String> tabComplete(CommandIssuer issuer, String commandLabel, String[] args) { 692 return tabComplete(issuer, commandLabel, args, false); 693 } 694 695 /** 696 * Gets the tab complete suggestions from a given command. This will automatically find anything 697 * which is valid for the specified command through the command's implementation. 698 * 699 * @param issuer The issuer of the command. 700 * @param commandLabel The command name as entered by the user instead of the ACF registered name. 701 * @param args All arguments entered by the user. 702 * @param isAsync Whether this is run off of the main thread. 703 * @return The possibilities to tab complete in no particular order. 704 */ 705 @SuppressWarnings("WeakerAccess") 706 public List<String> tabComplete(CommandIssuer issuer, String commandLabel, String[] args, boolean isAsync) 707 throws IllegalArgumentException { 708 709 commandLabel = commandLabel.toLowerCase(); 710 if (args.length == 0) { 711 args = new String[]{""}; 712 } 713 try { 714 CommandOperationContext commandOperationContext = preCommandOperation(issuer, commandLabel, args, isAsync); 715 716 final CommandSearch search = findSubCommand(args, true); 717 718 719 final List<String> cmds = new ArrayList<>(); 720 721 if (search != null) { 722 cmds.addAll(completeCommand(issuer, search.cmd, Arrays.copyOfRange(args, search.argIndex, args.length), commandLabel, isAsync)); 723 } else if (subCommands.get(CATCHUNKNOWN).size() == 1) { 724 cmds.addAll(completeCommand(issuer, ACFUtil.getFirstElement(subCommands.get(CATCHUNKNOWN)), args, commandLabel, isAsync)); 725 } else if (subCommands.get(DEFAULT).size() == 1) { 726 cmds.addAll(completeCommand(issuer, ACFUtil.getFirstElement(subCommands.get(DEFAULT)), args, commandLabel, isAsync)); 727 } 728 729 return filterTabComplete(args[args.length - 1], cmds); 730 } finally { 731 postCommandOperation(); 732 } 733 } 734 735 /** 736 * Gets all subcommands which are possible to tabcomplete. 737 * 738 * @param issuer The command issuer. 739 * @param args 740 * @return 741 */ 742 List<String> getCommandsForCompletion(CommandIssuer issuer, String[] args) { 743 final Set<String> cmds = new HashSet<>(); 744 final int cmdIndex = Math.max(0, args.length - 1); 745 String argString = ApacheCommonsLangUtil.join(args, " ").toLowerCase(); 746 for (Map.Entry<String, RegisteredCommand> entry : subCommands.entries()) { 747 final String key = entry.getKey(); 748 if (key.startsWith(argString) && !CATCHUNKNOWN.equals(key) && !DEFAULT.equals(key)) { 749 final RegisteredCommand value = entry.getValue(); 750 if (!value.hasPermission(issuer) || value.isPrivate) { 751 continue; 752 } 753 754 String[] split = ACFPatterns.SPACE.split(value.prefSubCommand); 755 cmds.add(split[cmdIndex]); 756 } 757 } 758 return new ArrayList<>(cmds); 759 } 760 761 /** 762 * Complete a command properly per issuer and input. 763 * 764 * @param issuer The user who executed this. 765 * @param cmd The command to be completed. 766 * @param args All arguments given by the user. 767 * @param commandLabel The command name the user used. 768 * @param isAsync Whether the command was executed async. 769 * @return All results to complete the command. 770 */ 771 private List<String> completeCommand(CommandIssuer issuer, RegisteredCommand cmd, String[] args, String commandLabel, boolean isAsync) { 772 if (!cmd.hasPermission(issuer) || args.length > cmd.consumeInputResolvers || args.length == 0 || cmd.complete == null) { 773 return Collections.emptyList(); 774 } 775 776 List<String> cmds = manager.getCommandCompletions().of(cmd, issuer, args, isAsync); 777 return filterTabComplete(args[args.length - 1], cmds); 778 } 779 780 /** 781 * Gets the actual args in string form the user typed 782 * This returns a list of all tab complete options which are possible with the given argument and commands. 783 * 784 * @param arg Argument which was pressed tab on. 785 * @param cmds The possibilities to return. 786 * @return All possible options. This may be empty. 787 */ 788 private static List<String> filterTabComplete(String arg, List<String> cmds) { 789 return cmds.stream() 790 .distinct() 791 .filter(cmd -> cmd != null && (arg.isEmpty() || ApacheCommonsLangUtil.startsWithIgnoreCase(cmd, arg))) 792 .collect(Collectors.toList()); 793 } 794 795 /** 796 * Gets a registered command under the given subcommand name. 797 * 798 * @param subcommand The name of the subcommand requested. 799 * @return The subcommand found or null if none. 800 */ 801 private RegisteredCommand getCommandBySubcommand(String subcommand) { 802 return getCommandBySubcommand(subcommand, false); 803 } 804 805 /** 806 * Gets a registered command under the given name. 807 * If requireOne is true, it won't accept more than a single matching subcommand. 808 * 809 * @param subcommand Name of the subcommand wanted. 810 * @param requireOne Whether to only accept 1 result. 811 * @return The subcommand found, or null if none/too many. 812 */ 813 private RegisteredCommand getCommandBySubcommand(String subcommand, boolean requireOne) { 814 final Set<RegisteredCommand> commands = subCommands.get(subcommand); 815 if (!commands.isEmpty() && (!requireOne || commands.size() == 1)) { 816 return commands.iterator().next(); 817 } 818 return null; 819 } 820 821 /** 822 * Internally calls {@link #executeCommand(CommandOperationContext, CommandIssuer, String[], RegisteredCommand)} 823 * and gets through {@link #getCommandBySubcommand(String)}. 824 * 825 * @param commandContext The command context to use. 826 * @param subcommand The subcommand to find the executor of. 827 * @param issuer The issuer who executed the subcommand. 828 * @param args All arguments given by the issuer. 829 * @return Whether it found a command or not. 830 * @see #executeCommand(CommandOperationContext, CommandIssuer, String[], RegisteredCommand) 831 * @see #getCommandBySubcommand(String) 832 * @see RegisteredCommand#invoke(CommandIssuer, List, CommandOperationContext) 833 */ 834 private boolean findAndExecuteCommand(CommandOperationContext commandContext, String subcommand, CommandIssuer issuer, String... args) { 835 final RegisteredCommand cmd = this.getCommandBySubcommand(subcommand); 836 if (cmd != null) { 837 executeCommand(commandContext, issuer, args, cmd); 838 return true; 839 } 840 841 return false; 842 } 843 844 /** 845 * Executes the precommand and sees whether something is wrong. Ideally, you get false from this. 846 * 847 * @param commandOperationContext The context to use. 848 * @param cmd The command executed. 849 * @param issuer The issuer who executed the command. 850 * @param args The arguments the issuer provided. 851 * @return Whether something went wrong. 852 */ 853 private boolean checkPrecommand(CommandOperationContext commandOperationContext, RegisteredCommand cmd, CommandIssuer issuer, String[] args) { 854 Method pre = this.preCommandHandler; 855 if (pre != null) { 856 try { 857 Class<?>[] types = pre.getParameterTypes(); 858 Object[] parameters = new Object[pre.getParameterCount()]; 859 for (int i = 0; i < parameters.length; i++) { 860 Class<?> type = types[i]; 861 Object issuerObject = issuer.getIssuer(); 862 if (manager.isCommandIssuer(type) && type.isAssignableFrom(issuerObject.getClass())) { 863 parameters[i] = issuerObject; 864 } else if (CommandIssuer.class.isAssignableFrom(type)) { 865 parameters[i] = issuer; 866 } else if (RegisteredCommand.class.isAssignableFrom(type)) { 867 parameters[i] = cmd; 868 } else if (String[].class.isAssignableFrom((type))) { 869 parameters[i] = args; 870 } else { 871 parameters[i] = null; 872 } 873 } 874 875 return (boolean) pre.invoke(this, parameters); 876 } catch (IllegalAccessException | InvocationTargetException e) { 877 this.manager.log(LogLevel.ERROR, "Exception encountered while command pre-processing", e); 878 } 879 } 880 return false; 881 } 882 883 /** 884 * @deprecated Unstable API 885 */ 886 @Deprecated 887 @UnstableAPI 888 public CommandHelp getCommandHelp() { 889 return manager.generateCommandHelp(); 890 } 891 892 /** 893 * @deprecated Unstable API 894 */ 895 @Deprecated 896 @UnstableAPI 897 public void showCommandHelp() { 898 getCommandHelp().showHelp(); 899 } 900 901 public void help(Object issuer, String[] args) { 902 help(manager.getCommandIssuer(issuer), args); 903 } 904 905 public void help(CommandIssuer issuer, String[] args) { 906 issuer.sendMessage(MessageType.ERROR, MessageKeys.UNKNOWN_COMMAND); 907 } 908 909 public void doHelp(Object issuer, String... args) { 910 doHelp(manager.getCommandIssuer(issuer), args); 911 } 912 913 public void doHelp(CommandIssuer issuer, String... args) { 914 help(issuer, args); 915 } 916 917 public void showSyntax(CommandIssuer issuer, RegisteredCommand<?> cmd) { 918 issuer.sendMessage(MessageType.SYNTAX, MessageKeys.INVALID_SYNTAX, 919 "{command}", manager.getCommandPrefix(issuer) + cmd.command, 920 "{syntax}", cmd.syntaxText 921 ); 922 } 923 924 public boolean hasPermission(Object issuer) { 925 return hasPermission(manager.getCommandIssuer(issuer)); 926 } 927 928 public boolean hasPermission(CommandIssuer issuer) { 929 return getRequiredPermissions().isEmpty() || getRequiredPermissions().stream().allMatch(permission -> manager.hasPermission(issuer, permission)); 930 } 931 932 public Set<String> getRequiredPermissions() { 933 return this.permissions; 934 } 935 936 public boolean requiresPermission(String permission) { 937 return getRequiredPermissions().contains(permission); 938 } 939 940 public String getName() { 941 return commandName; 942 } 943 944 public ExceptionHandler getExceptionHandler() { 945 return exceptionHandler; 946 } 947 948 public BaseCommand setExceptionHandler(ExceptionHandler exceptionHandler) { 949 this.exceptionHandler = exceptionHandler; 950 return this; 951 } 952 953 public RegisteredCommand getDefaultRegisteredCommand() { 954 return this.getCommandBySubcommand(DEFAULT); 955 } 956 957 public String setContextFlags(Class<?> cls, String flags) { 958 return this.contextFlags.put(cls, flags); 959 } 960 961 public String getContextFlags(Class<?> cls) { 962 return this.contextFlags.get(cls); 963 } 964 965 public List<RegisteredCommand> getRegisteredCommands() { 966 List<RegisteredCommand> registeredCommands = new ArrayList<>(); 967 registeredCommands.addAll(this.subCommands.values()); 968 return registeredCommands; 969 } 970 971 private static class CommandSearch { 972 RegisteredCommand cmd; 973 int argIndex; 974 String checkSub; 975 976 CommandSearch(RegisteredCommand cmd, int argIndex, String checkSub) { 977 this.cmd = cmd; 978 this.argIndex = argIndex; 979 this.checkSub = checkSub; 980 } 981 982 String getCheckSub() { 983 return this.checkSub; 984 } 985 986 @Override 987 public boolean equals(Object o) { 988 if (this == o) return true; 989 if (o == null || getClass() != o.getClass()) return false; 990 CommandSearch that = (CommandSearch) o; 991 return argIndex == that.argIndex && 992 Objects.equals(cmd, that.cmd) && 993 Objects.equals(checkSub, that.checkSub); 994 } 995 996 @Override 997 public int hashCode() { 998 return Objects.hash(cmd, argIndex, checkSub); 999 } 1000 } 1001}