Class MergedAnnotationResolver

java.lang.Object
com.ultikits.ultitools.context.MergedAnnotationResolver

@Internal public final class MergedAnnotationResolver extends Object
General merged-annotation resolution: a recursive walk of the whole annotation tree, with explicit meta-annotation attribute override via AliasFor.

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
  • Field Details

  • Constructor Details

    • MergedAnnotationResolver

      private MergedAnnotationResolver()
  • Method Details

    • find

      public static <A extends Annotation> A find(Class<?> clazz, Class<A> annotationType)
      Find an annotation on a class, or on any of its (meta-)annotations, or on its superclass hierarchy -- generalizing AnnotationUtils.findAnnotation from one meta-annotation level to the whole annotation tree, and merging in any @AliasFor override found along the way.
      Type Parameters:
      A - the annotation type
      Parameters:
      clazz - the class to search
      annotationType - the annotation type to find
      Returns:
      the merged annotation if found, or null
    • isPresent

      public static boolean isPresent(Class<?> clazz, Class<? extends Annotation> annotationType)
      Whether annotationType is present anywhere on clazz's annotation tree or superclass hierarchy.
      Parameters:
      clazz - the class to search
      annotationType - the annotation type to look for
      Returns:
      true if present
    • validateAliases

      public static void validateAliases(Class<? extends Annotation> annotationType)
      Validate that every AliasFor-annotated attribute declared on annotationType satisfies Spring's documented Implementation Requirements for an explicit meta-annotation alias, throwing a ContainerException naming 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)
      Whether target is present, directly or transitively, on declaring's own meta-annotation graph -- Spring's "meta-present" requirement for an explicit @AliasFor(annotation = ...). Cycle-guarded the same way search(java.lang.annotation.Annotation, java.lang.Class<A>, java.util.Set<java.lang.Class<? extends java.lang.annotation.Annotation>>, java.util.List<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)
    • search

      private static <A extends Annotation> A search(Annotation current, Class<A> annotationType, Set<Class<? extends Annotation>> visited, List<Annotation> ancestors)
      Depth-first walk of one annotation's own meta-annotation tree. visited is shared across the whole find(java.lang.Class<?>, java.lang.Class<A>) call (not reset per branch) so a cyclic meta-annotation graph terminates instead of overflowing the stack (T-03-03) -- once an annotation type has been visited anywhere in this call, it is never walked into again.
    • merge

      private static <A extends Annotation> A merge(Annotation found, Class<A> annotationType, List<Annotation> ancestors)
      Compute the merged attribute view of found (the located instance of annotationType), applying any @AliasFor override declared by an annotation on the path from the scanned class down to found -- including found itself, for a within-annotation mutual alias.
    • collectOverrideCandidates

      private static <A extends Annotation> void collectOverrideCandidates(Annotation dInstance, Class<A> target, Map<String,List<MergedAnnotationResolver.Candidate>> out)
      Collect every attribute-value override dInstance contributes towards target's merged view: for each of dInstance's own attribute methods carrying @AliasFor, if the alias points at target (explicitly, or via the Annotation.class default meaning "within this same annotation") and the method's value on dInstance differs from that method's own declared default, it is a candidate override for the aliased attribute name on target.

      When dInstance IS an instance of target itself, any attribute that participates in a within-annotation mutual alias also contributes its OWN declared value as a second candidate for its own attribute name -- otherwise a conflicting pair (foo=x aliased to bar, bar=y aliased to foo) would only ever compare "the other side's value" against itself, and the conflict between x and y would never surface as more than one candidate for the same attribute.

    • addCandidateIfNonDefault

      private static void addCandidateIfNonDefault(Map<String,List<MergedAnnotationResolver.Candidate>> out, String attributeName, Method sourceMethod, Annotation sourceInstance, Class<? extends Annotation> sourceType)
    • resolveAttributeName

      private static String resolveAttributeName(AliasFor aliasFor, Method declaringMethod)
    • findAttributeMethod

      static Method findAttributeMethod(Class<? extends Annotation> type, String name)
    • invokeAttribute

      private static Object invokeAttribute(Method method, Annotation instance)
    • isJavaLangAnnotation

      private static boolean isJavaLangAnnotation(Class<? extends Annotation> type)
    • createProxy

      private static <A extends Annotation> A createProxy(A original, Class<A> annotationType, Map<String,Object> overrides)