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

public class UsageLockValidator extends Object implements CommandValidator, PlayerCacheSaver
Validates and manages command usage locks to prevent concurrent execution. Supports sender-specific and server-wide locks.

Acquire-then-execute (D-02, GEN-09, @since 6.3.0): acquisition is no longer a separate step called by field from BaseCommandExecutor.executeCommand. It happens inside validate(CommandContext) itself -- "acquire-as-you-validate" -- so that on the synchronous dispatch path there is no scheduling point between deciding the lock is free and taking it. A failed acquisition surfaces as an ordinary CommandValidator.ValidationResult.failure(java.lang.String) through the normal validation-rejection path; the mapped method is never invoked. Release happens in onComplete(CommandContext, boolean), driven by the validator chain that actually ran this validator (see CommandValidator.onComplete(CommandContext, boolean)) -- never called twice, and never called for an invocation whose acquisition failed -- or, when the dispatch is refused after this validator acquired the lock but before the method runs, in onRefused(CommandContext) (#568).

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:
    • senderLocks

      private final Map<UUID,Set<String>> senderLocks
      Locks per sender: player UUID -> set of locked method keys

      saveBeforeRemove = true so savePlayerData(UUID) -- which delegates to the pre-existing clearPlayerLocks(UUID), clearing BOTH this field and serverLocks in one call -- fires exactly once per quit (GEN-08, D-03). serverLocks deliberately does NOT also carry saveBeforeRemove = true: doing so would fire savePlayerData(UUID) a second, redundant time per quit.

    • serverLocks

      private final Map<String,UUID> serverLocks
      Server-wide locks: method key -> locking player UUID
    • playerCacheRegistered

      private volatile boolean playerCacheRegistered
      True once this instance has registered senderLocks/serverLocks with the live PlayerCacheManager for quit-based sweeping (GEN-08, D-03). Set lazily from validate(CommandContext) rather than the constructor, mirroring CooldownValidator's identical field of the same name and rationale: a bare new UsageLockValidator() must never attempt contact with a core plugin that may not exist yet, and a failed attempt is retried on the next call rather than permanently abandoned.

      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).

  • Constructor Details

    • UsageLockValidator

      public UsageLockValidator()
  • Method Details

    • validate

      Validates -- and, per the acquire-then-execute contract on this class, ATTEMPTS TO ACQUIRE -- the lock for this invocation. Delegates entirely to acquireLock(CommandContext): a successful acquisition is a successful validation; a failed acquisition is reported as an ordinary validation failure carrying the scope-appropriate i18n key.
      Specified by:
      validate in interface CommandValidator
      Parameters:
      context - the command context
      Returns:
      the validation result
    • onComplete

      public void onComplete(CommandContext context, boolean commandSucceeded)
      Post-action hook that releases the lock acquired by validate(CommandContext) for this invocation. Delegates to releaseLock(CommandContext) and is invoked only by a chain that actually ran this validator for the current dispatch -- see CommandValidator.onComplete(CommandContext, boolean). An invocation whose acquisition failed never reaches this hook (it is absent from the chain's passed-validator list), so there is nothing here to release on that path.
      Specified by:
      onComplete in interface CommandValidator
      Parameters:
      context - the command context
      commandSucceeded - ignored -- the lock releases whether the mapped method succeeded or threw
      Since:
      6.3.0
    • onRefused

      public void onRefused(CommandContext context)
      Releases the lock this validator acquired for a dispatch that was then refused before the mapped method ran -- by a later validator (such as the cooldown, which runs after this one), the argument-count check, or parameter parsing. Before 6.3.0 nothing released it on those paths, and the lock stayed held until the player quit (#568).
      Specified by:
      onRefused in interface CommandValidator
      Parameters:
      context - the command context
      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; the locks it holds are left as they are (clearAllLocks() is the teardown's separate step).

      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) senderLocks/serverLocks entries. Delegates to the pre-existing clearPlayerLocks(UUID) rather than re-deriving its predicate -- the SAME predicate PlayerCacheManager's own value-side sweep branch was built from -- giving that previously zero-caller method a real, quit-path-reached production call site (GEN-08). Only senderLocks carries saveBeforeRemove = true, so this fires exactly once per quit despite clearing both maps.
      Specified by:
      savePlayerData in interface PlayerCacheSaver
      Parameters:
      playerId - the UUID of the player quitting
      Since:
      6.3.0
    • acquireLock

      public boolean acquireLock(CommandContext context)
      Acquires a lock for command execution. Should be called before executing the command. As of 6.3.0 this is also the method validate(CommandContext) itself calls -- see the class-level acquire-then-execute note.
      Parameters:
      context - the command context
      Returns:
      true if lock was acquired, false if already locked
    • releaseLock

      public void releaseLock(CommandContext context)
      Releases a lock after command execution. Should be called after command execution completes (success or failure). As of 6.3.0 this is also the method onComplete(CommandContext, boolean) calls -- see the class-level acquire-then-execute note.
      Parameters:
      context - the command context
    • resolveLimit

      private static UsageLimit resolveLimit(Method method, Class<?> executorClass)
      Resolves the effective @UsageLimit for method: the method's own declaration, falling back to a class-level one on the CONCRETE executor class dispatching this command, falling back to a class-level one on the method's declaring class -- most-derived-wins, via ReflectionUtil.resolveMethodOrClassAnnotation(Method, Class, Class). This is the SAME resolution PluginManager's load-time refusal treats as satisfying the contract (SILENT-11 / D-01 follow-up, WR-02 / 05-REVIEW.md fix): a class-level @UsageLimit that passes the load-time check -- whether declared on a shared abstract base or on the concrete executor class itself -- now actually locks every inherited mapping that does not declare its own.
      Parameters:
      method - the matched command mapping method, or null
      executorClass - the concrete executor class dispatching this command (WR-02, 05-REVIEW.md), or null when unavailable -- falls back to the pre-WR-02, declaring-class-only resolution in that case
      Returns:
      the resolved annotation, or null when method is null or neither the method, executorClass, nor its declaring class carries one
      Since:
      6.3.0
    • clearPlayerLocks

      public void clearPlayerLocks(UUID playerId)
      Clears all locks for a player. Useful when a player disconnects.
      Parameters:
      playerId - the player's UUID
    • clearAllLocks

      public void clearAllLocks()
      Clears all locks.
    • isLocked

      public boolean isLocked(UUID playerId, String methodKey)
      Checks if a specific method is locked for a player.
      Parameters:
      playerId - the player's UUID
      methodKey - the method key
      Returns:
      true if locked
    • 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