Class CommandTabCompletionDispatch

java.lang.Object
com.ultikits.ultitools.abstracts.command.CommandTabCompletionDispatch

public final class CommandTabCompletionDispatch extends Object
Single tab-completion dispatch implementation originally shared by both command-executor generations (WIRE-01 / D-06); AbstractCommandExecutor was removed in 6.3.0, so BaseCommandExecutor is now this class's only caller.

BaseCommandExecutor and the removed AbstractCommandExecutor each scanned their own @CmdMapping methods into an independent BiMap<String, Method> and did not share a class hierarchy, so neither could simply inherit a single suggest implementation from a common supertype. This class was the alternative both delegated to: a stateless dispatch entered with the caller's own mapping table and this.

Two responsibilities live here that were previously either duplicated or entirely absent:

  • The mapping-level @CmdMapping(permission=)/requireOp() guard (checkPermission(CommandSender, Method)/checkOp(CommandSender, Method)), relocated -- not copied -- from the removed (6.3.0) AbstractCommandExecutor's private methods of the same name (T-05-20 / T-05-21). BaseCommandExecutor.suggest had ZERO permission checks before this class existed, so migrating a command onto the current generation leaked the entire sub-command table to unprivileged players.
  • Argument-index -> matched-method -> parameter-name resolution for a multi-token argument vector (suggest(BiMap, Player, Command, String[], Object)), which is absent from commands/tabcomplete/TabCompletionManager (its createContext leaves matchedMethod/parameterName null).
