Class BaseCommandExecutor
- All Implemented Interfaces:
org.bukkit.command.CommandExecutor,org.bukkit.command.TabCompleter,org.bukkit.command.TabExecutor
- Direct Known Subclasses:
CloudLoginCommand,PluginInstallCommands,UltiToolsCommands
The default validator chain applies four validators in order: sender type (driven by the
class-level @CmdTarget, overridable per method), permission and operator requirement
(driven by @CmdExecutor), cooldown (applied only after a successful invocation), and
usage locking (acquired and released around the call in a try-finally). Execution flow in
onCommand(org.bukkit.command.CommandSender, org.bukkit.command.Command, java.lang.String, java.lang.String[]): help short-circuit, immutable CommandContext construction, scored
method matching via matchMethod(java.lang.String[]), parameter parsing, the validator chain, an
argument-count check, then invocation. Subclasses MUST implement handleHelp(org.bukkit.command.CommandSender).
- Since:
- 6.2.0
- Version:
- 2.0.0
- Author:
- wisdomme
-
Field Summary
FieldsModifier and TypeFieldDescriptionResolved seconds of this executor's config-bound@CmdCDannotations, keyed byCooldownValidator.bindingKey, and their load-time value sources (#531).private final CooldownValidatorprivate final UsageLockValidatorprivate static final Objectprivate final TypeParserRegistryprivate final ValidatorChain -
Constructor Summary
ConstructorsConstructorDescriptionCreates a new command executor with default validators.BaseCommandExecutor(ValidatorChain validatorChain) Creates a new command executor with a custom validator chain. -
Method Summary
Modifier and TypeMethodDescriptionvoidaddValidator(CommandValidator validator) Adds a custom validator to the chain.private voidarmTimeoutWatcher(CommandContext context, AsyncCommand asyncCommand, AtomicBoolean reported) Arms the delayed watcher that reports a timeout message to the sender if the command body has not already completed by then.protected Object[]buildMethodParams(CommandContext context, Method method) Builds the method parameters from the command context.private intcalculateMatchScore(String[] formatArgs, String[] actualArgs) Calculates a match score for format vs actual args.protected ValidatorChainCreates the default validator chain with standard validators.private static StringRenders a throwable for the user in a form that is never"null".private voiddispatchAsyncCommand(CommandContext context, AsyncCommand asyncCommand, org.bukkit.scheduler.BukkitRunnable runnable, AtomicBoolean reported) Sends the optional "processing" message, dispatchesrunnableasynchronously exactly once, and arms the timeout watcher if configured.protected voidexecuteCommand(CommandContext context, Method method, Object[] params, ValidatorChain.ChainValidationResult validationResult) Executes the command method.private StringExtract module name from the concrete command executor class.private StringextractParameterName(String formatArg) protected StringGets the help command string.Gets all registered command mappings.private booleanhandleGatedHelp(CommandContext context) Runs the sender-type and permission validators, then shows help if they pass.protected abstract voidhandleHelp(org.bukkit.command.CommandSender sender) Handles the help command.private voidinvokeCommandBody(CommandContext context, Method method, Object[] params, List<CommandValidator> ranValidators, TriggerContext triggerCtx, AtomicBoolean reported) Runs the matched command method plus its surrounding bookkeeping (audit-user context, post-action validator hooks, error reporting, the WIRE-12 completion flag) -- the body of theBukkitRunnableexecuteCommand(com.ultikits.ultitools.abstracts.command.CommandContext, java.lang.reflect.Method, java.lang.Object[], com.ultikits.ultitools.abstracts.command.validation.ValidatorChain.ChainValidationResult)schedules.private booleanisParameter(String arg) private static booleanisSenderType(Class<?> paramType) protected MethodmatchMethod(String[] args) Matches arguments to a registered method.booleanonCommand(org.bukkit.command.CommandSender sender, org.bukkit.command.Command command, String alias, String[] args) onTabComplete(org.bukkit.command.CommandSender sender, org.bukkit.command.Command command, String alias, String[] args) parseParameters(String[] args, String format) Parses command arguments into named parameters.protected ObjectparseParameterValue(String[] values, Class<?> type) Parses a parameter value to the target type.voidremoveValidator(CommandValidator validator) Removes a validator from the chain.private voidreportCommandExecutionError(CommandContext context, Method method, Exception e, TriggerContext triggerCtx) Sends the user-facing failure message, logs the exception, and best-effort reports it toErrorReportCollectorfor a command method that threw.private ObjectresolveCmdParam(CommandContext context, Parameter parameter, Class<?> paramType) private ObjectresolveParameter(CommandContext context, Parameter parameter) private ObjectresolveSender(CommandContext context, Class<?> paramType) private voidScans methods for command mappings.Generates tab completion suggestions.private voidReleasescooldownValidator/lockValidatorfromPlayerCacheManagertracking and bulk-clearsUsageLockValidator's locks when this executor is torn down (module unload).protected booleanvalidateParameterCount(String[] args, String format, org.bukkit.command.CommandSender sender, org.bukkit.command.Command command) Validates the parameter count.
-
Field Details
-
mappings
-
validatorChain
-
configBoundCooldownSeconds
Resolved seconds of this executor's config-bound@CmdCDannotations, keyed byCooldownValidator.bindingKey, and their load-time value sources (#531). Kept on the executor -- the exact owner of a binding -- so the state lives and dies with it and nothing shared (such as aCooldownValidatorseveral executors use) has to release it on any exit path. Replaced as whole immutable maps; written on the main thread, read on dispatch threads. Reached only throughConfigBoundCooldownState. -
configBoundCooldownSources
-
cooldownValidator
-
lockValidator
-
parserRegistry
-
PARSE_FAILED_SENTINEL
-
-
Constructor Details
-
BaseCommandExecutor
public BaseCommandExecutor()Creates a new command executor with default validators. -
BaseCommandExecutor
Creates a new command executor with a custom validator chain.This is the other half of the construction surface
createDefaultValidatorChain()warns about: a chain supplied directly here is subject to the exact same load-time contract. If any@CmdMappingmethod on this class -- or the class itself -- declares@CmdCDwhilevalidatorChainholds noCooldownValidator, or declares@UsageLimit(any scope butNONE) while it holds noUsageLockValidator, the module is refused at load, naming this class and the offending mapping method (SILENT-11 / D-01, D-04; enforced byPluginManager.validateCommandExecutorContract). There is no opt-out: Phase 3 D-08's module-granularity isolation is the accepted escape hatch -- only this module fails to load, every other module still starts.- Parameters:
validatorChain- the custom validator chain
-
-
Method Details
-
createDefaultValidatorChain
Creates the default validator chain with standard validators. Override this method to customize the validation pipeline.An override that drops
CooldownValidatororUsageLockValidatorfrom the chain while a mapping on this class -- or the class itself -- declares@CmdCDor@UsageLimit(any scope butNONE) respectively IS refused at load, naming the offending class and, when known, the offending mapping method (SILENT-11 / D-01, D-04; enforced byPluginManager.validateCommandExecutorContract, hooked at the end of container assembly, before any command reaches Bukkit). There is no opt-out: no system property, config key, or annotation attribute disables this check. Phase 3 D-08's module-granularity isolation is the accepted escape hatch instead -- only the offending module fails to load, every other module still starts.- Returns:
- the validator chain
-
unregisterValidatorsFromPlayerCache
private void unregisterValidatorsFromPlayerCache()ReleasescooldownValidator/lockValidatorfromPlayerCacheManagertracking and bulk-clearsUsageLockValidator's locks when this executor is torn down (module unload).Reached through the framework's EXISTING
@PreDestroycontainer lifecycle --PluginManager.unregister(plugin)already callsplugin.getContext().close(), which invokes every singleton bean's@PreDestroymethods -- rather than a second, purpose-built unload hook (GEN-08, D-03).cooldownValidator/lockValidatorare registered withPlayerCacheManagerSEPARATELY from this executor bean itself (their sweepable state lives on them, not on this class), so the pre-existing per-beanplayerCacheManager.unregisterBean(bean)loop already inPluginManager.unregister(...)-- which only ever sees the beans a plugin's own container constructed -- never reaches them on its own; this hook is what does.UsageLockValidator.clearAllLocks()has no per-player analogue and, unlikeUsageLockValidator.clearPlayerLocks(java.util.UUID)/CooldownValidator.clearCooldowns(java.util.UUID), no quit-path call site either (GEN-08) -- module unload is the correct point to bulk-release whatever this specific executor instance's locks still held at teardown.- Since:
- 6.3.0
-
scanCommandMappings
private void scanCommandMappings()Scans methods for command mappings. -
handleHelp
protected abstract void handleHelp(org.bukkit.command.CommandSender sender) Handles the help command. Override to provide custom help.- Parameters:
sender- the command sender
-
getHelpCommand
Gets the help command string.- Returns:
- the help command
-
onCommand
public boolean onCommand(org.bukkit.command.CommandSender sender, org.bukkit.command.Command command, String alias, String[] args) - Specified by:
onCommandin interfaceorg.bukkit.command.CommandExecutor
-
executeCommand
protected void executeCommand(CommandContext context, Method method, Object[] params, ValidatorChain.ChainValidationResult validationResult) Executes the command method. Supports both synchronous and asynchronous execution via @AsyncCommand or @RunAsync.- Parameters:
context- the command contextmethod- the method to executeparams- the method parametersvalidationResult- the chain result produced byonCommand's validation step for THIS invocation; itsValidatorChain.ChainValidationResult.getPassedValidators()is the sole source that drives each validator'sCommandValidator.onComplete(CommandContext, boolean)call -- see D-01. Never recomputed here: re-validating would run every validator twice and re-trigger their gates (e.g. a second lock acquisition attempt).
-
invokeCommandBody
private void invokeCommandBody(CommandContext context, Method method, Object[] params, List<CommandValidator> ranValidators, TriggerContext triggerCtx, AtomicBoolean reported) Runs the matched command method plus its surrounding bookkeeping (audit-user context, post-action validator hooks, error reporting, the WIRE-12 completion flag) -- the body of theBukkitRunnableexecuteCommand(com.ultikits.ultitools.abstracts.command.CommandContext, java.lang.reflect.Method, java.lang.Object[], com.ultikits.ultitools.abstracts.command.validation.ValidatorChain.ChainValidationResult)schedules.Split out of
executeCommandpurely to bring the enclosing method's NPathComplexity back under threshold; the control flow below, and therefore the runtime behaviour, is unchanged from before the extraction (refactor only, per D-01/D-02/T-02-REP design notes that used to live on the anonymousrun()this replaces).- Since:
- 6.3.0
-
handleGatedHelp
Runs the sender-type and permission validators, then shows help if they pass.Before 6.3.0 the help short-circuit sat at the very top of
onCommand(org.bukkit.command.CommandSender, org.bukkit.command.Command, java.lang.String, java.lang.String[]), ahead of theCommandContextbeing built, so it bypassed the entire validator chain (#381). Two consequences were measured on a real server: a console received the full help of a@CmdTarget(PLAYER)command -- including, in one module, an admin section gated only onhasPermission, which a console always satisfies -- and modules had begun working around the absent guarantee in incompatible ways. One testedsender instanceof Playerand silently printed nothing for a console; another printed everything. Neither is wrong against a contract that promised nothing.Cooldown and usage-limit are deliberately not applied here. Help is not the guarded action: withholding an explanation because the command itself is on cooldown would be perverse, and
UsageLockValidatoracquires a lock that only the invocation path releases. This is a choice, stated so it cannot later be mistaken for the same oversight in a different place.Applicability is consulted, same as the normal dispatch path (#413). Before invoking a sender-type or permission validator here,
ValidatorChain.isApplicable(com.ultikits.ultitools.abstracts.command.validation.CommandValidator, com.ultikits.ultitools.abstracts.command.CommandContext)is called -- the exact same static methodValidatorChain.validate(com.ultikits.ultitools.abstracts.command.CommandContext)/#validateAllcall internally, not a second, independent copy of the one-line condition it wraps (WR-02). A validator that reports it does not apply to this context is skipped, not invoked and happened to pass. This does not weaken enforcement: a validator that DOES apply and denies still denies help, exactly as before.- Parameters:
context- the command context, already built- Returns:
truealways -- Bukkit's contract for a handled command- Since:
- 6.3.0
-
reportCommandExecutionError
private void reportCommandExecutionError(CommandContext context, Method method, Exception e, TriggerContext triggerCtx) Sends the user-facing failure message, logs the exception, and best-effort reports it toErrorReportCollectorfor a command method that threw. Split out ofinvokeCommandBody(com.ultikits.ultitools.abstracts.command.CommandContext, java.lang.reflect.Method, java.lang.Object[], java.util.List<com.ultikits.ultitools.abstracts.command.validation.CommandValidator>, com.ultikits.ultitools.manager.TriggerContext, java.util.concurrent.atomic.AtomicBoolean)for the same NPathComplexity reason as that extraction.- Since:
- 6.3.0
-
describe
Renders a throwable for the user in a form that is never"null".Every command body is invoked reflectively, so anything it throws arrives wrapped in an
InvocationTargetException, whose owngetMessage()isnullby construction -- the detail belongs to the cause. Reporting the wrapper's message therefore appended a literal"null"to the error prefix for every command failure, sending the user to look for a null-pointer bug that was not there and hiding the real one in the server log (#385).The cause's own message can also be null --
NullPointerExceptionthrown by a bare dereference is the common case -- so the class's simple name is used as the fallback. It is a poor message, but"NullPointerException"tells the reader more than"null"does, and the full stack trace is one line below in the log either way.- Parameters:
cause- the unwrapped throwable- Returns:
- its message, or its simple class name when the message is null or blank
- Since:
- 6.3.0
-
dispatchAsyncCommand
private void dispatchAsyncCommand(CommandContext context, AsyncCommand asyncCommand, org.bukkit.scheduler.BukkitRunnable runnable, AtomicBoolean reported) Sends the optional "processing" message, dispatchesrunnableasynchronously exactly once, and arms the timeout watcher if configured. Split out ofexecuteCommand(com.ultikits.ultitools.abstracts.command.CommandContext, java.lang.reflect.Method, java.lang.Object[], com.ultikits.ultitools.abstracts.command.validation.ValidatorChain.ChainValidationResult)for the same NPathComplexity reason asinvokeCommandBody(com.ultikits.ultitools.abstracts.command.CommandContext, java.lang.reflect.Method, java.lang.Object[], java.util.List<com.ultikits.ultitools.abstracts.command.validation.CommandValidator>, com.ultikits.ultitools.manager.TriggerContext, java.util.concurrent.atomic.AtomicBoolean)'s extraction.- Since:
- 6.3.0
-
armTimeoutWatcher
private void armTimeoutWatcher(CommandContext context, AsyncCommand asyncCommand, AtomicBoolean reported) Arms the delayed watcher that reports a timeout message to the sender if the command body has not already completed by then. Split out ofdispatchAsyncCommand(com.ultikits.ultitools.abstracts.command.CommandContext, com.ultikits.ultitools.annotations.command.AsyncCommand, org.bukkit.scheduler.BukkitRunnable, java.util.concurrent.atomic.AtomicBoolean)for the same NPathComplexity reason as its sibling extractions.- Since:
- 6.3.0
-
matchMethod
Matches arguments to a registered method.- Parameters:
args- the command arguments- Returns:
- the matched method, or null if not found
-
calculateMatchScore
Calculates a match score for format vs actual args. Weights: an exact literal match scores 10, a<param>placeholder scores 1, and an exact-length match earns a +5 bonus. A format string is written against these three numbers.- Parameters:
formatArgs- the format argumentsactualArgs- the actual arguments- Returns:
- the match score (-1 if no match)
-
isParameter
-
parseParameters
Parses command arguments into named parameters.- Parameters:
args- the command argumentsformat- the command format- Returns:
- map of parameter names to values
-
extractParameterName
-
validateParameterCount
protected boolean validateParameterCount(String[] args, String format, org.bukkit.command.CommandSender sender, org.bukkit.command.Command command) Validates the parameter count. -
buildMethodParams
Builds the method parameters from the command context.- Parameters:
context- the command contextmethod- the method to invoke- Returns:
- the method parameters, or null if parsing fails
-
resolveParameter
-
isSenderType
-
resolveSender
-
resolveCmdParam
-
parseParameterValue
Parses a parameter value to the target type.- Parameters:
values- the string valuestype- the target type- Returns:
- the parsed value
- Throws:
TypeParseException
-
onTabComplete
@Nullable public List<String> onTabComplete(org.bukkit.command.CommandSender sender, org.bukkit.command.Command command, String alias, String[] args) - Specified by:
onTabCompletein interfaceorg.bukkit.command.TabCompleter
-
suggest
protected List<String> suggest(org.bukkit.entity.Player player, org.bukkit.command.Command command, String[] args) Generates tab completion suggestions. Override this method to provide custom suggestions.- Parameters:
player- the player requesting completioncommand- the commandargs- the current arguments- Returns:
- list of suggestions
-
getMappings
Gets all registered command mappings.- Returns:
- unmodifiable map of format to method
-
addValidator
Adds a custom validator to the chain.- Parameters:
validator- the validator to add
-
removeValidator
Removes a validator from the chain.- Parameters:
validator- the validator to remove
-
extractModuleName
Extract module name from the concrete command executor class. Uses the simple class name as a reasonable approximation.
-