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. In tab completion the silent counterpart (isVisible) filters every path before a mapping contributes anything: first-token literals and parameter slots directly, later-argument literals and parameter slots throughmatchCandidates. - 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.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, then for each candidate resolve the token at the position being completed -- either a<param>slot (delegated tocommands/tabcomplete/) or a literal token.
-
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, then for each candidate resolve the token at the position being completed -- either a<param>slot (delegated tocommands/tabcomplete/) or a literal token.A literal comes from the matched candidate itself and from nothing else:
matchCandidates(BiMap, Player, String[], int)has already filtered every candidate by the typed prefix, by format length and by visibility (permission and OP), so a player who typedalphais offered only the literals of thealpha ...mappings they may see. Until 6.3.0 the literal branch instead rescanned every visible mapping at this position, ignoring the typed branch, and repeated that list once per matching candidate (case 61).Each literal is offered once: several candidates can share the literal at this position (
alpha enableandalpha enable <target>), so a literal is added only when the result does not already hold it, in candidate order. Parameter-slot suggestions are a different source and are appended exactly as their completer returns them. -
matchCandidates
-
prefixMatches
-
suggestParameterSlot
-
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 later-argument 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
-