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> ->
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);
}
/**