Class CmdTargetComposition
@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
-
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionstatic enumHow a method-level@CmdTargetvalue relates to the class-level one. -
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionChecks a command class for ambiguous@CmdTargetcomposition.classify(CmdTarget.CmdTargetType classLevel, CmdTarget.CmdTargetType methodLevel) Classifies the relationship between a class-level and a method-level@CmdTargetvalue.private static Stringdescribe(Class<?> commandClass, Method method, CmdTarget.CmdTargetType classLevel, CmdTarget.CmdTargetType methodLevel, CmdTargetComposition.Transition transition) Builds the violation description for one offending method.static CmdTarget.CmdTargetTyperesolve(CmdTarget.CmdTargetType classLevel, Method method) Resolves the effective sender type for one invocation.
-
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@CmdTargetvalue.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@CmdTargetvalue; absent means BOTHmethodLevel- the method-level@CmdTargetvalue- Returns:
- the transition kind
-
resolve
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@CmdTargetvalue; pass BOTH when the class carries no annotationmethod- the matched command method; may carry a method-level@CmdTarget- Returns:
- the method-level value when present, otherwise
classLevel
-
check
Checks a command class for ambiguous@CmdTargetcomposition.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.
Two sets of methods are checked. First, the class's effective command mappings, exactly as dispatch registers them (
ReflectionUtil.getEffectiveCommandMappings(Class)): an inherited mapping widening or laterally switching the class-level value is refused here, not run by a sender the class excludes (case 57). Second, every method the class itself declares with@CmdTarget, whether or not it is a mapping - the check that existed before inherited mappings were added - so no class refused before is accepted now. A violation for an inherited method also names the class that declared it.- 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, peraop/AopEligibility's house precedent.
-