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

garydgregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-lang.git

commit 64375a58ee3ec6af67f318f389d3eeb8279db64e
Author: Gary Gregory <[email protected]>
AuthorDate: Mon Oct 12 14:25:18 2026 -0400

    Expand AnnotationUtils nested annotation array hash tests
    
    Make the regression test deterministic by returning a deliberately
    incorrect hash instead of an identity hash. Add coverage for empty
    arrays, multiple elements, element order, null elements, and deeper
    nesting. Clarify the helper Javadoc to describe recursive element
    hashing and Arrays.hashCode mixing.
---
 .../org/apache/commons/lang3/AnnotationUtils.java  |  4 +-
 .../apache/commons/lang3/AnnotationUtilsTest.java  | 62 +++++++++++++++++++++-
 2 files changed, 63 insertions(+), 3 deletions(-)

diff --git a/src/main/java/org/apache/commons/lang3/AnnotationUtils.java 
b/src/main/java/org/apache/commons/lang3/AnnotationUtils.java
index 0554e5529..1d9f95fd2 100644
--- a/src/main/java/org/apache/commons/lang3/AnnotationUtils.java
+++ b/src/main/java/org/apache/commons/lang3/AnnotationUtils.java
@@ -118,7 +118,9 @@ private static boolean annotationArrayMemberEquals(final 
Annotation[] a1, final
     }
 
     /**
-     * Helper method for generating a hash code for an array of annotations.
+     * Generates an array hash code using {@link #hashCode(Annotation)} for 
each non-null element.
+     *
+     * <p>Uses the element order, initial value, multiplier, and null-element 
handling of {@link Arrays#hashCode(Object[])}.</p>
      *
      * @param annotations The array
      * @return A hash code for the specified array
diff --git a/src/test/java/org/apache/commons/lang3/AnnotationUtilsTest.java 
b/src/test/java/org/apache/commons/lang3/AnnotationUtilsTest.java
index bddf44f46..ef5162814 100644
--- a/src/test/java/org/apache/commons/lang3/AnnotationUtilsTest.java
+++ b/src/test/java/org/apache/commons/lang3/AnnotationUtilsTest.java
@@ -32,6 +32,7 @@
 import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.Proxy;
 import java.time.Duration;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Map;
 import java.util.function.Function;
@@ -50,6 +51,21 @@ class AnnotationUtilsTest extends AbstractLangTest {
     private static final class AnnotatedClass {
     }
 
+    @Retention(RetentionPolicy.RUNTIME)
+    public @interface AnnotationArray {
+        FunctionAnnotation[] value();
+    }
+
+    @Retention(RetentionPolicy.RUNTIME)
+    public @interface AnnotationArrayContainer {
+        AnnotationArray[] value();
+    }
+
+    @AnnotationArray({ @FunctionAnnotation("first"), 
@FunctionAnnotation("second") })
+    @AnnotationArrayContainer(@AnnotationArray({ @FunctionAnnotation("first"), 
@FunctionAnnotation("second") }))
+    private static final class ArrayAnnotatedClass {
+    }
+
     @Retention(RetentionPolicy.RUNTIME)
     public @interface FunctionAnnotation {
         Function<String, String> FUNCTION = value -> value;
@@ -526,6 +542,48 @@ void testHashCode() {
         });
     }
 
+    @Test
+    void testHashCodeOfAnnotationArrayEdgeCases() {
+        final AnnotationArray real = 
ArrayAnnotatedClass.class.getAnnotation(AnnotationArray.class);
+        final FunctionAnnotation first = real.value()[0];
+        final FunctionAnnotation second = real.value()[1];
+        final FunctionAnnotation[][] values = {
+            {}, { first }, { first, second }, { second, first }, { null }, { 
first, null, second }
+        };
+        for (final FunctionAnnotation[] value : values) {
+            final AnnotationArray generated = (AnnotationArray) 
Proxy.newProxyInstance(
+                    AnnotationArray.class.getClassLoader(), new Class<?>[] { 
AnnotationArray.class },
+                    (proxy, method, args) -> "value".equals(method.getName()) 
? value : method.invoke(real, args));
+            final int expected = 127 * "value".hashCode() ^ 
Arrays.hashCode(value);
+            assertEquals(expected, AnnotationUtils.hashCode(generated));
+        }
+    }
+
+    @Test
+    void testHashCodeOfDeeplyNestedAnnotationArray() {
+        final AnnotationArrayContainer real = 
ArrayAnnotatedClass.class.getAnnotation(AnnotationArrayContainer.class);
+        final AnnotationArray inner = real.value()[0];
+        final FunctionAnnotation[] values = inner.value();
+        final FunctionAnnotation[] generatedValues = new 
FunctionAnnotation[values.length];
+        for (int i = 0; i < values.length; i++) {
+            final FunctionAnnotation value = values[i];
+            generatedValues[i] = (FunctionAnnotation) Proxy.newProxyInstance(
+                    FunctionAnnotation.class.getClassLoader(), new Class<?>[] 
{ FunctionAnnotation.class },
+                    (proxy, method, args) -> 
"hashCode".equals(method.getName())
+                            ? Integer.valueOf(value.hashCode() + 1) : 
method.invoke(value, args));
+        }
+        final AnnotationArray generatedInner = (AnnotationArray) 
Proxy.newProxyInstance(
+                AnnotationArray.class.getClassLoader(), new Class<?>[] { 
AnnotationArray.class },
+                (proxy, method, args) -> "value".equals(method.getName()) ? 
generatedValues : method.invoke(inner, args));
+        final AnnotationArrayContainer generated = (AnnotationArrayContainer) 
Proxy.newProxyInstance(
+                AnnotationArrayContainer.class.getClassLoader(), new 
Class<?>[] { AnnotationArrayContainer.class },
+                (proxy, method, args) -> "value".equals(method.getName())
+                        ? new AnnotationArray[] { generatedInner } : 
method.invoke(real, args));
+        assertTrue(AnnotationUtils.equals(real, generated));
+        assertTrue(AnnotationUtils.equals(generated, real));
+        assertEquals(real.hashCode(), AnnotationUtils.hashCode(generated));
+    }
+
     @Test
     void testHashCodeOfNestedAnnotationArray() {
         assertTimeoutPreemptively(Duration.ofSeconds(666L), () -> {
@@ -533,7 +591,7 @@ void testHashCodeOfNestedAnnotationArray() {
             // A nested annotation whose own hashCode() does not follow 
Annotation#hashCode().
             final InvocationHandler nestHandler = (proxy, method, args) -> {
                 if ("hashCode".equals(method.getName()) && 
method.getParameterTypes().length == 0) {
-                    return Integer.valueOf(System.identityHashCode(proxy));
+                    return Integer.valueOf(real.nests()[0].hashCode() + 1);
                 }
                 return method.invoke(real.nests()[0], args);
             };
@@ -563,7 +621,7 @@ void testHashCodeWithFunction() {
     void testHashCodeWithoutInvokingSupplier() {
         final ThrowingSupplierAnnotation annotation =
                 
AnnotatedClass.class.getAnnotation(ThrowingSupplierAnnotation.class);
-        final int expected = (127 * "value".hashCode()) ^ "member".hashCode();
+        final int expected = 127 * "value".hashCode() ^ "member".hashCode();
 
         assertEquals(expected, AnnotationUtils.hashCode(annotation));
     }

Reply via email to