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
The following commit(s) were added to refs/heads/master by this push:
new d62646495 [LANG-1323] Align built type hashes with JDK reflection
(#1826)
d62646495 is described below
commit d626464952b57e665a83bf556cd0f0e7fd9983fb
Author: agamal026 <[email protected]>
AuthorDate: Tue Oct 20 00:29:42 2026 +0200
[LANG-1323] Align built type hashes with JDK reflection (#1826)
* [LANG-1323] Add reflection type hash interoperability regressions
Compare built parameterized and generic array types with JDK reflection,
including nested arguments and owner types. Exercise symmetric equality,
hash equality, and HashMap/HashSet lookup in both directions.
Generated-by: OpenAI ChatGPT (GPT-6 Astra)
* [LANG-1323] Align built type hashes with JDK reflection
Use the component type hash for generic arrays and combine the argument,
owner and raw type hashes for parameterized types. Equal built and JDK
reflection types can then be used interchangeably as hash collection keys.
The 14 new regressions failed against unchanged production and pass with
this fix. The complete TypeUtilsTest run reports 510 tests, zero failures
or errors, and one existing disabled test. Preserve the wildcard changes
from #1807; this completes the two separate type kinds identified there.
Generated-by: OpenAI ChatGPT (GPT-6 Astra)
---
.../apache/commons/lang3/reflect/TypeUtils.java | 14 ++---
.../commons/lang3/reflect/TypeUtilsTest.java | 65 ++++++++++++++++++++++
2 files changed, 69 insertions(+), 10 deletions(-)
diff --git a/src/main/java/org/apache/commons/lang3/reflect/TypeUtils.java
b/src/main/java/org/apache/commons/lang3/reflect/TypeUtils.java
index c30e0f264..852283424 100644
--- a/src/main/java/org/apache/commons/lang3/reflect/TypeUtils.java
+++ b/src/main/java/org/apache/commons/lang3/reflect/TypeUtils.java
@@ -86,9 +86,8 @@ public Type getGenericComponentType() {
*/
@Override
public int hashCode() {
- int result = 67 << 4;
- result |= componentType.hashCode();
- return result;
+ // Match the JDK's hash code for an equal generic array type.
+ return componentType.hashCode();
}
/**
@@ -158,13 +157,8 @@ public Type getRawType() {
*/
@Override
public int hashCode() {
- int result = 71 << 4;
- result |= raw.hashCode();
- result <<= 4;
- result |= Objects.hashCode(useOwner);
- result <<= 8;
- result |= Arrays.hashCode(typeArguments);
- return result;
+ // Match the JDK's hash code for an equal parameterized type.
+ return Arrays.hashCode(typeArguments) ^ Objects.hashCode(useOwner)
^ raw.hashCode();
}
/**
diff --git a/src/test/java/org/apache/commons/lang3/reflect/TypeUtilsTest.java
b/src/test/java/org/apache/commons/lang3/reflect/TypeUtilsTest.java
index 4a9bea245..f145a0fab 100644
--- a/src/test/java/org/apache/commons/lang3/reflect/TypeUtilsTest.java
+++ b/src/test/java/org/apache/commons/lang3/reflect/TypeUtilsTest.java
@@ -18,6 +18,7 @@
import static
org.apache.commons.lang3.LangAssertions.assertIllegalArgumentException;
import static
org.apache.commons.lang3.LangAssertions.assertNullPointerException;
+import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -45,6 +46,7 @@
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -351,6 +353,23 @@ static class StaticMember implements TestIF<Number> {
}
}
+ private static class HashCodeTypes<T> {
+ T[] array;
+ List<T[]> arrayArgument;
+ Map.Entry<String, Integer> entry;
+ List<String> list;
+ List<String>[] listArray;
+ Map<String, Integer> map;
+ Outer<String>.InnerU<Integer> member;
+ Map<String, List<Integer>> nested;
+ T[][] nestedArray;
+ List<String>[][] nestedListArray;
+ ParameterizedOwner<String>.NonGenericInner nonGenericMember;
+ List<T> variableList;
+ List<? super T[]> wildcardArrayArgument;
+ List<?> wildcardList;
+ }
+
static class LexOrdering<T> extends MyOrdering<Iterable<T>> implements
Serializable {
private static final long serialVersionUID = 1L;
}
@@ -484,6 +503,29 @@ public static <T extends Comparable<? extends T>> T
stub3() {
return null;
}
+ static Stream<Arguments> testHashCodeMatchesJdk() {
+ final Type variable = HashCodeTypes.class.getTypeParameters()[0];
+ final Type array = TypeUtils.genericArrayType(variable);
+ final Type strings = TypeUtils.parameterize(List.class, String.class);
+ return Stream.of(
+ Arguments.of("array", array),
+ Arguments.of("arrayArgument",
TypeUtils.parameterize(List.class, array)),
+ Arguments.of("entry",
TypeUtils.parameterizeWithOwner(Map.class, Map.Entry.class, String.class,
Integer.class)),
+ Arguments.of("list", strings),
+ Arguments.of("listArray", TypeUtils.genericArrayType(strings)),
+ Arguments.of("map", TypeUtils.parameterize(Map.class,
String.class, Integer.class)),
+ Arguments.of("member",
TypeUtils.parameterizeWithOwner(TypeUtils.parameterize(Outer.class,
String.class),
+ Outer.InnerU.class, Integer.class)),
+ Arguments.of("nested", TypeUtils.parameterize(Map.class,
String.class, TypeUtils.parameterize(List.class, Integer.class))),
+ Arguments.of("nestedArray", TypeUtils.genericArrayType(array)),
+ Arguments.of("nestedListArray",
TypeUtils.genericArrayType(TypeUtils.genericArrayType(strings))),
+ Arguments.of("nonGenericMember",
TypeUtils.parameterizeWithOwner(TypeUtils.parameterize(ParameterizedOwner.class,
String.class),
+ ParameterizedOwner.NonGenericInner.class)),
+ Arguments.of("variableList",
TypeUtils.parameterize(List.class, variable)),
+ Arguments.of("wildcardArrayArgument",
TypeUtils.parameterize(List.class,
TypeUtils.wildcardType().withLowerBounds(array).build())),
+ Arguments.of("wildcardList",
TypeUtils.parameterize(List.class, TypeUtils.WILDCARD_ALL)));
+ }
+
static Stream<Arguments> testIsAssignable_ClassWildcardBounds() {
return Stream.of(
Arguments.of("arrayValues", "extendsStringArrays", true),
@@ -1217,6 +1259,29 @@ void testIsArrayGenericTypes() throws
NoSuchFieldException {
assertTrue(TypeUtils.isArrayType(superStringListTypeArray));
}
+ @ParameterizedTest
+ @MethodSource
+ void testHashCodeMatchesJdk(final String fieldName, final Type built)
throws NoSuchFieldException {
+ final Type jdk =
HashCodeTypes.class.getDeclaredField(fieldName).getGenericType();
+ assertEquals(jdk, built);
+ assertEquals(built, jdk);
+
+ final Map<Type, String> jdkMap = new HashMap<>();
+ jdkMap.put(jdk, "value");
+ final Map<Type, String> builtMap = new HashMap<>();
+ builtMap.put(built, "value");
+ final Set<Type> jdkSet = new HashSet<>();
+ jdkSet.add(jdk);
+ final Set<Type> builtSet = new HashSet<>();
+ builtSet.add(built);
+ assertAll(
+ () -> assertEquals(jdk.hashCode(), built.hashCode()),
+ () -> assertEquals("value", jdkMap.get(built)),
+ () -> assertEquals("value", builtMap.get(jdk)),
+ () -> assertTrue(jdkSet.contains(built)),
+ () -> assertTrue(builtSet.contains(jdk)));
+ }
+
@Test
void testIsArrayTypeClasses() {
assertTrue(TypeUtils.isArrayType(boolean[].class));