Reflective suggestion-method invocation itself is deliberately NOT reimplemented here -- the resolved <param> slot is handed to TabCompletionManager.suggest(TabCompletionContext), which reaches MethodInvocationCompleter. That completer walks the class hierarchy (issue #190), so an AOP-proxied executor's suggestion method resolves correctly; the removed (6.3.0) AbstractCommandExecutor's own eight private reflection helpers did not.

@key notation (a suggest() value starting with @) resolves through TabCompletionManager.resolveSuggestValue(Method, String) + TabCompletionManager.suggest(TabCompletionContext, String) (05-06 / D-07) -- driven by the parameter's actual @CmdParam.suggest(), never by parameterName (the display name), so a display name that happens to start with @ is never mistaken for a completer key.

Since:
6.3.0
Author:
wisdomme
  • Constructor Details

    • CommandTabCompletionDispatch

      private CommandTabCompletionDispatch()
  • Method Details

    • suggest

      public static List<String> suggest(com.google.common.collect.BiMap<String,Method> mappings, org.bukkit.entity.Player player, org.bukkit.command.Command command, String[] args, Object executorInstance)
      The single tab-completion dispatch entry point for both base-class generations.

      Both generations' suggest(Player, Command, String[]) reduce to a one-line delegation to this method -- see BaseCommandExecutor.suggest(Player, Command, String[]) and the removed (6.3.0) AbstractCommandExecutor's own former shell.

      Parameters:
      mappings - the executor's own format-to-method mapping table
      player - the player requesting completion
      command - the command being completed
      args - the current arguments, including the partial final token
      executorInstance - the executor instance (this from the caller) -- carried through to TabCompletionContext for reflective suggestion invocation
      Returns:
      the suggestions; never null
    • suggestFirstToken

      private static List<String> suggestFirstToken(com.google.common.collect.BiMap<String,Method> mappings, org.bukkit.entity.Player player, org.bukkit.command.Command command, String[] args, Object executorInstance)
      First-token completion: literal command formats, permission-filtered before they enter the candidate map, then delegated to TabCompletionManager.suggestFirstArgs(Map, TabCompletionContext) for the package's existing sort-and-dedup semantics. The removed (6.3.0) AbstractCommandExecutor's own first-token branch neither sorted nor deduplicated -- adopting the package's behaviour here is a deliberate, named change (see plan 05-05's SUMMARY).
    • firstTokenParameterSuggestions

      private static List<String> firstTokenParameterSuggestions(Map<String,Method> visible, org.bukkit.entity.Player player, org.bukkit.command.Command command, String[] args, Object executorInstance)
      Resolves the <param> slot of every visible format whose first token is a parameter rather than a literal.

      TabCompletionManager.suggestFirstArgs skips parameter placeholders by construction -- it collects literal subcommand names -- and until 6.3.0 nothing else covered position 0, so a format like "<name>" contributed no candidates at all and its @CmdParam(suggest = ...) method was never invoked. That silently disabled completion for the first argument of roughly forty mappings across nine modules, including /home <name>, /warp <name>, /tpa <player> and /pay; two of them had written a suggester that never ran. See #398.

      Position 1 and later were unaffected, because suggestSubsequentToken has always resolved parameter slots. UltiMenu declares the same suggester on "<name>" and on "open <name>", and only the second worked -- same class, same parameter, same method, differing only in token position.

      Since:
      6.3.0
    • suggestSubsequentToken

      private static List<String> suggestSubsequentToken(com.google.common.collect.BiMap<String,Method> mappings, org.bukkit.entity.Player player, org.bukkit.command.Command command, String[] args, Object executorInstance)
      Argument-position resolution for a multi-token vector: find every mapping whose format's literal/parameter prefix matches the already-typed tokens (permission-filtered), then for each candidate resolve the token at the position being completed -- either a <param> slot (delegated to commands/tabcomplete/) or a literal token (collected from every visible sibling format long enough to have one at this position).
    • matchCandidates

      private static List<Method> matchCandidates(com.google.common.collect.BiMap<String,Method> mappings, org.bukkit.entity.Player player, String[] args, int targetIndex)
    • prefixMatches

      private static boolean prefixMatches(String[] formatArgs, String[] args, int targetIndex)
    • suggestParameterSlot

      private static List<String> suggestParameterSlot(org.bukkit.entity.Player player, org.bukkit.command.Command command, String[] args, int targetIndex, Method method, String arg, Object executorInstance)
    • suggestLiteralSiblingToken

      private static List<String> suggestLiteralSiblingToken(com.google.common.collect.BiMap<String,Method> mappings, org.bukkit.entity.Player player, int targetIndex)
      Permission-filtered before it can contribute a suggestion -- T-05-20's mitigation extended to this branch too. The removed (6.3.0) AbstractCommandExecutor.suggest's equivalent else-branch scanned every sibling format unconditionally; both generations now share this gated version.
    • splitFormat

      private static String[] splitFormat(String format)
    • argAt

      private static String argAt(String format, int index)
    • isParameterToken

      private static boolean isParameterToken(String token)
    • isVisible

      private static boolean isVisible(org.bukkit.entity.Player player, Method method)
      Tab-completion visibility filter: silent counterpart to checkPermission(CommandSender, Method) / checkOp(CommandSender, Method) (T-05-fix Part 3 / SILENT-25). Real-machine UAT observed a player without permission repeatedly receiving the "no permission" message while typing unrelated sub-commands, because every mapping this class's first-token and literal-sibling-token paths evaluate for visibility went through the SAME messaging checkPermission/checkOp used by actual command dispatch (the removed (6.3.0) AbstractCommandExecutor.onCommand's call site). Tab completion is not a command invocation: filtering an unprivileged sender's candidates out silently is correct, but re-sending the rejection notice on every keystroke that happens to re-evaluate a gated mapping is not. checkPermission(CommandSender, Method) and checkOp(CommandSender, Method) themselves are UNCHANGED and still message -- only this tab-completion-only visibility filter switches to the silent predicates below.
      Parameters:
      player - the player requesting completion
      method - the candidate @CmdMapping method
      Returns:
      true if the mapping is visible to player
    • checkPermission

      public static boolean checkPermission(org.bukkit.command.CommandSender sender, Method method)
      Mapping-level permission guard: gates a matched @CmdMapping method out of tab completion (or dispatch) entirely before it can contribute anything, rather than filtering an already-assembled list afterwards.

      Relocated -- not copied -- from the removed (6.3.0) AbstractCommandExecutor's private method of the same name and signature (T-05-20). Behaviour is byte-for-byte identical, including the sendMessage on denial, which both base-class generations inherited unchanged from the pre-existing class this predicate was ported from, back when both generations coexisted. This method remains BaseCommandExecutor's actual-dispatch guard (its call site is unchanged); tab completion's own visibility filter goes through the silent isPermissionSatisfied(CommandSender, Method) instead (T-05-fix Part 3).

      Parameters:
      sender - the command sender being checked
      method - the matched @CmdMapping method
      Returns:
      true if the mapping declares no permission or the sender holds it
    • checkOp

      public static boolean checkOp(org.bukkit.command.CommandSender sender, Method method)
      Mapping-level OP guard, the requireOp() counterpart to checkPermission(CommandSender, Method). Relocated -- not copied -- from the removed (6.3.0) AbstractCommandExecutor's private method of the same name and signature (T-05-21). Remains the actual-dispatch guard, unchanged; tab completion's visibility filter goes through the silent isOpSatisfied(CommandSender, Method) instead (T-05-fix Part 3).
      Parameters:
      sender - the command sender being checked
      method - the matched @CmdMapping method
      Returns:
      true if the mapping does not require OP or the sender is an OP
    • isPermissionSatisfied

      private static boolean isPermissionSatisfied(org.bukkit.command.CommandSender sender, Method method)
      The permission predicate checkPermission(CommandSender, Method) messages on, and isVisible(Player, Method) does not -- the single source of truth for BOTH, so the two can never independently drift into disagreeing about whether a mapping is permitted.
      Parameters:
      sender - the command sender being checked
      method - the matched @CmdMapping method
      Returns:
      true if the mapping declares no permission or the sender holds it
    • isOpSatisfied

      private static boolean isOpSatisfied(org.bukkit.command.CommandSender sender, Method method)
      The OP predicate checkOp(CommandSender, Method) messages on, and isVisible(Player, Method) does not -- the single source of truth for both.
      Parameters:
      sender - the command sender being checked
      method - the matched @CmdMapping method
      Returns:
      true if the mapping does not require OP or the sender is an OP