Class CommandTabCompletionDispatch
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.suggesthad 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 fromcommands/tabcomplete/TabCompletionManager(itscreateContextleavesmatchedMethod/parameterNamenull).
<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 Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionprivate static Stringstatic booleanMapping-level OP guard, therequireOp()counterpart tocheckPermission(CommandSender, Method).static booleancheckPermission(org.bukkit.command.CommandSender sender, Method method) Mapping-level permission guard: gates a matched@CmdMappingmethod out of tab completion (or dispatch) entirely before it can contribute anything, rather than filtering an already-assembled list afterwards.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.private static booleanisOpSatisfied(org.bukkit.command.CommandSender sender, Method method) The OP predicatecheckOp(CommandSender, Method)messages on, andisVisible(Player, Method)does not -- the single source of truth for both.private static booleanisParameterToken(String token) private static booleanisPermissionSatisfied(org.bukkit.command.CommandSender sender, Method method) The permission predicatecheckPermission(CommandSender, Method)messages on, andisVisible(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.private static booleanTab-completion visibility filter: silent counterpart tocheckPermission(CommandSender, Method)/checkOp(CommandSender, Method)(T-05-fix Part 3 / SILENT-25).matchCandidates(com.google.common.collect.BiMap<String, Method> mappings, org.bukkit.entity.Player player, String[] args, int targetIndex) private static booleanprefixMatches(String[] formatArgs, String[] args, int targetIndex) private static String[]splitFormat(String format) 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.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 toTabCompletionManager.suggestFirstArgs(Map, TabCompletionContext)for the package's existing sort-and-dedup semantics.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.suggestParameterSlot(org.bukkit.entity.Player player, org.bukkit.command.Command command, String[] args, int targetIndex, Method method, String arg, Object executorInstance) 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 tocommands/tabcomplete/) or a literal token (collected from every visible sibling format long enough to have one at this position).
-
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 -- seeBaseCommandExecutor.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 tableplayer- the player requesting completioncommand- the command being completedargs- the current arguments, including the partial final tokenexecutorInstance- the executor instance (thisfrom the caller) -- carried through toTabCompletionContextfor 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 toTabCompletionManager.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.suggestFirstArgsskips 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
suggestSubsequentTokenhas 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 tocommands/tabcomplete/) or a literal token (collected from every visible sibling format long enough to have one at this position). -
matchCandidates
-
prefixMatches
-
suggestParameterSlot
-
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
-
argAt
-
isParameterToken
-
isVisible
Tab-completion visibility filter: silent counterpart tocheckPermission(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 messagingcheckPermission/checkOpused 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)andcheckOp(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 completionmethod- the candidate@CmdMappingmethod- Returns:
trueif the mapping is visible toplayer
-
checkPermission
Mapping-level permission guard: gates a matched@CmdMappingmethod 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 thesendMessageon 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 remainsBaseCommandExecutor's actual-dispatch guard (its call site is unchanged); tab completion's own visibility filter goes through the silentisPermissionSatisfied(CommandSender, Method)instead (T-05-fix Part 3).- Parameters:
sender- the command sender being checkedmethod- the matched@CmdMappingmethod- Returns:
trueif the mapping declares no permission or the sender holds it
-
checkOp
Mapping-level OP guard, therequireOp()counterpart tocheckPermission(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 silentisOpSatisfied(CommandSender, Method)instead (T-05-fix Part 3).- Parameters:
sender- the command sender being checkedmethod- the matched@CmdMappingmethod- Returns:
trueif 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 predicatecheckPermission(CommandSender, Method)messages on, andisVisible(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 checkedmethod- the matched@CmdMappingmethod- Returns:
trueif the mapping declares no permission or the sender holds it
-
isOpSatisfied
The OP predicatecheckOp(CommandSender, Method)messages on, andisVisible(Player, Method)does not -- the single source of truth for both.- Parameters:
sender- the command sender being checkedmethod- the matched@CmdMappingmethod- Returns:
trueif the mapping does not require OP or the sender is an OP
-