Class CmdTargetComposition

java.lang.Object
com.ultikits.ultitools.abstracts.command.validation.CmdTargetComposition

public final class CmdTargetComposition extends Object
The single implementation of @CmdTarget's class-versus-method composition rule.

Before this class existed, the rule was implemented twice and disagreed with itself: AbstractCommandExecutor (removed in 6.3.0) required both the class-level and method-level checks to pass (intersection), while SenderTypeValidator let the method-level value fully replace the class-level one with no check at all (unguarded override). Migrating a command class between the two executor generations silently changed who could invoke it - no compiler signal on either side. See issue #320 and D-01 in .planning/phases/01-adjudication-foundations-compatibility-baseline/01-ADJUDICATION.md.

D-01 settles this as narrowing-only override: a method-level value may only narrow the class-level one. CmdTarget.CmdTargetType has three values and is not a total order, so there are three outcomes for a differing pair, not two - CmdTargetComposition.Transition.WIDENING and CmdTargetComposition.Transition.LATERAL are both refused, and are refused identically. Treating PLAYER-to-CONSOLE as a widening would be arbitrary; treating it as legal would let a class-level PLAYER restriction be turned into a CONSOLE-only method with no signal at all. The legal cases - CmdTargetComposition.Transition.SAME and CmdTargetComposition.Transition.NARROWING - are exactly the cases where the old intersection and override readings already agreed, which is what makes migrating base class behaviour-preserving for them.

classify(com.ultikits.ultitools.annotations.command.CmdTarget.CmdTargetType, com.ultikits.ultitools.annotations.command.CmdTarget.CmdTargetType) is the cheap, string-free predicate; check(java.lang.Class<?>) is the diagnostic that builds a description only for a method that actually violates - the same split aop/AopEligibility uses, for the same reason: building a remediation string for every method and discarding most of them is wasted work on the plugin-load path.

Since:
6.3.0
Author:
wisdomme
  • Constructor Details

    • CmdTargetComposition

      private CmdTargetComposition()
  • Method Details

    • classify

      public static CmdTargetComposition.Transition classify(CmdTarget.CmdTargetType classLevel, CmdTarget.CmdTargetType methodLevel)
      Classifies the relationship between a class-level and a method-level @CmdTarget value.

      Implemented as an exhaustive three-by-three decision rather than an ordinal comparison on CmdTarget.CmdTargetType: an ordinal comparison is exactly how the lateral case would get silently folded into narrowing or widening depending on declaration order, which is not a decision anyone made on purpose.

      Parameters:
      classLevel - the class-level @CmdTarget value; absent means BOTH
      methodLevel - the method-level @CmdTarget value
      Returns:
      the transition kind
    • resolve

      public static CmdTarget.CmdTargetType resolve(CmdTarget.CmdTargetType classLevel, Method method)
      Resolves the effective sender type for one invocation.

      This is the per-invocation path: by the time it runs, check(java.lang.Class<?>) has already refused any class whose composition is ambiguous, so this method does no classification, no string work, and throws nothing - it only reads the method's own annotation, if any.

      Parameters:
      classLevel - the class-level @CmdTarget value; pass BOTH when the class carries no annotation
      method - the matched command method; may carry a method-level @CmdTarget
      Returns:
      the method-level value when present, otherwise classLevel
    • check

      public static List<String> check(Class<?> commandClass)
      Checks a command class for ambiguous @CmdTarget composition.

      Pure reflection - no instance required, safe to call before the class becomes a bean definition. Absent class-level annotation is treated as BOTH, so a method-level restriction on an otherwise-unrestricted class is always a narrowing, never a violation.

      Parameters:
      commandClass - the command class to check
      Returns:
      one human-readable violation per WIDENING or LATERAL method; empty for a legal class
    • describe

      private static String describe(Class<?> commandClass, Method method, CmdTarget.CmdTargetType classLevel, CmdTarget.CmdTargetType methodLevel, CmdTargetComposition.Transition transition)
      Builds the violation description for one offending method. Only called for a method that has already been classified as WIDENING or LATERAL - never built speculatively for every annotated method, per aop/AopEligibility's house precedent.