This is an automated email from the ASF dual-hosted git repository.

jamesbognar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/juneau.git


The following commit(s) were added to refs/heads/master by this push:
     new 7017c27a97 org.apache.juneau.common.reflect API improvements
7017c27a97 is described below

commit 7017c27a97863cf78d5c1d5347f3df7d86f4ca06
Author: James Bognar <[email protected]>
AuthorDate: Tue Nov 18 15:52:34 2025 -0500

    org.apache.juneau.common.reflect API improvements
---
 .../juneau/common/reflect/AnnotationProvider.java  | 77 +++++++++++++++++++---
 .../apache/juneau/common/reflect/ClassInfo.java    | 15 +++--
 .../juneau/common/reflect/ExecutableInfo.java      | 10 ++-
 .../apache/juneau/common/reflect/FieldInfo.java    | 13 +++-
 .../apache/juneau/common/reflect/PackageInfo.java  |  2 +-
 .../juneau/common/reflect/ParameterInfo.java       | 13 +++-
 .../org/apache/juneau/common/utils/ClassUtils.java | 45 ++++++-------
 7 files changed, 130 insertions(+), 45 deletions(-)

diff --git 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/AnnotationProvider.java
 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/AnnotationProvider.java
index 16f83b4a47..87792a2024 100644
--- 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/AnnotationProvider.java
+++ 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/AnnotationProvider.java
@@ -514,6 +514,67 @@ public class AnnotationProvider {
                        .map(a -> (AnnotationInfo<A>)a);
        }
 
