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.

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