Class MergedAnnotationResolver
AliasFor.
Nearest selection (decision P-10). The occurrence of the requested annotation nearest
the class is the one resolved: a directly declared annotation (distance 0) wins over any
meta-present one, then meta-annotations are searched breadth-first by distance with declaration
order breaking ties, and a superclass is consulted only when the class's own annotation trees
hold no occurrence. This follows Spring's @ComponentScan javadoc: "Locally declared
@ComponentScan annotations always take precedence over and effectively hide
@ComponentScan meta-annotations, which allows explicit local configuration to override
configuration that is meta-present." A module that declares both @UltiToolsModule and a
direct @ComponentScan (or @EnableAutoRegister) therefore gets the direct one,
whatever order the two are written in (C45).
AnnotationUtils.findAnnotation(java.lang.Class<?>, java.lang.Class<T>) only walks one
meta-annotation level and never inspects AliasFor at all, which is why
@UltiToolsModule.scanBasePackages works today (its call site hand-reads the
meta-annotation) while every other @AliasFor attribute on that same annotation is
declared but has no reader. Per-site special-casing was rejected (D-01) because it does not
remove the defect class, it only moves it to the next attribute nobody wired up yet -- this
class is the single general replacement.
Deliberately cacheless. Phase 1 (D-35/D-38) forbids a static Class-keyed cache
anywhere in this codebase: it would pin every module's Class objects for the lifetime
of the JVM and defeat the plugin class loader's release on module unload (see
AnnotationLookupCache's class javadoc for the full
reasoning). The walk this class performs runs once per class at scan/load time, not on a
per-invocation hot path, so a v1 without a cache is a deliberate choice, not an oversight. Any
future cache added here must be instance-scoped and owned by the container/scanner instance
that uses it -- never a static field on this class.
This type is @ApiStatus.Internal: module authors need
@AliasFor to work, not to call this resolver directly (D-04).
- Since:
- 6.3.0
-
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionprivate static final classOne attribute's declared@AliasFortarget, for reciprocity checking.private static final classOne candidate value for an attribute, with the annotation instance that declared it, that instance's distance from the class (0 = declared on the class), and the declaring attribute's name (for conflict messages).private static final classOne annotation instance reached byfindNearest(java.lang.Class<?>, java.lang.Class<? extends java.lang.annotation.Annotation>, java.util.Set<java.lang.Class<? extends java.lang.annotation.Annotation>>), linked to the one it was found on. -
Field Summary
Fields -
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionprivate static voidaddAllDistinct(List<MergedAnnotationResolver.Candidate> target, List<MergedAnnotationResolver.Candidate> source) private static voidaddCandidateIfNonDefault(Map<String, List<MergedAnnotationResolver.Candidate>> out, Method sourceMethod, Annotation sourceInstance, int distance) private static Map<String, List<MergedAnnotationResolver.Candidate>> collectOverrideCandidates(List<Annotation> path, int level, List<Map<String, List<MergedAnnotationResolver.Candidate>>> outerLevels) Collect the candidate values for every attribute of the annotation atpath[level], given the already-computed candidates of every level above it (outerLevels, indexed by distance from the class): Forwarded. For each outer level, each attribute declaring@AliasFor(annotation = <this level's type>)forwards all of its own candidates to the aliased attribute here.private static <A extends Annotation>
AcreateProxy(A original, Class<A> annotationType, Map<String, Object> overrides) private static voidenqueue(Deque<MergedAnnotationResolver.Occurrence> queue, Annotation annotation, MergedAnnotationResolver.Occurrence parent, Set<Class<? extends Annotation>> visited) static <A extends Annotation>
AFind an annotation on a class, or on any of its (meta-)annotations, or on its superclass hierarchy -- generalizingAnnotationUtils.findAnnotationfrom one meta-annotation level to the whole annotation tree, and merging in any@AliasForoverride found along the path to the selected occurrence.(package private) static MethodfindAttributeMethod(Class<? extends Annotation> type, String name) private static MergedAnnotationResolver.OccurrencefindNearest(Class<?> clazz, Class<? extends Annotation> annotationType, Set<Class<? extends Annotation>> visited) Breadth-first search of one class's annotation graph for the occurrence ofannotationTypenearest the class: its directly declared annotations are distance 0, their meta-annotations distance 1, and so on; at equal distance, declaration order breaks the tie.private static <A extends Annotation>
AfindOnClassHierarchy(Class<?> clazz, Class<A> annotationType, Set<Class<? extends Annotation>> visited) Nearest selection over the class hierarchy (decision P-10): the class's own annotation trees are searched completely before its superclass is consulted.private static ObjectinvokeAttribute(Method method, Annotation instance) private static booleanisJavaLangAnnotation(Class<? extends Annotation> type) private static booleanisMetaPresent(Class<? extends Annotation> declaring, Class<? extends Annotation> target) Whethertargetis present, directly or transitively, ondeclaring's own meta-annotation graph -- Spring's "meta-present" requirement for an explicit@AliasFor(annotation = ...).private static booleanisMetaPresent(Class<? extends Annotation> current, Class<? extends Annotation> target, Set<Class<? extends Annotation>> visited) static booleanisPresent(Class<?> clazz, Class<? extends Annotation> annotationType) WhetherannotationTypeis present anywhere onclazz's annotation tree or superclass hierarchy.private static <A extends Annotation>
Amerge(Annotation found, Class<A> annotationType, List<Annotation> ancestors) Compute the merged attribute view offound(the located instance ofannotationType) by walking the selected path top-down (decision P-10): from the annotation declared on the class, through every intermediate meta-annotation, down tofound.private static StringresolveAttributeName(AliasFor aliasFor, Method declaringMethod) private static ObjectresolveNearest(Class<? extends Annotation> annotationType, String attribute, List<MergedAnnotationResolver.Candidate> candidates) Apply the per-instance conflict rule and nearest-wins rule (seemerge(java.lang.annotation.Annotation, java.lang.Class<A>, java.util.List<java.lang.annotation.Annotation>)) to one attribute's candidates.private static ContainerExceptionselfContradiction(Class<? extends Annotation> annotationType, String attribute, MergedAnnotationResolver.Candidate conflicting, List<MergedAnnotationResolver.Candidate> candidates) static voidvalidateAliases(Class<? extends Annotation> annotationType) Validate that everyAliasFor-annotated attribute declared onannotationTypesatisfies Spring's documented Implementation Requirements for an explicit meta-annotation alias, throwing aContainerExceptionnaming the annotation and the offending attribute when it does not (D-02).
-
Field Details
-
JAVA_LANG_ANNOTATION_PACKAGE
- See Also:
-
-
Constructor Details
-
MergedAnnotationResolver
private MergedAnnotationResolver()
-
-
Method Details
-
find
Find an annotation on a class, or on any of its (meta-)annotations, or on its superclass hierarchy -- generalizingAnnotationUtils.findAnnotationfrom one meta-annotation level to the whole annotation tree, and merging in any@AliasForoverride found along the path to the selected occurrence.The occurrence nearest the class is selected (see the class javadoc): a directly declared
annotationTypehides every meta-present one, and the class's own annotations are exhausted before its superclass is searched.- Type Parameters:
A- the annotation type- Parameters:
clazz- the class to searchannotationType- the annotation type to find- Returns:
- the merged annotation if found, or
null
-
isPresent
WhetherannotationTypeis present anywhere onclazz's annotation tree or superclass hierarchy.- Parameters:
clazz- the class to searchannotationType- the annotation type to look for- Returns:
trueif present
-
validateAliases
Validate that everyAliasFor-annotated attribute declared onannotationTypesatisfies Spring's documented Implementation Requirements for an explicit meta-annotation alias, throwing aContainerExceptionnaming the annotation and the offending attribute when it does not (D-02).The structural checks land in this class's Task 2 commit; this stub exists so every caller of
find(java.lang.Class<?>, java.lang.Class<A>)is already wired against the real method signature before the checks themselves are filled in.- Parameters:
annotationType- the annotation type to validate
-
isMetaPresent
private static boolean isMetaPresent(Class<? extends Annotation> declaring, Class<? extends Annotation> target) Whethertargetis present, directly or transitively, ondeclaring's own meta-annotation graph -- Spring's "meta-present" requirement for an explicit@AliasFor(annotation = ...). Cycle-guarded the same wayfindNearest(java.lang.Class<?>, java.lang.Class<? extends java.lang.annotation.Annotation>, java.util.Set<java.lang.Class<? extends java.lang.annotation.Annotation>>)is. -
isMetaPresent
private static boolean isMetaPresent(Class<? extends Annotation> current, Class<? extends Annotation> target, Set<Class<? extends Annotation>> visited) -
findOnClassHierarchy
private static <A extends Annotation> A findOnClassHierarchy(Class<?> clazz, Class<A> annotationType, Set<Class<? extends Annotation>> visited) Nearest selection over the class hierarchy (decision P-10): the class's own annotation trees are searched completely before its superclass is consulted.visitedis shared across the wholefind(java.lang.Class<?>, java.lang.Class<A>)call so each annotation type is entered at most once; a type already entered on a subclass cannot lead to the target on the superclass either, because its whole subtree was already searched. -
findNearest
private static MergedAnnotationResolver.Occurrence findNearest(Class<?> clazz, Class<? extends Annotation> annotationType, Set<Class<? extends Annotation>> visited) Breadth-first search of one class's annotation graph for the occurrence ofannotationTypenearest the class: its directly declared annotations are distance 0, their meta-annotations distance 1, and so on; at equal distance, declaration order breaks the tie. A directly declared target therefore always wins over a meta-present one, whatever order the annotations are written in -- Spring's rule that a locally declared annotation "always take[s] precedence over and effectively hide[s]" a meta-present one (@ComponentScanjavadoc).visitedis shared across the wholefind(java.lang.Class<?>, java.lang.Class<A>)call (not reset per branch) so a cyclic meta-annotation graph terminates (T-03-03): once an annotation type has been enqueued anywhere in this call, it is never enqueued again. Every annotation type the search enters is passed tovalidateAliases(java.lang.Class<? extends java.lang.annotation.Annotation>), as the depth-first walk did.- Returns:
- the nearest occurrence with its parent chain, or
nullif this class's own annotation graph holds none
-
enqueue
private static void enqueue(Deque<MergedAnnotationResolver.Occurrence> queue, Annotation annotation, MergedAnnotationResolver.Occurrence parent, Set<Class<? extends Annotation>> visited) -
merge
private static <A extends Annotation> A merge(Annotation found, Class<A> annotationType, List<Annotation> ancestors) Compute the merged attribute view offound(the located instance ofannotationType) by walking the selected path top-down (decision P-10): from the annotation declared on the class, through every intermediate meta-annotation, down tofound. Each level's candidate values are computed bycollectOverrideCandidates(java.util.List<java.lang.annotation.Annotation>, int, java.util.List<java.util.Map<java.lang.String, java.util.List<com.ultikits.ultitools.context.MergedAnnotationResolver.Candidate>>>)from the levels above it, so a value set on an outer annotation travels through every intermediate@AliasForto the final target (C46): an attribute aliasing@UltiToolsModule.scanBasePackagesreaches@ComponentScan.basePackages, becausescanBasePackagesitself aliases it.Every candidate records the annotation instance that declared it and that instance's distance from the class. For each attribute of
foundthe rule is (C47):- if one annotation instance supplies two different values -- through a mutual alias, or
through two routes that reach the same final attribute -- the declaration contradicts
itself and is refused with a
ContainerExceptionnaming that instance's annotation; - otherwise the value declared by the instance nearest the class wins. Values from different instances never conflict: an outer override beats a value an inner meta-annotation declares.
@AliasForcontract: an explicit meta-annotation attribute override wins over the meta-annotation's own value, the lower level (closer to the annotated element) wins, and only an attribute and its alias declared with different values in one annotation is an error. - if one annotation instance supplies two different values -- through a mutual alias, or
through two routes that reach the same final attribute -- the declaration contradicts
itself and is refused with a
-
resolveNearest
private static Object resolveNearest(Class<? extends Annotation> annotationType, String attribute, List<MergedAnnotationResolver.Candidate> candidates) Apply the per-instance conflict rule and nearest-wins rule (seemerge(java.lang.annotation.Annotation, java.lang.Class<A>, java.util.List<java.lang.annotation.Annotation>)) to one attribute's candidates. Each level of the path is exactly one annotation instance, so the candidates' distance identifies their declaring instance. -
selfContradiction
private static ContainerException selfContradiction(Class<? extends Annotation> annotationType, String attribute, MergedAnnotationResolver.Candidate conflicting, List<MergedAnnotationResolver.Candidate> candidates) -
collectOverrideCandidates
private static Map<String,List<MergedAnnotationResolver.Candidate>> collectOverrideCandidates(List<Annotation> path, int level, List<Map<String, List<MergedAnnotationResolver.Candidate>>> outerLevels) Collect the candidate values for every attribute of the annotation atpath[level], given the already-computed candidates of every level above it (outerLevels, indexed by distance from the class):- Forwarded. For each outer level, each attribute declaring
@AliasFor(annotation = <this level's type>)forwards all of its own candidates to the aliased attribute here. A forwarded candidate keeps the instance that originally declared it -- an intermediate annotation only relays it. - Own. Each of this annotation's attributes that declares
@AliasForand whose value differs from its declared default is a candidate for itself, declared by this instance. (Attributes taking part in no alias never need a candidate: the merged view falls back to the found instance for them.) - Mirrored. Two attributes of this annotation that alias each other (a
within-annotation mutual alias) share one candidate list, so
foo=xwithbar=ysurfaces as two different values for the same attribute.
merge(java.lang.annotation.Annotation, java.lang.Class<A>, java.util.List<java.lang.annotation.Annotation>)applies the rule once, at the found annotation -- two different values from one instance are refused, otherwise the instance nearest the class wins -- which is Spring's@AliasForcontract (explicit meta-annotation overrides, transitive aliases, lower level wins, same-annotation contradiction is an error). - Forwarded. For each outer level, each attribute declaring
-
addAllDistinct
private static void addAllDistinct(List<MergedAnnotationResolver.Candidate> target, List<MergedAnnotationResolver.Candidate> source) -
addCandidateIfNonDefault
private static void addCandidateIfNonDefault(Map<String, List<MergedAnnotationResolver.Candidate>> out, Method sourceMethod, Annotation sourceInstance, int distance) -
resolveAttributeName
-
findAttributeMethod
-
invokeAttribute
-
isJavaLangAnnotation
-
createProxy
private static <A extends Annotation> A createProxy(A original, Class<A> annotationType, Map<String, Object> overrides)
-