java.lang.Object
com.ultikits.ultitools.abstracts.command.validation.validators.CooldownValidator
All Implemented Interfaces:
CommandValidator, PlayerCacheSaver, PlayerCacheManager.ExpiringPlayerCache

public class CooldownValidator extends Object implements CommandValidator, PlayerCacheManager.ExpiringPlayerCache, PlayerCacheSaver
Validates and manages command cooldowns for players. Thread-safe implementation using ConcurrentHashMap.

Player-cache lifecycle (case 48, @since 6.3.0): an instance registers itself with PlayerCacheManager at its first validate(CommandContext), so quits and the periodic sweep reach its per-player state. It is released when the last executor that has dispatched through it unloads -- BaseCommandExecutor's teardown decides that and calls releasePlayerCacheRegistration() -- and it registers again on its next use, so a chain reused after a release keeps being swept.

Since:
6.2.0
Version:
2.0.0
Author:
wisdomme
  • Field Details

    • ORDER

      private static final int ORDER
      See Also:
    • LOGGER

      private static final Logger LOGGER
    • cooldowns

      private final Map<UUID,Map<Object,Long>> cooldowns
      Map of player UUID -> (cooldown key -> cooldown end timestamp).

      The cooldown key is an CooldownValidator.ExecutorMethodKey -- the dispatching executor instance, held weakly and compared by identity, plus method.toString() -- whenever the context carries an executor, which every dispatch through BaseCommandExecutor does (#539). A context without an executor (a validator driven directly, outside any executor) keys by the method.toString() string alone, exactly as before 6.3.0.

      saveBeforeRemove = true so savePlayerData(UUID) -- which delegates to the pre-existing clearCooldowns(UUID) -- fires on quit; see that method's javadoc for why (GEN-08, D-03).

    • playerCacheRegistered

      private volatile boolean playerCacheRegistered
      True once this instance has registered cooldowns with the live PlayerCacheManager for quit-based and time-based sweeping (GEN-08, D-03).

      Set from validate(CommandContext) -- the first production call this validator receives after construction -- rather than from the constructor: a bare new CooldownValidator() (the shape a unit test, or BaseCommandExecutor's own constructor, uses) must never even attempt contact with a core plugin that may not exist yet. Guarding on a live UltiTools.getInstance() rather than an unconditional "did I try" flag means a first attempt made before the core plugin is up is retried on the next call instead of being permanently abandoned -- PlayerCacheManager.tryRegister(java.lang.Object) is itself idempotent per instance, so a redundant retry after the flag is already true is simply never made.

      Cleared by releasePlayerCacheRegistration() when the last executor that has dispatched through this instance unloads, so a reused instance registers again on its next validate(CommandContext) instead of staying unswept (case 48).

    • defaultCooldownSeconds

      private final int defaultCooldownSeconds
  • Constructor Details

    • CooldownValidator

      public CooldownValidator()
      Creates a cooldown validator with no default cooldown.
    • CooldownValidator

      public CooldownValidator(int defaultCooldownSeconds)
      Creates a cooldown validator with a default cooldown.
      Parameters:
      defaultCooldownSeconds - the default cooldown in seconds
  • Method Details

    • validate

      Description copied from interface: CommandValidator
      Validates the command context.
      Specified by:
      validate in interface CommandValidator
      Parameters:
      context - the command context to validate
      Returns:
      the validation result
    • applyCooldown

      public void applyCooldown(CommandContext context)
      Applies cooldown after command execution. Should be called after successful command execution.
      Parameters:
      context - the command context
    • cooldownKey

      private static Object cooldownKey(Object executor, String methodKey)
      The key an active cooldown is stored under (#539): the executor instance and the method when the dispatch names an executor, the method string alone when it does not.
    • isKeyFor

      private static boolean isKeyFor(Object key, String methodKey)
      Whether a stored cooldown key belongs to methodKey, whichever executor it names -- the match the string-keyed accessors use.
    • onComplete

      public void onComplete(CommandContext context, boolean commandSucceeded)
      Post-action hook that applies the cooldown recorded for this invocation. Delegates to applyCooldown(CommandContext) and is invoked only by a chain that actually ran this validator for the current dispatch -- see CommandValidator.onComplete(CommandContext, boolean).
      Specified by:
      onComplete in interface CommandValidator
      Parameters:
      context - the command context
      commandSucceeded - ignored -- the cooldown applies whether the mapped method succeeded or threw
      Since:
      6.3.0
    • ensurePlayerCacheRegistered

      private void ensurePlayerCacheRegistered()
      Attempts lazy first-use registration of this instance with the live PlayerCacheManager singleton. Safe to call unconditionally on every validate(CommandContext) invocation: a no-op once playerCacheRegistered is true, and a cheap, safely-no-op-on-failure retry otherwise (see that field's javadoc).
    • releasePlayerCacheRegistration

      @Internal public final void releasePlayerCacheRegistration()
      Stops PlayerCacheManager tracking of this instance and re-arms its lazy registration, so the next validate(CommandContext) registers it again (case 48).

      Called by BaseCommandExecutor's unload teardown once no other loaded executor that has dispatched still uses this validator. Not intended for module code: releasing a validator that a loaded executor still uses stops the quit sweep from reaching it until its next dispatch.

      Since:
      6.3.0
    • sweepExpired

      public void sweepExpired()
      PlayerCacheManager.ExpiringPlayerCache opt-in: delegates to the pre-existing cleanupExpired() time-based half. Invoked once per PlayerCacheManager.sweepExpiredEntries() pass -- the periodic sweep this instance only receives once registered (see ensurePlayerCacheRegistered()) -- independently of whether any player has quit (GEN-08, D-03).
      Specified by:
      sweepExpired in interface PlayerCacheManager.ExpiringPlayerCache
      Since:
      6.3.0
    • savePlayerData

      public void savePlayerData(UUID playerId)
      PlayerCacheSaver hook: fired by PlayerCacheManager.onPlayerQuit(UUID) ("the quit sweep") for the quitting player -- before the generic @PlayerCache field sweep removes the (by then already-empty) cooldowns entry. Delegates to the pre-existing clearCooldowns(UUID) rather than re-deriving its predicate, giving that previously zero-caller method a real, quit-path-reached production call site (GEN-08). The interface is named for persistence, but its contract is simply "run this before the generic removal" -- there is nothing here to persist, and reusing the hook for an idempotent, already-correct cleanup method is the same predicate the generic sweep would otherwise perform standalone.
      Specified by:
      savePlayerData in interface PlayerCacheSaver
      Parameters:
      playerId - the UUID of the player quitting
      Since:
      6.3.0
    • clearCooldowns

      public void clearCooldowns(UUID playerId)
      Clears all cooldowns for a player.
      Parameters:
      playerId - the player's UUID
    • clearCooldown

      public void clearCooldown(UUID playerId, String methodKey)
      Clears a specific cooldown for a player, on every executor this validator serves.

      As of 6.3.0 an active cooldown is keyed by the executor instance as well as the method (#539). With one executor per validator -- the shape both BaseCommandExecutor constructors create -- this clears exactly that executor's cooldown, as before. When one validator serves several executors, it clears the method's cooldown on all of them; use clearCooldown(UUID, Object, String) to address one.

      Parameters:
      playerId - the player's UUID
      methodKey - the method key, method.toString() of the mapping method
    • clearCooldown

      public void clearCooldown(UUID playerId, Object executor, String methodKey)
      Clears one executor's cooldown of a method for a player (#539).
      Parameters:
      playerId - the player's UUID
      executor - the executor instance the cooldown was recorded on
      methodKey - the method key, method.toString() of the mapping method
      Since:
      6.3.0
    • getRemainingCooldown

      public long getRemainingCooldown(UUID playerId, String methodKey)
      Gets the remaining cooldown time in seconds, on any executor this validator serves.

      With one executor per validator this is that executor's remaining time, as before 6.3.0. When one validator serves several executors it is the longest remaining time among them; use getRemainingCooldown(UUID, Object, String) to address one (#539).

      Parameters:
      playerId - the player's UUID
      methodKey - the method key, method.toString() of the mapping method
      Returns:
      remaining seconds, or 0 if not on cooldown
    • getRemainingCooldown

      public long getRemainingCooldown(UUID playerId, Object executor, String methodKey)
      Gets one executor's remaining cooldown of a method, in seconds (#539).
      Parameters:
      playerId - the player's UUID
      executor - the executor instance the cooldown was recorded on
      methodKey - the method key, method.toString() of the mapping method
      Returns:
      remaining seconds, or 0 if not on cooldown
      Since:
      6.3.0
    • remainingSeconds

      private static long remainingSeconds(long endTime)
    • isLive

      private static boolean isLive(Object key)
      A key whose executor has been garbage collected can never be matched again.
    • cleanupExpired

      public void cleanupExpired()
      Cleans up expired cooldowns to prevent memory leaks. Should be called periodically. Also drops the cooldowns of an executor that has since been garbage collected (a module that unloaded), which no dispatch can reach any more.
    • isBoundMapping

      private static boolean isBoundMapping(Method method, CommandContext context)
      Returns:
      whether the @CmdCD that governs method is bound to a config key
    • getCooldownSeconds

      private Integer getCooldownSeconds(Method method, CommandContext context)
    • unresolvedBinding

      private CommandValidator.ValidationResult unresolvedBinding(CommandContext context, Method method)
      Fails the command closed for a config-bound @CmdCD this validator never had resolved -- reachable only when a CooldownValidator is added after the module loaded, or an executor is registered outside the module loader. Treating it as "no cooldown" would be a silent no-op, and throwing would escape onCommand as Bukkit's generic internal error; instead the player gets the same message shape as a failed command, and the cause is logged at SEVERE and sent to the error report collector like any other command failure (#531 gate-1 WR-02).
    • reportUnresolvedBinding

      private static void reportUnresolvedBinding(CommandContext context, IllegalStateException cause)
    • bindingKey

      @Internal public static String bindingKey(CmdCD cmdCD)
      The cache key of a config-bound @CmdCD: its config class and key.
      Parameters:
      cmdCD - the annotation
      Returns:
      configClassName#key, or null when cmdCD is unbound
      Since:
      6.3.0
    • getOrder

      public int getOrder()
      Description copied from interface: CommandValidator
      Gets the order of this validator. Lower values are executed first.
      Specified by:
      getOrder in interface CommandValidator
      Returns:
      the order value
    • getName

      public String getName()
      Description copied from interface: CommandValidator
      Gets the name of this validator for debugging and logging purposes.
      Specified by:
      getName in interface CommandValidator
      Returns:
      the validator name