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 43154e016 Default unbounded WildcardType upper bound to Object in
TypeUtils (#1807)
43154e016 is described below
commit 43154e016ae213a0da5b727cd1aa18e6e0c56adb
Author: alhuda <[email protected]>
AuthorDate: Mon Oct 5 01:23:31 2026 +0530
Default unbounded WildcardType upper bound to Object in TypeUtils (#1807)
* default unbounded WildcardType upper bound to Object in TypeUtils
* drop changes.xml entry
The maintainers keep src/changes/changes.xml themselves, so restore it to
the base version and leave it out of this change.
* match the JDK hashCode in TypeUtils WildcardTypeImpl
A wildcard built by TypeUtils is now equal to the JDK's in both
directions, but WildcardTypeImpl.hashCode() used its own shift/OR formula
while the JDK computes Arrays.hashCode(lowerBounds) ^
Arrays.hashCode(upperBounds), so two equal wildcards hashed differently.
Use the JDK algorithm and assert the hash codes agree in
testWildcardTypeImplicitUpperBound.
* hash the bounds equals compares in TypeUtils WildcardTypeImpl
WildcardTypeImpl.equals() compares normalised bounds: an absent lower
bound and a lone null lower bound are the same, and redundant upper bounds
are dropped. hashCode() hashed the raw arrays, so
wildcardType().withLowerBounds((Type) null).withUpperBounds().build()
equalled WILDCARD_ALL with a different hash code, and likewise a wildcard
with a redundant upper bound.
Hash the same forms equals() compares, keeping an absent lower bound as
the JDK's empty array so the hash code still matches a JDK wildcard.
---
.../apache/commons/lang3/reflect/TypeUtils.java | 13 ++++++-----
.../commons/lang3/reflect/TypeUtilsTest.java | 27 ++++++++++++++++++++++
2 files changed, 34 insertions(+), 6 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 5d90023c6..74feb8848 100644
--- a/src/main/java/org/apache/commons/lang3/reflect/TypeUtils.java
+++ b/src/main/java/org/apache/commons/lang3/reflect/TypeUtils.java
@@ -236,7 +236,9 @@ private static final class WildcardTypeImpl implements
WildcardType {
* @param lowerBounds of this type.
*/
private WildcardTypeImpl(final Type[] upperBounds, final Type[]
lowerBounds) {
- this.upperBounds = upperBounds != null ? upperBounds.clone() :
ArrayUtils.EMPTY_TYPE_ARRAY;
+ // A wildcard with no explicit upper bound has an implicit upper
bound of Object, per
+ // WildcardType.getUpperBounds(); returning an empty array breaks
equals() with a JDK wildcard.
+ this.upperBounds = ArrayUtils.isNotEmpty(upperBounds) ?
upperBounds.clone() : new Type[] {Object.class};
this.lowerBounds = lowerBounds != null ? lowerBounds.clone() :
ArrayUtils.EMPTY_TYPE_ARRAY;
}
@@ -269,11 +271,10 @@ public Type[] getUpperBounds() {
*/
@Override
public int hashCode() {
- int result = 73 << 8;
- result |= Arrays.hashCode(upperBounds);
- result <<= 8;
- result |= Arrays.hashCode(lowerBounds);
- return result;
+ // Same algorithm as the JDK's WildcardType, over the bounds that
equals() compares, so that equal wildcards share a hash code.
+ // A lone null lower bound means no lower bound, which the JDK
reports as an empty array.
+ final Type[] lower = lowerBounds.length == 1 && lowerBounds[0] ==
null ? ArrayUtils.EMPTY_TYPE_ARRAY : lowerBounds;
+ return Arrays.hashCode(lower) ^
Arrays.hashCode(getImplicitUpperBounds(this));
}
/**
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 291cfd45d..8b0749c34 100644
--- a/src/test/java/org/apache/commons/lang3/reflect/TypeUtilsTest.java
+++ b/src/test/java/org/apache/commons/lang3/reflect/TypeUtilsTest.java
@@ -1571,6 +1571,7 @@ void testTypeToString(final Type type) {
void testUnboundedWildcardType() {
final WildcardType unbounded =
TypeUtils.wildcardType().withLowerBounds((Type) null).withUpperBounds().build();
assertTrue(TypeUtils.equals(TypeUtils.WILDCARD_ALL, unbounded));
+ assertEquals(TypeUtils.WILDCARD_ALL.hashCode(), unbounded.hashCode());
assertArrayEquals(new Type[] { Object.class },
TypeUtils.getImplicitUpperBounds(unbounded));
assertArrayEquals(new Type[] { null },
TypeUtils.getImplicitLowerBounds(unbounded));
assertEquals("?", TypeUtils.toString(unbounded));
@@ -1621,6 +1622,32 @@ void testWildcardTypeBuilderDefensiveCopy() {
assertArrayEquals(new Type[] { String.class },
wildcardLower.getLowerBounds());
}
+ @Test
+ void testWildcardTypeImplicitUpperBound() throws NoSuchFieldException {
+ final WildcardType built = TypeUtils.wildcardType().build();
+ // An unbounded wildcard has an implicit upper bound of Object, per
WildcardType.getUpperBounds().
+ assertArrayEquals(new Type[] { Object.class }, built.getUpperBounds());
+ // equals() must be symmetric with a JDK unbounded wildcard.
+ final WildcardType jdk = (WildcardType) ((ParameterizedType)
TypeUtilsTest.class.getField("wildcardComparable")
+ .getGenericType()).getActualTypeArguments()[0];
+ assertEquals(built, jdk);
+ assertEquals(jdk, built);
+ // Equal wildcards must share a hash code.
+ assertEquals(jdk.hashCode(), built.hashCode());
+ assertEquals(TypeUtils.WILDCARD_ALL, built);
+ assertEquals(TypeUtils.WILDCARD_ALL.hashCode(), built.hashCode());
+ }
+
+ @Test
+ void testWildcardTypeRedundantUpperBound() {
+ // equals() ignores a redundant upper bound, so hashCode() must ignore
it too.
+ final WildcardType redundant =
TypeUtils.wildcardType().withUpperBounds(Collection.class, List.class).build();
+ final WildcardType simple =
TypeUtils.wildcardType().withUpperBounds(List.class).build();
+ assertEquals(simple, redundant);
+ assertEquals(redundant, simple);
+ assertEquals(simple.hashCode(), redundant.hashCode());
+ }
+
@Test
void testWrap() {
final Type t = getClass().getTypeParameters()[0];