+       /**
+        * Finds all annotations of the specified type on the specified class 
in a bottom-up traversal order.
+        *
+        * <p>
+        * This method provides a very specific traversal order that searches 
annotations in the following sequence:
+        * <ol>
+        *      <li><b>This class</b>
+        *              <ul>
+        *                      <li>Runtime annotations
+        *                      <li>Declared annotations
+        *              </ul>
+        *      <li><b>Parent classes (child-to-parent order)</b>
+        *              <ul>
+        *                      <li>For each parent: runtime annotations, then 
declared annotations
+        *              </ul>
+        *      <li><b>Interfaces (for each class level, then their parent 
interfaces)</b>
+        *              <ul>
+        *                      <li>For each interface: runtime annotations, 
then declared annotations
+        *              </ul>
+        *      <li><b>Package of this class</b>
+        *              <ul>
+        *                      <li>Declared annotations (packages do not 
support runtime annotations)
+        *              </ul>
+        * </ol>
+        *
+        * <p>
+        * <b>Example traversal order</b> (given class {@code Child extends 
Parent implements I1, I2}):
+        * <ol>
+        *      <li>Child runtime annotations
+        *      <li>Child declared annotations
+        *      <li>Parent runtime annotations
+        *      <li>Parent declared annotations
+        *      <li>I1 runtime annotations (declared on Child)
+        *      <li>I1 declared annotations
+        *      <li>I2 runtime annotations (declared on Child)
+        *      <li>I2 declared annotations
+        *      <li>Parent interfaces and their parents...
+        *      <li>Package declared annotations
+        * </ol>
+        *
+        * <p>
+        * <b>Comparison with {@link #find(Class, Class)}:</b>
+        * <ul>
+        *      <li>{@link #find(Class, Class)} interleaves parent classes and 
interfaces at each level
+        *      <li>{@code findBottomUp} processes all parent classes first, 
then all interfaces, then package
+        *      <li>{@code findBottomUp} ensures runtime annotations always 
come before declared annotations at each level (except packages which don't 
support runtime annotations)
+        * </ul>
+        *
+        * @param <A> The annotation type to find.
+        * @param type The annotation type to find.
+        * @param onClass The class info to search on.
+        * @return A stream of {@link AnnotationInfo} objects in the specified 
bottom-up order. Never <jk>null</jk>.
+        */
+       @SuppressWarnings("unchecked")
+       public <A extends Annotation> Stream<AnnotationInfo<A>> 
findBottomUp(Class<A> type, ClassInfo onClass) {
+               assertArgNotNull("type", type);
+               assertArgNotNull("onClass", onClass);
+
+               return null;  // TODO
+       }
+
        /**
         * Finds annotations declared directly on the specified class, 
including runtime annotations.
         *
@@ -795,8 +856,7 @@ public class AnnotationProvider {
                if (nn(pkg)) {
                        var pi = PackageInfo.of(pkg.inner());
                        for (var a : pkg.inner().getAnnotations())
-                               for (var a2 : splitRepeated(a))
-                                       list.add(AnnotationInfo.of(pi, a2));
+                               streamRepeated(a).forEach(a2 -> 
list.add(AnnotationInfo.of(pi, a2)));
                }
 
                return u(list);
@@ -852,8 +912,7 @@ public class AnnotationProvider {
                var ci = ClassInfo.of(forClass);
                runtimeAnnotations.find(forClass).forEach(x -> 
appendTo.add(AnnotationInfo.of(ClassInfo.of(forClass), x)));
                for (var a : forClass.getDeclaredAnnotations())
-                       for (var a2 : splitRepeated(a))
-                               appendTo.add(AnnotationInfo.of(ci, a2));
+                       streamRepeated(a).forEach(a2 -> 
appendTo.add(AnnotationInfo.of(ci, a2)));
        }
 
        /**
@@ -968,7 +1027,7 @@ public class AnnotationProvider {
         * Streams annotations from a class using configurable traversal 
options in parent-first order.
         *
         * <p>
-        * This is equivalent to calling {@link #findAnnotations(Class, 
ClassInfo, AnnotationTraversal...)} 
+        * This is equivalent to calling {@link #findAnnotations(Class, 
ClassInfo, AnnotationTraversal...)}
         * and reversing the result.
         *
         * @param <A> The annotation type.
@@ -1026,7 +1085,7 @@ public class AnnotationProvider {
         * Streams annotations from a method using configurable traversal 
options in parent-first order.
         *
         * <p>
-        * This is equivalent to calling {@link #findAnnotations(Class, 
MethodInfo, AnnotationTraversal...)} 
+        * This is equivalent to calling {@link #findAnnotations(Class, 
MethodInfo, AnnotationTraversal...)}
         * and reversing the result.
         *
         * @param <A> The annotation type.
@@ -1084,7 +1143,7 @@ public class AnnotationProvider {
         * Streams annotations from a parameter using configurable traversal 
options in parent-first order.
         *
         * <p>
-        * This is equivalent to calling {@link #findAnnotations(Class, 
ParameterInfo, AnnotationTraversal...)} 
+        * This is equivalent to calling {@link #findAnnotations(Class, 
ParameterInfo, AnnotationTraversal...)}
         * and reversing the result.
         *
         * @param <A> The annotation type.
@@ -1134,7 +1193,7 @@ public class AnnotationProvider {
         * Streams annotations from a field using configurable traversal 
options in parent-first order.
         *
         * <p>
-        * This is equivalent to calling {@link #findAnnotations(Class, 
FieldInfo, AnnotationTraversal...)} 
+        * This is equivalent to calling {@link #findAnnotations(Class, 
FieldInfo, AnnotationTraversal...)}
         * and reversing the result.
         *
         * @param <A> The annotation type.
@@ -1184,7 +1243,7 @@ public class AnnotationProvider {
         * Streams annotations from a constructor using configurable traversal 
options in parent-first order.
         *
         * <p>
-        * This is equivalent to calling {@link #findAnnotations(Class, 
ConstructorInfo, AnnotationTraversal...)} 
+        * This is equivalent to calling {@link #findAnnotations(Class, 
ConstructorInfo, AnnotationTraversal...)}
         * and reversing the result.
         *
         * @param <A> The annotation type.
diff --git 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/ClassInfo.java
 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/ClassInfo.java
index b7de067fab..afc5057d64 100644
--- 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/ClassInfo.java
+++ 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/ClassInfo.java
@@ -225,7 +225,7 @@ public class ClassInfo extends ElementInfo implements 
Annotatable {
                this.componentType = memoize(this::findComponentType);
                this.packageInfo = memoize(() -> opt(inner).map(x -> 
x.getPackage()).filter(p -> p != null).map(PackageInfo::of).orElse(null));  // 
PackageInfo may be null for primitive types and arrays.
                this.parents = memoize(this::findParents);
-               this.declaredAnnotations = memoize(() -> (List)opt(inner).map(x 
-> u(l(x.getDeclaredAnnotations()))).orElse(liste()).stream().map(a -> 
AnnotationInfo.of(this, a)).toList());
+               this.declaredAnnotations = memoize(() -> (List)opt(inner).map(x 
-> u(l(x.getDeclaredAnnotations()))).orElse(liste()).stream().flatMap(a -> 
streamRepeated(a)).map(a -> AnnotationInfo.of(this, a)).toList());
                this.fullName = memoize(() -> getNameFormatted(FULL, true, '$', 
BRACKETS));
                this.shortName = memoize(() -> getNameFormatted(SHORT, true, 
'$', BRACKETS));
                this.readableName = memoize(() -> getNameFormatted(SIMPLE, 
false, '$', WORD));
@@ -2270,10 +2270,17 @@ public class ClassInfo extends ElementInfo implements 
Annotatable {
         * from parent classes. Each annotation is wrapped for additional 
functionality such as annotation member
         * access and metadata inspection.
         *
+        * <p>
+        * <b>Note on Repeatable Annotations:</b>
+        * Repeatable annotations (those marked with {@link 
java.lang.annotation.Repeatable @Repeatable}) are automatically
+        * expanded into their individual annotation instances. For example, if 
a class has multiple {@code @Bean} annotations,
+        * this method returns each {@code @Bean} annotation separately, rather 
than the container annotation.
+        *
         * @return
         *      An unmodifiable list of {@link AnnotationInfo} wrappers for 
annotations declared directly on this class.
         *      <br>List is empty if no annotations are declared.
         *      <br>Results are in declaration order.
+        *      <br>Repeatable annotations are expanded into individual 
instances.
         */
        public List<AnnotationInfo> getDeclaredAnnotations() {
                return declaredAnnotations.get();
@@ -2472,8 +2479,7 @@ public class ClassInfo extends ElementInfo implements 
Annotatable {
                        var ci = parentsAndInterfaces.get(i);
                        // Add declared annotations from this class/interface
                        for (var a : ci.inner().getDeclaredAnnotations())
-                               for (var a2 : splitRepeated(a))
-                                       list.add(AnnotationInfo.of(ci, a2));
+                               streamRepeated(a).forEach(a2 -> 
list.add(AnnotationInfo.of(ci, a2)));
                }
 
                // On the package of this class
@@ -2481,8 +2487,7 @@ public class ClassInfo extends ElementInfo implements 
Annotatable {
                if (nn(pkg)) {
                        var pi = PackageInfo.of(pkg.inner());
                        for (var a : pkg.inner().getAnnotations())
-                               for (var a2 : splitRepeated(a))
-                                       list.add(AnnotationInfo.of(pi, a2));
+                               streamRepeated(a).forEach(a2 -> 
list.add(AnnotationInfo.of(pi, a2)));
                }
 
                return u(list);
diff --git 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/ExecutableInfo.java
 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/ExecutableInfo.java
index 67d23bc045..0640d58312 100644
--- 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/ExecutableInfo.java
+++ 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/ExecutableInfo.java
@@ -180,7 +180,15 @@ public abstract class ExecutableInfo extends 
AccessibleInfo {
        /**
         * Returns the declared annotations on this executable.
         *
-        * @return The declared annotations on this executable as {@link 
AnnotationInfo} objects.
+        * <p>
+        * <b>Note on Repeatable Annotations:</b>
+        * Repeatable annotations (those marked with {@link 
java.lang.annotation.Repeatable @Repeatable}) are automatically
+        * expanded into their individual annotation instances. For example, if 
a method has multiple {@code @Bean} annotations,
+        * this method returns each {@code @Bean} annotation separately, rather 
than the container annotation.
+        *
+        * @return
+        *      The declared annotations on this executable as {@link 
AnnotationInfo} objects.
+        *      <br>Repeatable annotations are expanded into individual 
instances.
         */
        public final List<AnnotationInfo<Annotation>> getDeclaredAnnotations() 
{ return declaredAnnotations.get(); }
 
diff --git 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/FieldInfo.java
 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/FieldInfo.java
index 441f30955b..44cdb6b39d 100644
--- 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/FieldInfo.java
+++ 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/FieldInfo.java
@@ -19,6 +19,7 @@ package org.apache.juneau.common.reflect;
 import static org.apache.juneau.common.reflect.ClassArrayFormat.*;
 import static org.apache.juneau.common.reflect.ClassNameFormat.*;
 import static org.apache.juneau.common.utils.AssertionUtils.*;
+import static org.apache.juneau.common.utils.ClassUtils.*;
 import static org.apache.juneau.common.utils.CollectionUtils.*;
 import static org.apache.juneau.common.utils.Utils.*;
 
@@ -79,7 +80,7 @@ public class FieldInfo extends AccessibleInfo implements 
Comparable<FieldInfo>,
                this.declaringClass = declaringClass;
                this.inner = inner;
                this.type = memoize(() -> ClassInfo.of(inner.getType()));
-               this.declaredAnnotations = memoize(() -> 
stream(inner.getAnnotations()).map(a -> AnnotationInfo.of(this, a)).toList());
+               this.declaredAnnotations = memoize(() -> 
stream(inner.getAnnotations()).flatMap(a -> streamRepeated(a)).map(a -> 
AnnotationInfo.of(this, a)).toList());
                this.fullName = memoize(this::findFullName);
        }
 
@@ -97,7 +98,15 @@ public class FieldInfo extends AccessibleInfo implements 
Comparable<FieldInfo>,
        /**
         * Returns all annotations declared on this field.
         *
-        * @return An unmodifiable list of all annotations declared on this 
field.
+        * <p>
+        * <b>Note on Repeatable Annotations:</b>
+        * Repeatable annotations (those marked with {@link 
java.lang.annotation.Repeatable @Repeatable}) are automatically
+        * expanded into their individual annotation instances. For example, if 
a field has multiple {@code @Bean} annotations,
+        * this method returns each {@code @Bean} annotation separately, rather 
than the container annotation.
+        *
+        * @return
+        *      An unmodifiable list of all annotations declared on this field.
+        *      <br>Repeatable annotations are expanded into individual 
instances.
         */
        public List<AnnotationInfo<Annotation>> getDeclaredAnnotations() {
                return declaredAnnotations.get();
diff --git 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/PackageInfo.java
 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/PackageInfo.java
index 4e3b77ff96..5b15467e07 100644
--- 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/PackageInfo.java
+++ 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/PackageInfo.java
@@ -91,7 +91,7 @@ public class PackageInfo implements Annotatable {
        protected PackageInfo(Package inner) {
                assertArgNotNull("inner", inner);
                this.inner = inner;
-               this.annotations = memoize(() -> opt(inner).map(pkg -> 
stream(pkg.getAnnotations()).flatMap(a -> stream(splitRepeated(a))).map(a -> 
AnnotationInfo.of(this, a)).toList()).orElse(liste()));
+               this.annotations = memoize(() -> opt(inner).map(pkg -> 
stream(pkg.getAnnotations()).flatMap(a -> streamRepeated(a)).map(a -> 
AnnotationInfo.of(this, a)).toList()).orElse(liste()));
        }
 
        /**
diff --git 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/ParameterInfo.java
 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/ParameterInfo.java
index 305b1d6a54..2ed7df9c94 100644
--- 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/ParameterInfo.java
+++ 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/ParameterInfo.java
@@ -17,6 +17,7 @@
 package org.apache.juneau.common.reflect;
 
 
+import static org.apache.juneau.common.utils.ClassUtils.*;
 import static org.apache.juneau.common.utils.CollectionUtils.*;
 import static org.apache.juneau.common.utils.Utils.*;
 
@@ -80,7 +81,7 @@ public class ParameterInfo extends ElementInfo implements 
Annotatable {
                this.inner = inner;
                this.index = index;
                this.type = type;
-               this.declaredAnnotations = memoize(() -> 
stream(inner.getAnnotations()).map(a -> AnnotationInfo.of(this, a)).toList());
+               this.declaredAnnotations = memoize(() -> 
stream(inner.getAnnotations()).flatMap(a -> streamRepeated(a)).map(a -> 
AnnotationInfo.of(this, a)).toList());
                this.matchingParameters = memoize(this::findMatchingParameters);
        }
 
@@ -99,7 +100,15 @@ public class ParameterInfo extends ElementInfo implements 
Annotatable {
         * <p>
         * Returns annotations directly declared on this parameter, wrapped as 
{@link AnnotationInfo} objects.
         *
-        * @return An unmodifiable list of annotations on this parameter, never 
<jk>null</jk>.
+        * <p>
+        * <b>Note on Repeatable Annotations:</b>
+        * Repeatable annotations (those marked with {@link 
java.lang.annotation.Repeatable @Repeatable}) are automatically
+        * expanded into their individual annotation instances. For example, if 
a parameter has multiple {@code @Bean} annotations,
+        * this method returns each {@code @Bean} annotation separately, rather 
than the container annotation.
+        *
+        * @return
+        *      An unmodifiable list of annotations on this parameter, never 
<jk>null</jk>.
+        *      <br>Repeatable annotations are expanded into individual 
instances.
         */
        public List<AnnotationInfo<Annotation>> getAnnotations() {
                return declaredAnnotations.get();
diff --git 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/utils/ClassUtils.java
 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/utils/ClassUtils.java
index be6b50dd2c..be981c5418 100644
--- 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/utils/ClassUtils.java
+++ 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/utils/ClassUtils.java
@@ -25,6 +25,7 @@ import java.lang.annotation.*;
 import java.lang.reflect.*;
 import java.util.*;
 import java.util.function.*;
+import java.util.stream.*;
 
 import org.apache.juneau.common.collections.*;
 import org.apache.juneau.common.reflect.*;
@@ -485,45 +486,39 @@ public class ClassUtils {
        }
 
        /**
-        * If the annotation is an array of other annotations, returns the 
inner annotations.
+        * Returns a stream of nested annotations in a repeated annotation if 
the specified annotation is a repeated annotation,
+        * or a singleton stream with the same annotation if not.
         *
         * <p>
-        * This is used to handle repeated annotations where a container 
annotation holds multiple instances
-        * of the same annotation type.
+        * This method is a stream-based alternative to {@link 
#splitRepeated(Annotation)} that avoids creating intermediate arrays.
         *
-        * <h5 class='section'>Example:</h5>
+        * <p>
+        * <b>Example:</b>
         * <p class='bjava'>
-        *      <ja>@Repeatable</ja>(Authors.<jk>class</jk>)
-        *      <ja>@interface</ja> Author {
-        *              String value();
-        *      }
+        *      <jc>// Given an annotation that may be repeatable</jc>
+        *      Annotation <jv>annotation</jv> = ...;
         *
-        *      <ja>@interface</ja> Authors {
-        *              Author[] value();
-        *      }
-        *
-        *      <jc>// Given a class with repeated annotations:</jc>
-        *      <ja>@Author</ja>(<js>"John"</js>)
-        *      <ja>@Author</ja>(<js>"Jane"</js>)
-        *      <jk>class</jk> MyClass {}
-        *
-        *      <jc>// At runtime, this becomes an Authors annotation 
containing an array</jc>
-        *      <jc>// splitRepeated() extracts the individual Author 
annotations</jc>
+        *      <jc>// Stream individual annotations (expanded if 
repeatable)</jc>
+        *      streamRepeated(<jv>annotation</jv>)
+        *              .forEach(<jv>a</jv> -&gt; 
System.<jsf>out</jsf>.println(<jv>a</jv>));
         * </p>
         *
-        * @param a The annotation to split if repeated.
-        * @return The nested annotations, or a singleton array of the same 
annotation if it's not repeated.
+        * @param a The annotation to split.
+        * @return A stream of nested annotations, or a singleton stream with 
the same annotation if it's not repeated.
+        *      Never <jk>null</jk>.
         */
-       public static Annotation[] splitRepeated(Annotation a) {
+       public static Stream<Annotation> streamRepeated(Annotation a) {
                try {
                        var ci = ClassInfo.of(a.annotationType());
                        var mi = ci.getRepeatedAnnotationMethod();
-                       if (nn(mi))
-                               return mi.invoke(a);
+                       if (nn(mi)) {
+                               Annotation[] annotations = mi.invoke(a);
+                               return Arrays.stream(annotations);
+                       }
                } catch (Exception e) {
                        e.printStackTrace();
                }
-               return a(a);
+               return Stream.of(a);
        }
 
        /**

Reply via email to