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 02eee99554 Utility class modernization
02eee99554 is described below
commit 02eee995543c967ff6a899ab30ab21d7a6cde812
Author: James Bognar <[email protected]>
AuthorDate: Wed Nov 5 07:54:23 2025 -0500
Utility class modernization
---
.../juneau/common/reflect/AnnotationInfo.java | 30 ++++--
.../juneau/common/reflect/ConstructorInfo.java | 24 -----
.../juneau/common/reflect/ExecutableInfo.java | 18 ----
.../apache/juneau/common/reflect/FieldInfo.java | 92 ++++++++--------
.../juneau/common/reflect/ParameterInfo.java | 7 +-
.../juneau/common/reflect/ExecutableInfo_Test.java | 14 +--
.../reflect/FieldInfo_AnnotationInfos_Test.java | 117 +++++++++++++++++++++
.../common/reflect/FieldInfo_FullName_Test.java | 74 +++++++++++++
8 files changed, 269 insertions(+), 107 deletions(-)
diff --git
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/AnnotationInfo.java
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/AnnotationInfo.java
index 8af5663e01..283ae8dd2e 100644
---
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/AnnotationInfo.java
+++
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/AnnotationInfo.java
@@ -524,6 +524,16 @@ public class AnnotationInfo<T extends Annotation> {
return eq(value, a.annotationType().getName());
}
+ /**
+ * Returns the method with the specified name on this annotation.
+ *
+ * @param methodName The method name.
+ * @return An optional containing the method, or empty if method not
found.
+ */
+ public Optional<MethodInfo> getMethod(String methodName) {
+ return methods.get().stream().filter(x -> eq(methodName,
x.getSimpleName())).findFirst();
+ }
+
/**
* Returns the value of the <c>value()</c> method on this annotation as
a string.
*
@@ -540,7 +550,7 @@ public class AnnotationInfo<T extends Annotation> {
* @return An optional containing the value of the specified method, or
empty if not found or not a string.
*/
public Optional<String> getString(String methodName) {
- return methods.get().stream().filter(x -> eq(methodName,
x.getSimpleName()) && x.hasReturnType(String.class)).map(x ->
s(x.invoke(a))).findFirst();
+ return getMethod(methodName).filter(x ->
x.hasReturnType(String.class)).map(x -> s(x.invoke(a)));
}
/**
@@ -550,7 +560,7 @@ public class AnnotationInfo<T extends Annotation> {
* @return An optional containing the value of the specified method, or
empty if not found or not an integer.
*/
public Optional<Integer> getInt(String methodName) {
- return methods.get().stream().filter(x -> eq(methodName,
x.getSimpleName()) && x.hasReturnType(int.class)).map(x ->
(Integer)x.invoke(a)).findFirst();
+ return getMethod(methodName).filter(x ->
x.hasReturnType(int.class)).map(x -> (Integer)x.invoke(a));
}
/**
@@ -560,7 +570,7 @@ public class AnnotationInfo<T extends Annotation> {
* @return An optional containing the value of the specified method, or
empty if not found or not a boolean.
*/
public Optional<Boolean> getBoolean(String methodName) {
- return methods.get().stream().filter(x -> eq(methodName,
x.getSimpleName()) && x.hasReturnType(boolean.class)).map(x ->
(Boolean)x.invoke(a)).findFirst();
+ return getMethod(methodName).filter(x ->
x.hasReturnType(boolean.class)).map(x -> (Boolean)x.invoke(a));
}
/**
@@ -570,7 +580,7 @@ public class AnnotationInfo<T extends Annotation> {
* @return An optional containing the value of the specified method, or
empty if not found or not a long.
*/
public Optional<Long> getLong(String methodName) {
- return methods.get().stream().filter(x -> eq(methodName,
x.getSimpleName()) && x.hasReturnType(long.class)).map(x ->
(Long)x.invoke(a)).findFirst();
+ return getMethod(methodName).filter(x ->
x.hasReturnType(long.class)).map(x -> (Long)x.invoke(a));
}
/**
@@ -580,7 +590,7 @@ public class AnnotationInfo<T extends Annotation> {
* @return An optional containing the value of the specified method, or
empty if not found or not a double.
*/
public Optional<Double> getDouble(String methodName) {
- return methods.get().stream().filter(x -> eq(methodName,
x.getSimpleName()) && x.hasReturnType(double.class)).map(x ->
(Double)x.invoke(a)).findFirst();
+ return getMethod(methodName).filter(x ->
x.hasReturnType(double.class)).map(x -> (Double)x.invoke(a));
}
/**
@@ -590,7 +600,7 @@ public class AnnotationInfo<T extends Annotation> {
* @return An optional containing the value of the specified method, or
empty if not found or not a float.
*/
public Optional<Float> getFloat(String methodName) {
- return methods.get().stream().filter(x -> eq(methodName,
x.getSimpleName()) && x.hasReturnType(float.class)).map(x ->
(Float)x.invoke(a)).findFirst();
+ return getMethod(methodName).filter(x ->
x.hasReturnType(float.class)).map(x -> (Float)x.invoke(a));
}
/**
@@ -601,7 +611,7 @@ public class AnnotationInfo<T extends Annotation> {
*/
@SuppressWarnings("unchecked")
public Optional<Class<?>> getClassValue(String methodName) {
- return
(Optional<Class<?>>)(Optional<?>)methods.get().stream().filter(x ->
eq(methodName, x.getSimpleName()) && x.hasReturnType(Class.class)).map(x ->
x.invoke(a)).findFirst();
+ return
(Optional<Class<?>>)(Optional<?>)getMethod(methodName).filter(x ->
x.hasReturnType(Class.class)).map(x -> x.invoke(a));
}
/**
@@ -611,7 +621,7 @@ public class AnnotationInfo<T extends Annotation> {
* @return An optional containing the value of the specified method, or
empty if not found or not a string array.
*/
public Optional<String[]> getStringArray(String methodName) {
- return methods.get().stream().filter(x -> eq(methodName,
x.getSimpleName()) && x.hasReturnType(String[].class)).map(x ->
(String[])x.invoke(a)).findFirst();
+ return getMethod(methodName).filter(x ->
x.hasReturnType(String[].class)).map(x -> (String[])x.invoke(a));
}
/**
@@ -622,7 +632,7 @@ public class AnnotationInfo<T extends Annotation> {
*/
@SuppressWarnings("unchecked")
public Optional<Class<?>[]> getClassArray(String methodName) {
- return
(Optional<Class<?>[]>)(Optional<?>)methods.get().stream().filter(x ->
eq(methodName, x.getSimpleName()) && x.hasReturnType(Class[].class)).map(x ->
x.invoke(a)).findFirst();
+ return
(Optional<Class<?>[]>)(Optional<?>)getMethod(methodName).filter(x ->
x.hasReturnType(Class[].class)).map(x -> x.invoke(a));
}
/**
@@ -637,6 +647,6 @@ public class AnnotationInfo<T extends Annotation> {
* @return An optional containing the return type of the specified
method, or empty if method not found.
*/
public Optional<ClassInfo> getReturnType(String methodName) {
- return methods.get().stream().filter(x -> eq(methodName,
x.getSimpleName())).map(x -> x.getReturnType()).findFirst();
+ return getMethod(methodName).map(x -> x.getReturnType());
}
}
\ No newline at end of file
diff --git
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/ConstructorInfo.java
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/ConstructorInfo.java
index bc9a7db918..27e48f8aab 100644
---
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/ConstructorInfo.java
+++
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/ConstructorInfo.java
@@ -71,19 +71,6 @@ public class ConstructorInfo extends ExecutableInfo
implements Comparable<Constr
this.c = c;
}
- /**
- * Performs an action on this object if the specified predicate test
passes.
- *
- * @param test A test to apply to determine if action should be
executed. Can be <jk>null</jk>.
- * @param action An action to perform on this object.
- * @return This object.
- */
- public ConstructorInfo accept(Predicate<ConstructorInfo> test,
Consumer<ConstructorInfo> action) {
- if (matches(test))
- action.accept(this);
- return this;
- }
-
@Override /* Overridden from ExecutableInfo */
public ConstructorInfo accessible() {
super.accessible();
@@ -153,17 +140,6 @@ public class ConstructorInfo extends ExecutableInfo
implements Comparable<Constr
return t.orElse(null);
}
- /**
- * Finds the annotation of the specified type defined on this
constructor.
- *
- * @param <A> The annotation type to look for.
- * @param type The annotation to look for.
- * @return The annotation if found, or <jk>null</jk> if not.
- */
- public <A extends Annotation> A getAnnotation(Class<A> type) {
- return getAnnotation(AnnotationProvider.DEFAULT, type);
- }
-
/**
* Returns <jk>true</jk> if the specified annotation is present on this
constructor.
*
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 0892e03a0c..a3659dd61c 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
@@ -74,24 +74,6 @@ public abstract class ExecutableInfo extends AccessibleInfo {
return this;
}
- /**
- * Performs an action on all matching parameter annotations at the
specified parameter index.
- *
- * @param <A> The annotation type.
- * @param index The parameter index.
- * @param type The annotation type.
- * @param predicate The predicate.
- * @param consumer The consumer.
- * @return This object.
- */
- @Deprecated
- public final <A extends Annotation> ExecutableInfo
forEachParameterAnnotation(int index, Class<A> type, Predicate<A> predicate,
Consumer<A> consumer) {
- for (var ai : getParameter(index).getAnnotationInfos())
- if (type.isInstance(ai.inner()))
- consumeIf(predicate, consumer,
type.cast(ai.inner()));
- return this;
- }
-
/**
* Returns how well this method matches the specified arg types.
*
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 a67db4fdb1..2ccaccf821 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
@@ -16,6 +16,8 @@
*/
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.CollectionUtils.*;
import static org.apache.juneau.common.utils.PredicateUtils.*;
import static org.apache.juneau.common.utils.Utils.*;
@@ -24,6 +26,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.*;
@@ -65,6 +68,8 @@ public class FieldInfo extends AccessibleInfo implements
Comparable<FieldInfo>,
Field f; // Effectively final
private final ClassInfo declaringClass;
private final Supplier<ClassInfo> typeCache = memoize(() ->
ClassInfo.of(f.getType()));
+ private final Supplier<List<AnnotationInfo<Annotation>>> annotations =
memoize(this::_findAnnotations);
+ private final Supplier<String> fullName = memoize(this::findFullName);
/**
* Constructor.
@@ -78,17 +83,53 @@ public class FieldInfo extends AccessibleInfo implements
Comparable<FieldInfo>,
this.f = f;
}
+ private List<AnnotationInfo<Annotation>> _findAnnotations() {
+ return stream(f.getAnnotations()).map(a ->
AnnotationInfo.of(this, a)).toList();
+ }
+
+ private String findFullName() {
+ var sb = new StringBuilder(128);
+ var dc = declaringClass;
+ var pi = dc.getPackage();
+ if (nn(pi))
+ sb.append(pi.getName()).append('.');
+ dc.appendNameFormatted(sb, SHORT, true, '$', BRACKETS);
+ sb.append('.').append(getName());
+ return sb.toString();
+ }
+
/**
- * Performs an action on this object if the specified predicate test
passes.
+ * Returns all annotations declared on this field.
*
- * @param test A test to apply to determine if action should be
executed. Can be <jk>null</jk>.
- * @param action An action to perform on this object.
- * @return This object.
+ * @return An unmodifiable list of all annotations declared on this
field.
*/
- public FieldInfo accept(Predicate<FieldInfo> test, Consumer<FieldInfo>
action) {
- if (matches(test))
- action.accept(this);
- return this;
+ public List<AnnotationInfo<Annotation>> getAnnotationInfos() {
+ return annotations.get();
+ }
+
+ /**
+ * Returns all annotations of the specified type declared on this field.
+ *
+ * @param <A> The annotation type.
+ * @param type The annotation type.
+ * @return A stream of all matching annotations.
+ */
+ @SuppressWarnings("unchecked")
+ public <A extends Annotation> Stream<AnnotationInfo<A>>
getAnnotationInfos(Class<A> type) {
+ return annotations.get().stream()
+ .filter(x -> type.isInstance(x.inner()))
+ .map(x -> (AnnotationInfo<A>)x);
+ }
+
+ /**
+ * Returns the first annotation of the specified type declared on this
field.
+ *
+ * @param <A> The annotation type.
+ * @param type The annotation type.
+ * @return An optional containing the first matching annotation, or
empty if not found.
+ */
+ public <A extends Annotation> Optional<AnnotationInfo<A>>
getAnnotationInfo(Class<A> type) {
+ return getAnnotationInfos(type).findFirst();
}
/**
@@ -146,7 +187,7 @@ public class FieldInfo extends AccessibleInfo implements
Comparable<FieldInfo>,
* @return The annotation, or <jk>null</jk> if not found.
*/
public <A extends Annotation> A getAnnotation(Class<A> type) {
- return getAnnotation(AnnotationProvider.DEFAULT, type);
+ return
getAnnotationInfo(type).map(AnnotationInfo::inner).orElse(null);
}
/**
@@ -167,14 +208,7 @@ public class FieldInfo extends AccessibleInfo implements
Comparable<FieldInfo>,
* @return The underlying executable name.
*/
public String getFullName() {
- var sb = new StringBuilder(128);
- ClassInfo dc = declaringClass;
- PackageInfo pi = dc.getPackage();
- if (nn(pi))
- sb.append(pi.getName()).append('.');
- dc.appendNameFormatted(sb, ClassNameFormat.SHORT, true, '$',
ClassArrayFormat.BRACKETS);
- sb.append(".").append(getName());
- return sb.toString();
+ return fullName.get();
}
/**
@@ -406,30 +440,6 @@ public class FieldInfo extends AccessibleInfo implements
Comparable<FieldInfo>,
return f.isEnumConstant();
}
- /**
- * Returns a {@link Type} object that represents the declared type for
the field represented by this object.
- *
- * <p>
- * Same as calling {@link Field#getGenericType()}.
- *
- * <h5 class='section'>Example:</h5>
- * <p class='bjava'>
- * <jc>// Get generic type information for field:
List<String> values</jc>
- * FieldInfo <jv>fi</jv> =
ClassInfo.<jsm>of</jsm>(MyClass.<jk>class</jk>).getField(<js>"values"</js>);
- * Type <jv>type</jv> = <jv>fi</jv>.getGenericType();
- * <jk>if</jk> (<jv>type</jv> <jk>instanceof</jk>
ParameterizedType) {
- * ParameterizedType <jv>pType</jv> =
(ParameterizedType)<jv>type</jv>;
- * <jc>// pType.getActualTypeArguments()[0] is
String.class</jc>
- * }
- * </p>
- *
- * @return A {@link Type} object representing the declared type.
- * @see Field#getGenericType()
- */
- public Type getGenericType() {
- return f.getGenericType();
- }
-
/**
* Returns an {@link AnnotatedType} object that represents the use of a
type to specify the declared type of the field.
*
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 110489cbb6..e2c6b5e808 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
@@ -777,7 +777,12 @@ public class ParameterInfo extends ElementInfo implements
Annotatable {
var mi = (MethodInfo)executable;
var ci =
executable.getParameter(index).getParameterType().unwrap(Value.class,
Optional.class);
ci.forEachAnnotation(ap, a, filter, action);
- mi.forEachMatchingParentFirst(x -> true, x ->
x.forEachParameterAnnotation(index, a, filter, action));
+ mi.forEachMatchingParentFirst(x -> true, x -> {
+
x.getParameter(index).getAnnotationInfos().stream()
+ .filter(ai -> a.isInstance(ai.inner()))
+ .map(ai -> a.cast(ai.inner()))
+ .forEach(ann -> consumeIf(filter,
action, ann));
+ });
}
return this;
}
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/common/reflect/ExecutableInfo_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/common/reflect/ExecutableInfo_Test.java
index 88b46d5e6e..03cac9d33c 100644
---
a/juneau-utest/src/test/java/org/apache/juneau/common/reflect/ExecutableInfo_Test.java
+++
b/juneau-utest/src/test/java/org/apache/juneau/common/reflect/ExecutableInfo_Test.java
@@ -281,18 +281,6 @@ class ExecutableInfo_Test extends TestBase {
assertTrue(c_m3.hasAnnotation(CA.class));
}
- @Test void getAnnotation() {
- check(null, c_c2.getAnnotation(CA.class));
- check("@CA()", c_c3.getAnnotation(CA.class));
- check(null, c_m1.getAnnotation(CA.class));
- check(null, c_m2.getAnnotation(CA.class));
- check("@CA()", c_m3.getAnnotation(CA.class));
- }
-
- @Test void getAnnotation_nullArg() {
- check(null, c_c3.getAnnotation(null));
- }
-
//-----------------------------------------------------------------------------------------------------------------
// Exceptions
//-----------------------------------------------------------------------------------------------------------------
@@ -378,7 +366,7 @@ class ExecutableInfo_Test extends TestBase {
// TRANSIENT is a valid modifier flag but doesn't apply to
executables
// Should return false (executables can't be transient) but not
throw exception
assertFalse(e_deprecated.is(TRANSIENT));
-
+
// CLASS is not a modifier flag and doesn't apply to
executables, should throw exception
assertThrowsWithMessage(RuntimeException.class, "Invalid flag
for element: CLASS", () -> e_deprecated.is(ElementFlag.CLASS));
}
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/common/reflect/FieldInfo_AnnotationInfos_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/common/reflect/FieldInfo_AnnotationInfos_Test.java
new file mode 100644
index 0000000000..9ca8755a2b
--- /dev/null
+++
b/juneau-utest/src/test/java/org/apache/juneau/common/reflect/FieldInfo_AnnotationInfos_Test.java
@@ -0,0 +1,117 @@
+//
***************************************************************************************************************************
+// * Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file *
+// * distributed with this work for additional information regarding copyright
ownership. The ASF licenses this file *
+// * to you under the Apache License, Version 2.0 (the "License"); you may not
use this file except in compliance *
+// * with the License. You may obtain a copy of the License at
*
+// *
*
+// * http://www.apache.org/licenses/LICENSE-2.0
*
+// *
*
+// * Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an *
+// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
express or implied. See the License for the *
+// * specific language governing permissions and limitations under the
License. *
+//
***************************************************************************************************************************
+package org.apache.juneau.common.reflect;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.lang.annotation.*;
+
+import org.junit.jupiter.api.*;
+
+/**
+ * Tests for {@link FieldInfo#getAnnotationInfos()} methods.
+ */
+public class FieldInfo_AnnotationInfos_Test {
+
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.FIELD)
+ public @interface TestAnnotation1 {
+ String value() default "";
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.FIELD)
+ public @interface TestAnnotation2 {
+ int value() default 0;
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.FIELD)
+ public @interface TestAnnotation3 {
+ String value() default "";
+ }
+
+ public static class TestClass {
+ @TestAnnotation1("test1")
+ @TestAnnotation2(42)
+ public String field1;
+
+ @TestAnnotation1("test2")
+ public String field2;
+
+ public String field3;
+ }
+
+ @Test
+ public void testGetAnnotationInfos() {
+ var ci = ClassInfo.of(TestClass.class);
+ var field1 = ci.getPublicField(x ->
x.getName().equals("field1"));
+ var field2 = ci.getPublicField(x ->
x.getName().equals("field2"));
+ var field3 = ci.getPublicField(x ->
x.getName().equals("field3"));
+
+ // field1 has 2 annotations
+ var annotations1 = field1.getAnnotationInfos();
+ assertEquals(2, annotations1.size());
+ assertTrue(annotations1.stream().anyMatch(a ->
a.hasSimpleName("TestAnnotation1")));
+ assertTrue(annotations1.stream().anyMatch(a ->
a.hasSimpleName("TestAnnotation2")));
+
+ // field2 has 1 annotation
+ var annotations2 = field2.getAnnotationInfos();
+ assertEquals(1, annotations2.size());
+ assertTrue(annotations2.stream().anyMatch(a ->
a.hasSimpleName("TestAnnotation1")));
+
+ // field3 has no annotations
+ var annotations3 = field3.getAnnotationInfos();
+ assertEquals(0, annotations3.size());
+ }
+
+ @Test
+ public void testGetAnnotationInfosTyped() {
+ var ci = ClassInfo.of(TestClass.class);
+ var field1 = ci.getPublicField(x ->
x.getName().equals("field1"));
+ var field2 = ci.getPublicField(x ->
x.getName().equals("field2"));
+
+ // Test filtering by type for field1
+ var ann1_type1 =
field1.getAnnotationInfos(TestAnnotation1.class).toList();
+ assertEquals(1, ann1_type1.size());
+ assertEquals("test1", ann1_type1.get(0).getValue().get());
+
+ var ann1_type2 =
field1.getAnnotationInfos(TestAnnotation2.class).toList();
+ assertEquals(1, ann1_type2.size());
+ assertEquals(42, ann1_type2.get(0).getInt("value").get());
+
+ // Test filtering by type that doesn't exist
+ var ann1_type3 =
field1.getAnnotationInfos(TestAnnotation3.class).toList();
+ assertEquals(0, ann1_type3.size());
+
+ // Test filtering for field2
+ var ann2_type1 =
field2.getAnnotationInfos(TestAnnotation1.class).toList();
+ assertEquals(1, ann2_type1.size());
+ assertEquals("test2", ann2_type1.get(0).getValue().get());
+
+ var ann2_type2 =
field2.getAnnotationInfos(TestAnnotation2.class).toList();
+ assertEquals(0, ann2_type2.size());
+ }
+
+ @Test
+ public void testGetAnnotationInfosMemoization() {
+ var ci = ClassInfo.of(TestClass.class);
+ var field1 = ci.getPublicField(x ->
x.getName().equals("field1"));
+
+ // Calling getAnnotationInfos() multiple times should return
the same list instance
+ var annotations1 = field1.getAnnotationInfos();
+ var annotations2 = field1.getAnnotationInfos();
+ assertSame(annotations1, annotations2);
+ }
+}
+
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/common/reflect/FieldInfo_FullName_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/common/reflect/FieldInfo_FullName_Test.java
new file mode 100644
index 0000000000..94dc3d803a
--- /dev/null
+++
b/juneau-utest/src/test/java/org/apache/juneau/common/reflect/FieldInfo_FullName_Test.java
@@ -0,0 +1,74 @@
+//
***************************************************************************************************************************
+// * Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file *
+// * distributed with this work for additional information regarding copyright
ownership. The ASF licenses this file *
+// * to you under the Apache License, Version 2.0 (the "License"); you may not
use this file except in compliance *
+// * with the License. You may obtain a copy of the License at
*
+// *
*
+// * http://www.apache.org/licenses/LICENSE-2.0
*
+// *
*
+// * Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an *
+// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
express or implied. See the License for the *
+// * specific language governing permissions and limitations under the
License. *
+//
***************************************************************************************************************************
+package org.apache.juneau.common.reflect;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.junit.jupiter.api.*;
+
+/**
+ * Tests for {@link FieldInfo#getFullName()} method.
+ */
+public class FieldInfo_FullName_Test {
+
+ public static class TestClass {
+ public String field1;
+ public int field2;
+ }
+
+ @Test
+ public void testGetFullName() {
+ var ci = ClassInfo.of(TestClass.class);
+ var field1 = ci.getPublicField(x ->
x.getName().equals("field1"));
+ var field2 = ci.getPublicField(x ->
x.getName().equals("field2"));
+
+ // Verify full names are correct
+ String fullName1 = field1.getFullName();
+ String fullName2 = field2.getFullName();
+
+
assertTrue(fullName1.endsWith("FieldInfo_FullName_Test$TestClass.field1"));
+
assertTrue(fullName2.endsWith("FieldInfo_FullName_Test$TestClass.field2"));
+
+ // Verify package is included
+
assertTrue(fullName1.startsWith("org.apache.juneau.common.reflect."));
+
assertTrue(fullName2.startsWith("org.apache.juneau.common.reflect."));
+ }
+
+ @Test
+ public void testGetFullNameMemoization() {
+ var ci = ClassInfo.of(TestClass.class);
+ var field1 = ci.getPublicField(x ->
x.getName().equals("field1"));
+
+ // Calling getFullName() multiple times should return the same
String instance (memoized)
+ String name1 = field1.getFullName();
+ String name2 = field1.getFullName();
+ assertSame(name1, name2);
+ }
+
+ public static class InnerClass {
+ public String innerField;
+ }
+
+ @Test
+ public void testGetFullNameWithInnerClass() {
+ var ci = ClassInfo.of(InnerClass.class);
+ var field = ci.getPublicField(x ->
x.getName().equals("innerField"));
+
+ String fullName = field.getFullName();
+
+ // Verify $ separator is used for inner class
+
assertTrue(fullName.contains("FieldInfo_FullName_Test$InnerClass"));
+ assertTrue(fullName.endsWith(".innerField"));
+ }
+}
+