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 e41b63d0d5 Utility class modernization
e41b63d0d5 is described below
commit e41b63d0d5a4fe9825330eef7d631010778af6c7
Author: James Bognar <[email protected]>
AuthorDate: Tue Nov 4 09:49:45 2025 -0500
Utility class modernization
---
.../juneau/common/reflect/ParameterInfo.java | 58 ++++++++
.../juneau/common/reflect/ParamInfoTest.java | 162 +++++++++++++++++++++
2 files changed, 220 insertions(+)
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 674a59a6c8..c32cd0f496 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
@@ -48,6 +48,12 @@ public class ParameterInfo extends ElementInfo implements
Annotatable {
.supplier(k -> opt(findAnnotation(k)))
.build();
+ @SuppressWarnings({"rawtypes"})
+ private final Cache foundAnnotations =
+ Cache.of(Class.class, List.class)
+ .supplier(this::findAnnotationInfosInternal)
+ .build();
+
private final Supplier<List<AnnotationInfo<Annotation>>> annotations =
memoize(this::_findAnnotations);
private final Supplier<List<ParameterInfo>> matchingParameters =
memoize(this::_findMatchingParameters);
@@ -248,6 +254,36 @@ public class ParameterInfo extends ElementInfo implements
Annotatable {
return
(A)((Optional<Annotation>)annotationCache.get(type)).orElse(null);
}
+ /**
+ * Finds all annotation infos of the specified type defined on this
method parameter.
+ *
+ * <p>
+ * Searches through matching parameters in the hierarchy and the
parameter type.
+ *
+ * @param <A> The annotation type to look for.
+ * @param type The annotation type to look for.
+ * @return A list of annotation infos found, or an empty list if none
found.
+ */
+ @SuppressWarnings("unchecked")
+ public <A extends Annotation> List<AnnotationInfo<A>>
findAnnotationInfos(Class<A> type) {
+ return (List<AnnotationInfo<A>>)foundAnnotations.get(type);
+ }
+
+ /**
+ * Finds the first annotation info of the specified type defined on
this method parameter.
+ *
+ * <p>
+ * Searches through matching parameters in the hierarchy and the
parameter type.
+ *
+ * @param <A> The annotation type to look for.
+ * @param type The annotation type to look for.
+ * @return The annotation info if found, or <jk>null</jk> if not.
+ */
+ public <A extends Annotation> AnnotationInfo<A>
findAnnotationInfo(Class<A> type) {
+ var list = findAnnotationInfos(type);
+ return list.isEmpty() ? null : list.get(0);
+ }
+
/**
* Returns the first matching annotation on this method parameter.
*
@@ -791,6 +827,28 @@ public class ParameterInfo extends ElementInfo implements
Annotatable {
return v.orElseGet(() ->
executable.getParameter(index).getParameterType().unwrap(Value.class,
Optional.class).getAnnotation(type));
}
+ @SuppressWarnings("unchecked")
+ private <A extends Annotation> List<AnnotationInfo<A>>
findAnnotationInfosInternal(Class<A> type) {
+ var list = new ArrayList<AnnotationInfo<A>>();
+
+ // Search through matching parameters in hierarchy
+ for (var mp : getMatchingParameters()) {
+ mp.getAnnotationInfos().stream()
+ .filter(x -> x.isType(type))
+ .map(x -> (AnnotationInfo<A>)x)
+ .forEach(list::add);
+ }
+
+ // Search on parameter type
+ var paramType =
executable.getParameter(index).getParameterType().unwrap(Value.class,
Optional.class);
+ paramType.getDeclaredAnnotationInfos().stream()
+ .filter(x -> x.isType(type))
+ .map(x -> (AnnotationInfo<A>)x)
+ .forEach(list::add);
+
+ return list;
+ }
+
private <A extends Annotation> ParameterInfo
forEachAnnotation(AnnotationProvider ap, Class<A> a, Predicate<A> filter,
Consumer<A> action) {
if (executable.isConstructor) {
var ci =
executable.getParameter(index).getParameterType().unwrap(Value.class,
Optional.class);
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/common/reflect/ParamInfoTest.java
b/juneau-utest/src/test/java/org/apache/juneau/common/reflect/ParamInfoTest.java
index a55c2dcd19..890f7d69ba 100644
---
a/juneau-utest/src/test/java/org/apache/juneau/common/reflect/ParamInfoTest.java
+++
b/juneau-utest/src/test/java/org/apache/juneau/common/reflect/ParamInfoTest.java
@@ -614,6 +614,168 @@ class ParamInfoTest extends TestBase {
}
}
+
//-----------------------------------------------------------------------------------------------------------------
+ // findAnnotationInfos() / findAnnotationInfo()
+
//-----------------------------------------------------------------------------------------------------------------
+
+ @Nested
+ class FindAnnotationInfosTests {
+
+ // Annotations for testing
+ @Documented
+ @Target({PARAMETER, TYPE})
+ @Retention(RUNTIME)
+ public @interface FA1 {
+ int value();
+ }
+
+ @Documented
+ @Target({PARAMETER, TYPE})
+ @Retention(RUNTIME)
+ public @interface FA2 {
+ String value();
+ }
+
+ // Test finding annotation on parameter itself
+ public static class F1 {
+ public void test(@FA1(1) String x) {} // NOSONAR
+ }
+
+ @Test void findOnParameter() throws Exception {
+ var mi = MethodInfo.of(F1.class.getMethod("test",
String.class));
+ var pi = mi.getParameter(0);
+ var infos = pi.findAnnotationInfos(FA1.class);
+ assertEquals(1, infos.size());
+ assertEquals(1, infos.get(0).inner().value());
+ }
+
+ @Test void findOnParameter_single() throws Exception {
+ var mi = MethodInfo.of(F1.class.getMethod("test",
String.class));
+ var pi = mi.getParameter(0);
+ var info = pi.findAnnotationInfo(FA1.class);
+ assertNotNull(info);
+ assertEquals(1, info.inner().value());
+ }
+
+ // Test finding annotation from matching method parameters
+ public interface F2 {
+ void test(@FA1(2) String x);
+ }
+
+ public static class F3 implements F2 {
+ @Override public void test(String x) {} // NOSONAR
+ }
+
+ @Test void findFromMatchingMethod() throws Exception {
+ var mi = MethodInfo.of(F3.class.getMethod("test",
String.class));
+ var pi = mi.getParameter(0);
+ var infos = pi.findAnnotationInfos(FA1.class);
+ assertEquals(1, infos.size());
+ assertEquals(2, infos.get(0).inner().value());
+ }
+
+ // Test finding annotation from parameter type
+ @FA1(3)
+ public static class F4Type {}
+
+ public static class F5 {
+ public void test(F4Type x) {} // NOSONAR
+ }
+
+ @Test void findFromParameterType() throws Exception {
+ var mi = MethodInfo.of(F5.class.getMethod("test",
F4Type.class));
+ var pi = mi.getParameter(0);
+ var infos = pi.findAnnotationInfos(FA1.class);
+ assertEquals(1, infos.size());
+ assertEquals(3, infos.get(0).inner().value());
+ }
+
+ // Test finding multiple annotations from hierarchy
+ public interface F6 {
+ void test(@FA1(4) String x);
+ }
+
+ public static class F7 {
+ public void test(@FA1(5) String x) {} // NOSONAR
+ }
+
+ public static class F8 extends F7 implements F6 {
+ @Override public void test(@FA1(6) String x) {} //
NOSONAR
+ }
+
+ @Test void findMultipleFromHierarchy() throws Exception {
+ var mi = MethodInfo.of(F8.class.getMethod("test",
String.class));
+ var pi = mi.getParameter(0);
+ var infos = pi.findAnnotationInfos(FA1.class);
+ assertEquals(3, infos.size());
+ assertEquals(6, infos.get(0).inner().value()); // F8
+ assertEquals(4, infos.get(1).inner().value()); // F6
+ assertEquals(5, infos.get(2).inner().value()); // F7
+ }
+
+ @Test void findMultipleFromHierarchy_single() throws Exception {
+ var mi = MethodInfo.of(F8.class.getMethod("test",
String.class));
+ var pi = mi.getParameter(0);
+ var info = pi.findAnnotationInfo(FA1.class);
+ assertNotNull(info);
+ assertEquals(6, info.inner().value()); // Returns first
(F8)
+ }
+
+ // Test finding annotation from constructor parameters
+ public static class F9 {
+ public F9(@FA1(7) String x) {} // NOSONAR
+ }
+
+ public static class F10 extends F9 {
+ public F10(@FA1(8) String x) { super(x); } // NOSONAR
+ }
+
+ @Test void findFromMatchingConstructor() throws Exception {
+ var ci =
ConstructorInfo.of(F10.class.getConstructor(String.class));
+ var pi = ci.getParameter(0);
+ var infos = pi.findAnnotationInfos(FA1.class);
+ assertEquals(2, infos.size());
+ assertEquals(8, infos.get(0).inner().value()); // F10
+ assertEquals(7, infos.get(1).inner().value()); // F9
+ }
+
+ // Test not found
+ public static class F11 {
+ public void test(String x) {} // NOSONAR
+ }
+
+ @Test void notFound() throws Exception {
+ var mi = MethodInfo.of(F11.class.getMethod("test",
String.class));
+ var pi = mi.getParameter(0);
+ var infos = pi.findAnnotationInfos(FA1.class);
+ assertEquals(0, infos.size());
+ }
+
+ @Test void notFound_single() throws Exception {
+ var mi = MethodInfo.of(F11.class.getMethod("test",
String.class));
+ var pi = mi.getParameter(0);
+ var info = pi.findAnnotationInfo(FA1.class);
+ assertNull(info);
+ }
+
+ // Test parameter annotation takes precedence over type
annotation
+ @FA1(9)
+ public static class F12Type {}
+
+ public static class F13 {
+ public void test(@FA1(10) F12Type x) {} // NOSONAR
+ }
+
+ @Test void parameterAnnotationBeforeTypeAnnotation() throws
Exception {
+ var mi = MethodInfo.of(F13.class.getMethod("test",
F12Type.class));
+ var pi = mi.getParameter(0);
+ var infos = pi.findAnnotationInfos(FA1.class);
+ assertEquals(2, infos.size());
+ assertEquals(10, infos.get(0).inner().value()); //
Parameter annotation first
+ assertEquals(9, infos.get(1).inner().value()); // Type
annotation second
+ }
+ }
+
//-----------------------------------------------------------------------------------------------------------------
// Helpers
//-----------------------------------------------------------------------------------------------------------------