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.CommandRouter.RouteSearch; 027import co.aikar.commands.annotation.CatchAll; 028import co.aikar.commands.annotation.CatchUnknown; 029import co.aikar.commands.annotation.CommandAlias; 030import co.aikar.commands.annotation.CommandPermission; 031import co.aikar.commands.annotation.Conditions; 032import co.aikar.commands.annotation.Default; 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.Locale; 055import java.util.Map; 056import java.util.Objects; 057import java.util.Set; 058import java.util.Stack; 059import java.util.stream.Collectors; 060import java.util.stream.Stream; 061 062/** 063 * A Base command is defined as a command group of related commands. 064 * A BaseCommand does not imply nor enforce that they use the same root command. 065 * <p> 066 * It is up to the end user how to organize their command. you could use 1 base command per 067 * command in your application. 068 * <p> 069 * Optionally (and encouraged), you can use the base command to represent a root command, and 070 * then each actionable command is a sub command 071 */ 072 073public abstract class BaseCommand { 074 075 /** 076 * This is a field which contains the magic key in the {@link #subCommands} map for the method to catch any unknown 077 * argument to command states. 078 */ 079 static final String CATCHUNKNOWN = "__catchunknown"; 080 /** 081 * This is a field which contains the magic key in the {@link #subCommands} map for the method which is default for the 082 * entire base command. 083 */ 084 static final String DEFAULT = "__default"; 085 086 /** 087 * A map of all the registered commands for this base command, keyed to each potential subcommand to access it. 088 */ 089 final SetMultimap<String, RegisteredCommand> subCommands = HashMultimap.create(); 090 final Set<BaseCommand> subScopes = new HashSet<>(); 091 092 /** 093 * A map of flags to pass to Context Resolution for every parameter of the type. This is like an automatic @Flags on each. 094 */ 095 final Map<Class<?>, String> contextFlags = new HashMap<>(); 096 097 /** 098 * What method was annoated with {@link PreCommand} to execute before commands. 099 */ 100 @Nullable 101 private Method preCommandHandler; 102 103 /** 104 * What root command the user actually entered to access the currently executing command 105 */ 106 @SuppressWarnings("WeakerAccess") 107 private String execLabel; 108 /** 109 * What subcommand the user actually entered to access the currently executing command 110 */ 111 @SuppressWarnings("WeakerAccess") 112 private String execSubcommand; 113 /** 114 * What arguments the user actually entered after the root command to access the currently executing command 115 */ 116 @SuppressWarnings("WeakerAccess") 117 private String[] origArgs; 118 119 /** 120 * The manager this is registered to 121 */ 122 CommandManager<?, ?, ?, ?, ?, ?> manager = null; 123 124 /** 125 * The command which owns this. This may be null if there are no owners. 126 */ 127 BaseCommand parentCommand; 128 Map<String, RootCommand> registeredCommands = new HashMap<>(); 129 /** 130 * The description of the command. This may be null if no description has been provided. 131 * Used for help documentation 132 */ 133 @Nullable String description; 134 /** 135 * The name of the command. This may be null if no name has been provided. 136 */ 137 @Nullable String commandName; 138 /** 139 * The permission of the command. This may be null if no permission has been provided. 140 */ 141 @Nullable String permission; 142 /** 143 * The conditions of the command. This may be null if no conditions has been provided. 144 */ 145 @Nullable String conditions; 146 /** 147 * Identifies if the command has an explicit help command annotated with {@link HelpCommand} 148 */ 149 boolean hasHelpCommand; 150 151 /** 152 * The handler of all uncaught exceptions thrown by the user's command implementation. 153 */ 154 private ExceptionHandler exceptionHandler = null; 155 /** 156 * The last operative context data of this command. This may be null if this command is not currently being executed. 157 */ 158 private final ThreadLocal<CommandOperationContext> lastCommandOperationContext = new ThreadLocal<>(); 159 /** 160 * If a parent exists to this command, and it has a Subcommand annotation, prefix all subcommands in this class with this 161 */ 162 @Nullable 163 private String parentSubcommand; 164 165 /** 166 * The permissions of the command. 167 */ 168 private final Set<String> permissions = new HashSet<>(); 169 170 public BaseCommand() { 171 } 172 173 /** 174 * Constructor based defining of commands will be removed in the next version bump. 175 * 176 * @param cmd 177 * @deprecated Please switch to {@link CommandAlias} for defining all root commands. 178 */ 179 @Deprecated 180 public BaseCommand(@Nullable String cmd) { 181 this.commandName = cmd; 182 } 183 184 /** 185 * Returns a reference to the last used CommandOperationContext. 186 * This method is ThreadLocal, in that it can only be used on a thread that has executed a command 187 * 188 * @return 189 */ 190 public CommandOperationContext getLastCommandOperationContext() { 191 return lastCommandOperationContext.get(); 192 } 193 194 /** 195 * Gets the root command name that the user actually typed 196 * 197 * @return Name 198 */ 199 public String getExecCommandLabel() { 200 return execLabel; 201 } 202 203 /** 204 * Gets the actual sub command name the user typed 205 * 206 * @return Name 207 */ 208 public String getExecSubcommand() { 209 return execSubcommand; 210 } 211 212 /** 213 * Gets the actual args in string form the user typed 214 * 215 * @return Args 216 */ 217 public String[] getOrigArgs() { 218 return origArgs; 219 } 220 221 /** 222 * This should be called whenever the command gets registered. 223 * It sets all required fields correctly and injects dependencies. 224 * 225 * @param manager The manager to register as this command's owner and handler. 226 */ 227 void onRegister(CommandManager manager) { 228 onRegister(manager, this.commandName); 229 } 230 231 /** 232 * This should be called whenever the command gets registered. 233 * It sets all required fields correctly and injects dependencies. 234 * 235 * @param manager The manager to register as this command's owner and handler. 236 * @param cmd The command name to use register with. 237 */ 238 private void onRegister(CommandManager manager, String cmd) { 239 manager.injectDependencies(this); 240 this.manager = manager; 241 242 final Annotations annotations = manager.getAnnotations(); 243 final Class<? extends BaseCommand> self = this.getClass(); 244 245 String[] cmdAliases = annotations.getAnnotationValues(self, CommandAlias.class, Annotations.REPLACEMENTS | Annotations.LOWERCASE | Annotations.NO_EMPTY); 246 247 if (cmd == null && cmdAliases != null) { 248 cmd = cmdAliases[0]; 249 } 250 251 this.commandName = cmd != null ? cmd : self.getSimpleName().toLowerCase(Locale.ENGLISH); 252 this.permission = annotations.getAnnotationValue(self, CommandPermission.class, Annotations.REPLACEMENTS); 253 this.description = annotations.getAnnotationValue(self, Description.class, Annotations.NO_EMPTY | Annotations.REPLACEMENTS); 254 this.parentSubcommand = getParentSubcommand(self); 255 this.conditions = annotations.getAnnotationValue(self, Conditions.class, Annotations.REPLACEMENTS | Annotations.NO_EMPTY); 256 257 computePermissions(); // Must be before any subcommands so they can inherit permissions 258 registerSubcommands(); 259 registerSubclasses(cmd); 260 261 if (cmdAliases != null) { 262 Set<String> cmdList = new HashSet<>(); 263 Collections.addAll(cmdList, cmdAliases); 264 cmdList.remove(cmd); 265 for (String cmdAlias : cmdList) { 266 register(cmdAlias, this); 267 } 268 } 269 270 if (cmd != null) { 271 register(cmd, this); 272 } 273 } 274 275 /** 276 * This recursively registers all subclasses of the command as subcommands, if they are of type {@link BaseCommand}. 277 * 278 * @param cmd The command name of this command. 279 */ 280 private void registerSubclasses(String cmd) { 281 for (Class<?> clazz : this.getClass().getDeclaredClasses()) { 282 if (BaseCommand.class.isAssignableFrom(clazz)) { 283 try { 284 BaseCommand subScope = null; 285 Constructor<?>[] declaredConstructors = clazz.getDeclaredConstructors(); 286 for (Constructor<?> declaredConstructor : declaredConstructors) { 287 288 declaredConstructor.setAccessible(true); 289 Parameter[] parameters = declaredConstructor.getParameters(); 290 if (parameters.length == 1) { 291 subScope = (BaseCommand) declaredConstructor.newInstance(this); 292 } else { 293 manager.log(LogLevel.INFO, "Found unusable constructor: " + declaredConstructor.getName() + "(" + Stream.of(parameters).map(p -> p.getType().getSimpleName() + " " + p.getName()).collect(Collectors.joining("<c2>,</c2> ")) + ")"); 294 } 295 } 296 if (subScope != null) { 297 subScope.parentCommand = this; 298 this.subScopes.add(subScope); 299 subScope.onRegister(manager, cmd); 300 this.subCommands.putAll(subScope.subCommands); 301 this.registeredCommands.putAll(subScope.registeredCommands); 302 } else { 303 this.manager.log(LogLevel.ERROR, "Could not find a subcommand ctor for " + clazz.getName()); 304 } 305 } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { 306 this.manager.log(LogLevel.ERROR, "Error registering subclass", e); 307 } 308 } 309 } 310 } 311 312 /** 313 * This registers all subcommands of the command. 314 */ 315 private void registerSubcommands() { 316 final Annotations annotations = manager.getAnnotations(); 317 boolean foundCatchUnknown = false; 318 boolean isParentEmpty = parentSubcommand == null || parentSubcommand.isEmpty(); 319 Set<Method> methods = new LinkedHashSet<>(); 320 Collections.addAll(methods, this.getClass().getDeclaredMethods()); 321 Collections.addAll(methods, this.getClass().getMethods()); 322 323 for (Method method : methods) { 324 method.setAccessible(true); 325 String sublist = null; 326 String sub = getSubcommandValue(method); 327 final String helpCommand = annotations.getAnnotationValue(method, HelpCommand.class, Annotations.NOTHING); 328 final String commandAliases = annotations.getAnnotationValue(method, CommandAlias.class, Annotations.NOTHING); 329 330 if (annotations.hasAnnotation(method, Default.class)) { 331 if (!isParentEmpty) { 332 sub = parentSubcommand; 333 } else { 334 registerSubcommand(method, DEFAULT); 335 } 336 } 337 338 if (sub != null) { 339 sublist = sub; 340 } else if (commandAliases != null) { 341 sublist = commandAliases; 342 } else if (helpCommand != null) { 343 sublist = helpCommand; 344 hasHelpCommand = true; 345 } 346 347 boolean preCommand = annotations.hasAnnotation(method, PreCommand.class); 348 boolean hasCatchUnknown = annotations.hasAnnotation(method, CatchUnknown.class) || 349 annotations.hasAnnotation(method, CatchAll.class) || 350 annotations.hasAnnotation(method, UnknownHandler.class); 351 352 if (hasCatchUnknown || (!foundCatchUnknown && helpCommand != null)) { 353 if (!foundCatchUnknown) { 354 if (hasCatchUnknown) { 355 this.subCommands.get(CATCHUNKNOWN).clear(); 356 foundCatchUnknown = true; 357 } 358 registerSubcommand(method, CATCHUNKNOWN); 359 } else { 360 ACFUtil.sneaky(new IllegalStateException("Multiple @CatchUnknown/@HelpCommand commands, duplicate on " + method.getDeclaringClass().getName() + "#" + method.getName())); 361 } 362 } else if (preCommand) { 363 if (this.preCommandHandler == null) { 364 this.preCommandHandler = method; 365 } else { 366 ACFUtil.sneaky(new IllegalStateException("Multiple @PreCommand commands, duplicate on " + method.getDeclaringClass().getName() + "#" + method.getName())); 367 } 368 } 369 if (Objects.equals(method.getDeclaringClass(), this.getClass()) && sublist != null) { 370 registerSubcommand(method, sublist); 371 } 372 } 373 } 374 375 /** 376 * This registers all the permissions required to execute this command. 377 */ 378 private void computePermissions() { 379 this.permissions.clear(); 380 if (this.permission != null && !this.permission.isEmpty()) { 381 this.permissions.addAll(Arrays.asList(ACFPatterns.COMMA.split(this.permission))); 382 } 383 if (this.parentCommand != null) { 384 this.permissions.addAll(this.parentCommand.getRequiredPermissions()); 385 } 386 this.subCommands.values().forEach(RegisteredCommand::computePermissions); 387 this.subScopes.forEach(BaseCommand::computePermissions); 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(Locale.ENGLISH); 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(Locale.ENGLISH)); 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 void execute(CommandIssuer issuer, CommandRouter.CommandRouteResult command) { 510 try { 511 CommandOperationContext commandContext = preCommandOperation(issuer, command.commandLabel, command.args, false); 512 execSubcommand = command.subcommand; 513 executeCommand(commandContext, issuer, command.args, command.cmd); 514 } finally { 515 postCommandOperation(); 516 } 517 } 518 519 /** 520 * This is ran after any command operation has been performed. 521 */ 522 private void postCommandOperation() { 523 CommandManager.commandOperationContext.get().pop(); 524 lastCommandOperationContext.set(null); 525 execSubcommand = null; 526 execLabel = null; 527 origArgs = new String[]{}; 528 } 529 530 /** 531 * This is ran before any command operation has been performed. 532 * 533 * @param issuer The user who executed the command. 534 * @param commandLabel The label the user used to execute the command. This is not the command name, but their input. 535 * When there is multiple aliases, this is which alias was used 536 * @param args The arguments passed to the command when executing it. 537 * @param isAsync Whether the command is executed off of the main thread. 538 * @return The context which is being registered to the {@link CommandManager}'s {@link 539 * CommandManager#commandOperationContext thread local stack}. 540 */ 541 private CommandOperationContext preCommandOperation(CommandIssuer issuer, String commandLabel, String[] args, boolean isAsync) { 542 Stack<CommandOperationContext> contexts = CommandManager.commandOperationContext.get(); 543 CommandOperationContext context = this.manager.createCommandOperationContext(this, issuer, commandLabel, args, isAsync); 544 contexts.push(context); 545 lastCommandOperationContext.set(context); 546 execSubcommand = null; 547 execLabel = commandLabel; 548 origArgs = args; 549 return context; 550 } 551 552 /** 553 * Gets the current command issuer. 554 * 555 * @return The current command issuer. 556 */ 557 public CommandIssuer getCurrentCommandIssuer() { 558 return CommandManager.getCurrentCommandIssuer(); 559 } 560 561 /** 562 * Gets the current command manager. 563 * 564 * @return The current command manager. 565 */ 566 public CommandManager getCurrentCommandManager() { 567 return CommandManager.getCurrentCommandManager(); 568 } 569 570 private void executeCommand(CommandOperationContext commandOperationContext, 571 CommandIssuer issuer, String[] args, RegisteredCommand cmd) { 572 if (cmd.hasPermission(issuer)) { 573 commandOperationContext.setRegisteredCommand(cmd); 574 if (checkPrecommand(commandOperationContext, cmd, issuer, args)) { 575 return; 576 } 577 List<String> sargs = Arrays.asList(args); 578 cmd.invoke(issuer, sargs, commandOperationContext); 579 } else { 580 issuer.sendMessage(MessageType.ERROR, MessageKeys.PERMISSION_DENIED); 581 } 582 } 583 584 /** 585 * Please use command conditions for restricting execution 586 * 587 * @param issuer 588 * @param cmd 589 * @return 590 * @deprecated See {@link CommandConditions} 591 */ 592 @SuppressWarnings("DeprecatedIsStillUsed") 593 @Deprecated 594 public boolean canExecute(CommandIssuer issuer, RegisteredCommand<?> cmd) { 595 return true; 596 } 597 598 /** 599 * Gets tab completed data from the given command from the user. 600 * 601 * @param issuer The user who executed the tabcomplete. 602 * @param commandLabel The label which is being used by the user. 603 * @param args The arguments the user has typed so far. 604 * @return All possibilities in the tab complete. 605 */ 606 public List<String> tabComplete(CommandIssuer issuer, String commandLabel, String[] args) { 607 return tabComplete(issuer, commandLabel, args, false); 608 } 609 610 /** 611 * Gets the tab complete suggestions from a given command. This will automatically find anything 612 * which is valid for the specified command through the command's implementation. 613 * 614 * @param issuer The issuer of the command. 615 * @param commandLabel The command name as entered by the user instead of the ACF registered name. 616 * @param args All arguments entered by the user. 617 * @param isAsync Whether this is run off of the main thread. 618 * @return The possibilities to tab complete in no particular order. 619 */ 620 @SuppressWarnings("WeakerAccess") 621 public List<String> tabComplete(CommandIssuer issuer, String commandLabel, String[] args, boolean isAsync) 622 throws IllegalArgumentException { 623 return tabComplete(issuer, manager.getRootCommand(commandLabel.toLowerCase(Locale.ENGLISH)), args, isAsync); 624 } 625 626 List<String> tabComplete(CommandIssuer issuer, RootCommand rootCommand, String[] args, boolean isAsync) 627 throws IllegalArgumentException { 628 if (args.length == 0) { 629 args = new String[]{""}; 630 } 631 String commandLabel = rootCommand.getCommandName(); 632 try { 633 CommandRouter router = manager.getRouter(); 634 635 preCommandOperation(issuer, commandLabel, args, isAsync); 636 637 final RouteSearch search = router.routeCommand(rootCommand, commandLabel, args, true); 638 639 final List<String> cmds = new ArrayList<>(); 640 if (search != null) { 641 for (RegisteredCommand<?> command : search.commands) { 642 cmds.addAll(completeCommand(issuer, command, search.args, commandLabel, isAsync)); 643 } 644 } 645 646 return filterTabComplete(args[args.length - 1], cmds); 647 } finally { 648 postCommandOperation(); 649 } 650 } 651 652 /** 653 * Gets all subcommands which are possible to tabcomplete. 654 * 655 * @param issuer The command issuer. 656 * @param args 657 * @return 658 */ 659 List<String> getCommandsForCompletion(CommandIssuer issuer, String[] args) { 660 final Set<String> cmds = new HashSet<>(); 661 final int cmdIndex = Math.max(0, args.length - 1); 662 String argString = ApacheCommonsLangUtil.join(args, " ").toLowerCase(Locale.ENGLISH); 663 for (Map.Entry<String, RegisteredCommand> entry : subCommands.entries()) { 664 final String key = entry.getKey(); 665 if (key.startsWith(argString) && !isSpecialSubcommand(key)) { 666 final RegisteredCommand value = entry.getValue(); 667 if (!value.hasPermission(issuer) || value.isPrivate) { 668 continue; 669 } 670 671 String[] split = ACFPatterns.SPACE.split(value.prefSubCommand); 672 cmds.add(split[cmdIndex]); 673 } 674 } 675 return new ArrayList<>(cmds); 676 } 677 678 static boolean isSpecialSubcommand(String key) { 679 return CATCHUNKNOWN.equals(key) || DEFAULT.equals(key); 680 } 681 682 /** 683 * Complete a command properly per issuer and input. 684 * 685 * @param issuer The user who executed this. 686 * @param cmd The command to be completed. 687 * @param args All arguments given by the user. 688 * @param commandLabel The command name the user used. 689 * @param isAsync Whether the command was executed async. 690 * @return All results to complete the command. 691 */ 692 private List<String> completeCommand(CommandIssuer issuer, RegisteredCommand cmd, String[] args, String commandLabel, boolean isAsync) { 693 if (!cmd.hasPermission(issuer) || args.length == 0 || cmd.parameters.length == 0) { 694 return Collections.emptyList(); 695 } 696 697 if (!cmd.parameters[cmd.parameters.length - 1].consumesRest && args.length > cmd.consumeInputResolvers) { 698 return Collections.emptyList(); 699 } 700 701 List<String> cmds = manager.getCommandCompletions().of(cmd, issuer, args, isAsync); 702 return filterTabComplete(args[args.length - 1], cmds); 703 } 704 705 /** 706 * Gets the actual args in string form the user typed 707 * This returns a list of all tab complete options which are possible with the given argument and commands. 708 * 709 * @param arg Argument which was pressed tab on. 710 * @param cmds The possibilities to return. 711 * @return All possible options. This may be empty. 712 */ 713 private static List<String> filterTabComplete(String arg, List<String> cmds) { 714 return cmds.stream() 715 .distinct() 716 .filter(cmd -> cmd != null && (arg.isEmpty() || ApacheCommonsLangUtil.startsWithIgnoreCase(cmd, arg))) 717 .collect(Collectors.toList()); 718 } 719 720 /** 721 * Executes the precommand and sees whether something is wrong. Ideally, you get false from this. 722 * 723 * @param commandOperationContext The context to use. 724 * @param cmd The command executed. 725 * @param issuer The issuer who executed the command. 726 * @param args The arguments the issuer provided. 727 * @return Whether something went wrong. 728 */ 729 private boolean checkPrecommand(CommandOperationContext commandOperationContext, RegisteredCommand cmd, CommandIssuer issuer, String[] args) { 730 Method pre = this.preCommandHandler; 731 if (pre != null) { 732 try { 733 Class<?>[] types = pre.getParameterTypes(); 734 Object[] parameters = new Object[pre.getParameterCount()]; 735 for (int i = 0; i < parameters.length; i++) { 736 Class<?> type = types[i]; 737 Object issuerObject = issuer.getIssuer(); 738 if (manager.isCommandIssuer(type) && type.isAssignableFrom(issuerObject.getClass())) { 739 parameters[i] = issuerObject; 740 } else if (CommandIssuer.class.isAssignableFrom(type)) { 741 parameters[i] = issuer; 742 } else if (RegisteredCommand.class.isAssignableFrom(type)) { 743 parameters[i] = cmd; 744 } else if (String[].class.isAssignableFrom((type))) { 745 parameters[i] = args; 746 } else { 747 parameters[i] = null; 748 } 749 } 750 751 return (boolean) pre.invoke(this, parameters); 752 } catch (IllegalAccessException | InvocationTargetException e) { 753 this.manager.log(LogLevel.ERROR, "Exception encountered while command pre-processing", e); 754 } 755 } 756 return false; 757 } 758 759 /** 760 * @deprecated Unstable API 761 */ 762 @Deprecated 763 @UnstableAPI 764 public CommandHelp getCommandHelp() { 765 return manager.generateCommandHelp(); 766 } 767 768 /** 769 * @deprecated Unstable API 770 */ 771 @Deprecated 772 @UnstableAPI 773 public void showCommandHelp() { 774 getCommandHelp().showHelp(); 775 } 776 777 public void help(Object issuer, String[] args) { 778 help(manager.getCommandIssuer(issuer), args); 779 } 780 781 public void help(CommandIssuer issuer, String[] args) { 782 issuer.sendMessage(MessageType.ERROR, MessageKeys.UNKNOWN_COMMAND); 783 } 784 785 public void doHelp(Object issuer, String... args) { 786 doHelp(manager.getCommandIssuer(issuer), args); 787 } 788 789 public void doHelp(CommandIssuer issuer, String... args) { 790 help(issuer, args); 791 } 792 793 public void showSyntax(CommandIssuer issuer, RegisteredCommand<?> cmd) { 794 issuer.sendMessage(MessageType.SYNTAX, MessageKeys.INVALID_SYNTAX, 795 "{command}", manager.getCommandPrefix(issuer) + cmd.command, 796 "{syntax}", cmd.getSyntaxText(issuer) 797 ); 798 } 799 800 public boolean hasPermission(Object issuer) { 801 return hasPermission(manager.getCommandIssuer(issuer)); 802 } 803 804 public boolean hasPermission(CommandIssuer issuer) { 805 return this.manager.hasPermission(issuer, getRequiredPermissions()); 806 } 807 808 public Set<String> getRequiredPermissions() { 809 return this.permissions; 810 } 811 812 public boolean requiresPermission(String permission) { 813 return getRequiredPermissions().contains(permission); 814 } 815 816 public String getName() { 817 return commandName; 818 } 819 820 public ExceptionHandler getExceptionHandler() { 821 return exceptionHandler; 822 } 823 824 public BaseCommand setExceptionHandler(ExceptionHandler exceptionHandler) { 825 this.exceptionHandler = exceptionHandler; 826 return this; 827 } 828 829 public RegisteredCommand getDefaultRegisteredCommand() { 830 return ACFUtil.getFirstElement(this.subCommands.get(DEFAULT)); 831 } 832 833 public String setContextFlags(Class<?> cls, String flags) { 834 return this.contextFlags.put(cls, flags); 835 } 836 837 public String getContextFlags(Class<?> cls) { 838 return this.contextFlags.get(cls); 839 } 840 841 public List<RegisteredCommand> getRegisteredCommands() { 842 List<RegisteredCommand> registeredCommands = new ArrayList<>(); 843 registeredCommands.addAll(this.subCommands.values()); 844 return registeredCommands; 845 } 846 847 protected SetMultimap<String, RegisteredCommand> getSubCommands() { 848 return subCommands; 849 } 850}