You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by gg...@apache.org on 2026/10/10 19:30:47 UTC
(commons-lang) 01/02: Sort members
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 cf50fbc4969a84dc5185b1681224d8025b929fc2
Author: Gary Gregory <ga...@gmail.com>
AuthorDate: Sat Oct 10 15:23:33 2026 -0400
Sort members
---
.../apache/commons/lang3/reflect/TypeUtils.java | 94 +++----
.../apache/commons/lang3/AnnotationUtilsTest.java | 68 ++---
.../commons/lang3/reflect/TypeUtilsTest.java | 306 ++++++++++-----------
3 files changed, 234 insertions(+), 234 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 ecd4dfe64..82b80f1b4 100644
--- a/src/main/java/org/apache/commons/lang3/reflect/TypeUtils.java
+++ b/src/main/java/org/apache/commons/lang3/reflect/TypeUtils.java
@@ -383,15 +383,6 @@ public static boolean containsTypeVariables(final Type type) {
return containsTypeVariables(type, newIdentitySet());
}
- /**
- * Creates a set that compares by identity, so that cycle detection never calls {@code hashCode()} or {@code equals()} of a custom {@link Type}.
- *
- * @return A new identity-based set.
- */
- private static Set<Type> newIdentitySet() {
- return Collections.newSetFromMap(new IdentityHashMap<>());
- }
-
private static boolean containsTypeVariables(final Type type, final Set<Type> visited) {
if (type instanceof TypeVariable<?>) {
return true;
@@ -587,7 +578,6 @@ private static Type[] extractTypeArgumentsFrom(final Map<TypeVariable<?>, Type>
return result;
}
-
/**
* Creates a generic array type instance.
*
@@ -599,6 +589,7 @@ public static GenericArrayType genericArrayType(final Type componentType) {
return new GenericArrayTypeImpl(Objects.requireNonNull(componentType, "componentType"));
}
+
/**
* Formats a {@link GenericArrayType} as a {@link String}.
*
@@ -952,6 +943,23 @@ private static Map<TypeVariable<?>, Type> getTypeArguments(final Type type, fina
throw new IllegalStateException("found an unhandled type: " + type);
}
+ /**
+ * Tests whether following the owner types of the given type leads back to a type that was already seen. Only a custom {@link ParameterizedType} can do that.
+ *
+ * @param type The type whose owner chain to check.
+ * @return Whether the owner chain of {@code type} is cyclic.
+ */
+ private static boolean hasCyclicOwnerChain(final ParameterizedType type) {
+ final Set<Type> seen = newIdentitySet();
+ seen.add(type);
+ for (Type owner = type.getOwnerType(); owner instanceof ParameterizedType; owner = ((ParameterizedType) owner).getOwnerType()) {
+ if (!seen.add(owner)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/**
* Tests whether the specified type denotes an array type.
*
@@ -1330,6 +1338,26 @@ public static boolean isInstance(final Object value, final Type type) {
return value == null ? !(type instanceof Class<?>) || !((Class<?>) type).isPrimitive() : isAssignable(value.getClass(), type, null);
}
+ /**
+ * Tests whether two type arrays hold the very same types, compared by identity so that no {@code equals()} or {@code hashCode()} of a custom {@link Type}
+ * is called.
+ *
+ * @param types The first array.
+ * @param otherTypes The second array.
+ * @return Whether both arrays have the same length and the same types at each index.
+ */
+ private static boolean isSameTypes(final Type[] types, final Type[] otherTypes) {
+ if (types.length != otherTypes.length) {
+ return false;
+ }
+ for (int i = 0; i < types.length; i++) {
+ if (types[i] != otherTypes[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
/**
* Maps type variables.
*
@@ -1370,6 +1398,15 @@ private static <T> void mapTypeVariablesToArguments(final Class<T> cls, final Pa
}
}
+ /**
+ * Creates a set that compares by identity, so that cycle detection never calls {@code hashCode()} or {@code equals()} of a custom {@link Type}.
+ *
+ * @return A new identity-based set.
+ */
+ private static Set<Type> newIdentitySet() {
+ return Collections.newSetFromMap(new IdentityHashMap<>());
+ }
+
/**
* Strips out the redundant upper bound types in type variable types and wildcard types (or it would with wildcard types if multiple upper bounds were
* allowed).
@@ -1747,23 +1784,6 @@ public static Type unrollVariables(Map<TypeVariable<?>, Type> typeArguments, fin
return unrollVariables(typeArguments, type, new HashSet<>(), newIdentitySet());
}
- /**
- * Tests whether following the owner types of the given type leads back to a type that was already seen. Only a custom {@link ParameterizedType} can do that.
- *
- * @param type The type whose owner chain to check.
- * @return Whether the owner chain of {@code type} is cyclic.
- */
- private static boolean hasCyclicOwnerChain(final ParameterizedType type) {
- final Set<Type> seen = newIdentitySet();
- seen.add(type);
- for (Type owner = type.getOwnerType(); owner instanceof ParameterizedType; owner = ((ParameterizedType) owner).getOwnerType()) {
- if (!seen.add(owner)) {
- return true;
- }
- }
- return false;
- }
-
private static Type unrollVariables(final Map<TypeVariable<?>, Type> typeArguments, final Type type, final Set<TypeVariable<?>> visited, final Set<Type> unrolling) {
if (type instanceof TypeVariable<?>) {
final TypeVariable<?> var = (TypeVariable<?>) type;
@@ -1824,26 +1844,6 @@ private static Type unrollVariables(final Map<TypeVariable<?>, Type> typeArgumen
return type;
}
- /**
- * Tests whether two type arrays hold the very same types, compared by identity so that no {@code equals()} or {@code hashCode()} of a custom {@link Type}
- * is called.
- *
- * @param types The first array.
- * @param otherTypes The second array.
- * @return Whether both arrays have the same length and the same types at each index.
- */
- private static boolean isSameTypes(final Type[] types, final Type[] otherTypes) {
- if (types.length != otherTypes.length) {
- return false;
- }
- for (int i = 0; i < types.length; i++) {
- if (types[i] != otherTypes[i]) {
- return false;
- }
- }
- return true;
- }
-
/**
* Creates a new {@link WildcardTypeBuilder}.
*
diff --git a/src/test/java/org/apache/commons/lang3/AnnotationUtilsTest.java b/src/test/java/org/apache/commons/lang3/AnnotationUtilsTest.java
index 3bc12fcff..9c6dca93d 100644
--- a/src/test/java/org/apache/commons/lang3/AnnotationUtilsTest.java
+++ b/src/test/java/org/apache/commons/lang3/AnnotationUtilsTest.java
@@ -57,31 +57,6 @@ private static final class AnnotatedClass {
String value() default "member";
}
- @Retention(RetentionPolicy.RUNTIME)
- public @interface RunnableAnnotation {
- Runnable RUNNABLE = () -> {
- // Creates a void synthetic method for the regression test.
- };
-
- String value() default "member";
- }
-
- @Retention(RetentionPolicy.RUNTIME)
- public @interface SupplierAnnotation {
- Supplier<String> SUPPLIER = () -> "constant";
-
- String value() default "member";
- }
-
- @Retention(RetentionPolicy.RUNTIME)
- public @interface ThrowingSupplierAnnotation {
- Supplier<String> SUPPLIER = () -> {
- throw new AssertionError("Annotation member access must not invoke a lambda");
- };
-
- String value() default "member";
- }
-
@Retention(RetentionPolicy.RUNTIME)
public @interface NestAnnotation {
boolean booleanValue();
@@ -108,10 +83,26 @@ private static final class AnnotatedClass {
Class<?>[] types();
}
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface RunnableAnnotation {
+ Runnable RUNNABLE = () -> {
+ // Creates a void synthetic method for the regression test.
+ };
+
+ String value() default "member";
+ }
+
public enum Stooge {
MOE, LARRY, CURLY, JOE, SHEMP
}
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface SupplierAnnotation {
+ Supplier<String> SUPPLIER = () -> "constant";
+
+ String value() default "member";
+ }
+
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
@@ -154,6 +145,15 @@ final class None extends Throwable {
long timeout() default 0L;
}
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface ThrowingSupplierAnnotation {
+ Supplier<String> SUPPLIER = () -> {
+ throw new AssertionError("Annotation member access must not invoke a lambda");
+ };
+
+ String value() default "member";
+ }
+
@TestAnnotation(
booleanValue = false,
booleanValues = { false },
@@ -532,6 +532,15 @@ void testHashCodeWithFunction() {
assertEquals(annotation.hashCode(), AnnotationUtils.hashCode(annotation));
}
+ @Test
+ void testHashCodeWithoutInvokingSupplier() {
+ final ThrowingSupplierAnnotation annotation =
+ AnnotatedClass.class.getAnnotation(ThrowingSupplierAnnotation.class);
+ final int expected = (127 * "value".hashCode()) ^ "member".hashCode();
+
+ assertEquals(expected, AnnotationUtils.hashCode(annotation));
+ }
+
@Test
void testHashCodeWithRunnable() {
final RunnableAnnotation annotation = AnnotatedClass.class.getAnnotation(RunnableAnnotation.class);
@@ -544,15 +553,6 @@ void testHashCodeWithSupplier() {
assertEquals(annotation.hashCode(), AnnotationUtils.hashCode(annotation));
}
- @Test
- void testHashCodeWithoutInvokingSupplier() {
- final ThrowingSupplierAnnotation annotation =
- AnnotatedClass.class.getAnnotation(ThrowingSupplierAnnotation.class);
- final int expected = (127 * "value".hashCode()) ^ "member".hashCode();
-
- assertEquals(expected, AnnotationUtils.hashCode(annotation));
- }
-
@Test
void testIsValidAnnotationMemberType() {
for (final Class<?> type : new Class[] { byte.class, short.class, int.class, char.class,
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 f0e0e9d52..19efa56c5 100644
--- a/src/test/java/org/apache/commons/lang3/reflect/TypeUtilsTest.java
+++ b/src/test/java/org/apache/commons/lang3/reflect/TypeUtilsTest.java
@@ -232,42 +232,6 @@ public static <U> Iterable<U> methodWithGenericReturnType() {
}
}
- static class LexOrdering<T> extends MyOrdering<Iterable<T>> implements Serializable {
- private static final long serialVersionUID = 1L;
- }
-
- interface MyComparator<T> {
- }
-
- static class MyException extends Exception implements Iterable<Throwable> {
-
- private static final long serialVersionUID = 1L;
-
- @Override
- public Iterator<Throwable> iterator() {
- return null;
- }
- }
-
- /** This non-static inner class is parameterized. */
- private class MyInnerClass<T> {
-
- class MyInnerClass2<X> {
- // empty
- }
- }
-
- static class MyNonTransientException extends MyException {
- private static final long serialVersionUID = 1L;
- }
-
- static class MyOrdering<T> implements MyComparator<T> {
- }
-
- public class Other<T> implements This<String, T> {
- // empty
- }
-
/**
* A custom {@link ParameterizedType} with a structural {@code hashCode()}, whose owner and type arguments can be set after construction so that tests can
* create cycles.
@@ -303,11 +267,6 @@ public Type getRawType() {
return rawType;
}
- @Override
- public String toString() {
- return "CyclicParameterizedType<" + rawType.getName() + ">";
- }
-
@Override
public int hashCode() {
// Structural like the JDK implementation: it follows the owner, so a cycle would overflow the stack.
@@ -321,6 +280,47 @@ void setOwnerType(final Type ownerType) {
void setTypeArguments(final Type... typeArguments) {
this.typeArguments = typeArguments;
}
+
+ @Override
+ public String toString() {
+ return "CyclicParameterizedType<" + rawType.getName() + ">";
+ }
+ }
+
+ static class LexOrdering<T> extends MyOrdering<Iterable<T>> implements Serializable {
+ private static final long serialVersionUID = 1L;
+ }
+
+ interface MyComparator<T> {
+ }
+
+ static class MyException extends Exception implements Iterable<Throwable> {
+
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public Iterator<Throwable> iterator() {
+ return null;
+ }
+ }
+
+ /** This non-static inner class is parameterized. */
+ private class MyInnerClass<T> {
+
+ class MyInnerClass2<X> {
+ // empty
+ }
+ }
+
+ static class MyNonTransientException extends MyException {
+ private static final long serialVersionUID = 1L;
+ }
+
+ static class MyOrdering<T> implements MyComparator<T> {
+ }
+
+ public class Other<T> implements This<String, T> {
+ // empty
}
/** LANG-1836: non-static inner classes whose owner type is parameterized. */
@@ -565,6 +565,30 @@ void testContainsTypeVariables() throws NoSuchMethodException {
assertTrue(TypeUtils.containsTypeVariables(Test1.class.getMethod("m17").getGenericReturnType()));
}
+ @Test
+ void testContainsTypeVariablesCyclicOwnerType() {
+ // LANG-1836: a (custom) ParameterizedType that is its own owner must not cause a StackOverflowError.
+ final ParameterizedType[] self = new ParameterizedType[1];
+ self[0] = new ParameterizedType() {
+
+ @Override
+ public Type[] getActualTypeArguments() {
+ return new Type[0];
+ }
+
+ @Override
+ public Type getOwnerType() {
+ return self[0];
+ }
+
+ @Override
+ public Type getRawType() {
+ return Outer.Inner.class;
+ }
+ };
+ assertFalse(TypeUtils.containsTypeVariables(self[0]));
+ }
+
@Test
void testContainsTypeVariablesMultiBoundWildcard() {
final TypeVariable<?> t = getClass().getTypeParameters()[0];
@@ -576,6 +600,17 @@ void testContainsTypeVariablesMultiBoundWildcard() {
assertFalse(TypeUtils.containsTypeVariables(wtNone));
}
+ @Test
+ void testContainsTypeVariablesMutuallyReferencingTypeArguments() {
+ // LANG-1836
+ final CyclicParameterizedType a = new CyclicParameterizedType(List.class);
+ final CyclicParameterizedType b = new CyclicParameterizedType(List.class);
+ a.setTypeArguments(b);
+ b.setTypeArguments(a);
+ assertFalse(TypeUtils.containsTypeVariables(a));
+ assertFalse(TypeUtils.containsTypeVariables(b));
+ }
+
@Test
void testContainsTypeVariablesOwnerType() throws NoSuchFieldException {
// LANG-1836: Outer<T>.Inner
@@ -631,6 +666,31 @@ abstract class Test2<G> {
assertTrue(TypeUtils.containsTypeVariables(Test2.class.getMethod("m17").getGenericReturnType()));
}
+ @Test
+ void testContainsTypeVariablesSelfOwnedTypeWithStructuralHashCode() {
+ // LANG-1836: detecting the cycle must not call hashCode(), which would follow the owner again.
+ final CyclicParameterizedType self = new CyclicParameterizedType(Outer.Inner.class);
+ self.setOwnerType(self);
+ assertFalse(TypeUtils.containsTypeVariables(self));
+ }
+
+ @Test
+ void testContainsTypeVariablesSelfReferencingTypeArgument() {
+ // LANG-1836: a (custom) ParameterizedType that is its own type argument must not cause a StackOverflowError.
+ final CyclicParameterizedType self = new CyclicParameterizedType(List.class);
+ self.setTypeArguments(self);
+ assertFalse(TypeUtils.containsTypeVariables(self));
+ }
+
+ @Test
+ void testContainsTypeVariablesSelfReferencingTypeArgumentBesideVariable() {
+ // LANG-1836: cutting the cycle must not hide a variable that is found elsewhere.
+ final TypeVariable<?> t = Outer.class.getTypeParameters()[0];
+ final CyclicParameterizedType self = new CyclicParameterizedType(Map.class);
+ self.setTypeArguments(self, t);
+ assertTrue(TypeUtils.containsTypeVariables(self));
+ }
+
@Test
void testCyclicGenericArrayTypeToString() {
final GenericArrayType[] holder = new GenericArrayType[1];
@@ -1681,104 +1741,15 @@ void testUnrollVariables() {
}
@Test
- void testUnrollVariablesOwnerType() throws NoSuchFieldException {
- // LANG-1836
- final TypeVariable<?> t = Outer.class.getTypeParameters()[0];
- final Map<TypeVariable<?>, Type> mapping = Collections.<TypeVariable<?>, Type>singletonMap(t, String.class);
- final ParameterizedType outerString = TypeUtils.parameterize(Outer.class, String.class);
- // Outer<T>.Inner -> Outer<String>.Inner
- assertEquals(TypeUtils.parameterizeWithOwner(outerString, Outer.Inner.class),
- TypeUtils.unrollVariables(mapping, Outer.class.getDeclaredField("inner").getGenericType()));
- // Outer<T>.InnerU<Integer> -> Outer<String>.InnerU<Integer>
- assertEquals(TypeUtils.parameterizeWithOwner(outerString, Outer.InnerU.class, Integer.class),
- TypeUtils.unrollVariables(mapping, Outer.class.getDeclaredField("innerUInteger").getGenericType()));
- // Outer<T>.InnerU<T> -> Outer<String>.InnerU<String>
- assertEquals(TypeUtils.parameterizeWithOwner(outerString, Outer.InnerU.class, String.class),
- TypeUtils.unrollVariables(mapping, Outer.class.getDeclaredField("innerUT").getGenericType()));
- // Map.Entry<T, Integer> -> Map.Entry<String, Integer>: the raw owner Map.class is left unchanged.
- assertEquals(TypeUtils.parameterizeWithOwner(Map.class, Map.Entry.class, String.class, Integer.class),
- TypeUtils.unrollVariables(mapping, Outer.class.getDeclaredField("entry").getGenericType()));
- }
-
- @Test
- void testContainsTypeVariablesSelfOwnedTypeWithStructuralHashCode() {
- // LANG-1836: detecting the cycle must not call hashCode(), which would follow the owner again.
- final CyclicParameterizedType self = new CyclicParameterizedType(Outer.Inner.class);
- self.setOwnerType(self);
- assertFalse(TypeUtils.containsTypeVariables(self));
- }
-
- @Test
- void testContainsTypeVariablesCyclicOwnerType() {
- // LANG-1836: a (custom) ParameterizedType that is its own owner must not cause a StackOverflowError.
- final ParameterizedType[] self = new ParameterizedType[1];
- self[0] = new ParameterizedType() {
-
- @Override
- public Type[] getActualTypeArguments() {
- return new Type[0];
- }
-
- @Override
- public Type getOwnerType() {
- return self[0];
- }
-
- @Override
- public Type getRawType() {
- return Outer.Inner.class;
- }
- };
- assertFalse(TypeUtils.containsTypeVariables(self[0]));
- }
-
- @Test
- void testContainsTypeVariablesSelfReferencingTypeArgument() {
- // LANG-1836: a (custom) ParameterizedType that is its own type argument must not cause a StackOverflowError.
- final CyclicParameterizedType self = new CyclicParameterizedType(List.class);
- self.setTypeArguments(self);
- assertFalse(TypeUtils.containsTypeVariables(self));
- }
-
- @Test
- void testContainsTypeVariablesMutuallyReferencingTypeArguments() {
- // LANG-1836
- final CyclicParameterizedType a = new CyclicParameterizedType(List.class);
- final CyclicParameterizedType b = new CyclicParameterizedType(List.class);
- a.setTypeArguments(b);
- b.setTypeArguments(a);
- assertFalse(TypeUtils.containsTypeVariables(a));
- assertFalse(TypeUtils.containsTypeVariables(b));
- }
-
- @Test
- void testContainsTypeVariablesSelfReferencingTypeArgumentBesideVariable() {
- // LANG-1836: cutting the cycle must not hide a variable that is found elsewhere.
- final TypeVariable<?> t = Outer.class.getTypeParameters()[0];
- final CyclicParameterizedType self = new CyclicParameterizedType(Map.class);
- self.setTypeArguments(self, t);
- assertTrue(TypeUtils.containsTypeVariables(self));
- }
-
- @Test
- void testUnrollVariablesWildcardCycleThroughOwnerType() {
- // LANG-1836: {T -> Outer<? extends T>.Inner} must not cause a StackOverflowError when T is unrolled.
- final TypeVariable<?> t = Outer.class.getTypeParameters()[0];
- final WildcardType extendsT = TypeUtils.wildcardType().withUpperBounds(t).build();
- final Type outerExtendsT = TypeUtils.parameterizeWithOwner(TypeUtils.parameterize(Outer.class, extendsT), Outer.Inner.class);
- final Map<TypeVariable<?>, Type> mapping = Collections.<TypeVariable<?>, Type>singletonMap(t, outerExtendsT);
- assertEquals(outerExtendsT, TypeUtils.unrollVariables(mapping, t));
- }
-
- @Test
- void testUnrollVariablesSelfReferencingTypeArgument() {
- // LANG-1836: the cycle through the type argument is left as it is while the variable is still unrolled.
+ void testUnrollVariablesCycleThroughTypeArgumentAndOwner() {
+ // LANG-1836: Map<Inner, T> whose argument Inner has the Map as its owner. The cycle is left as it is instead of overflowing the stack, while T is unrolled.
final TypeVariable<?> t = Outer.class.getTypeParameters()[0];
- final CyclicParameterizedType self = new CyclicParameterizedType(Map.class);
- self.setTypeArguments(self, t);
+ final CyclicParameterizedType inner = new CyclicParameterizedType(Outer.Inner.class);
+ final CyclicParameterizedType map = new CyclicParameterizedType(Map.class, inner, t);
+ inner.setOwnerType(map);
final Map<TypeVariable<?>, Type> mapping = Collections.<TypeVariable<?>, Type>singletonMap(t, String.class);
- final Type[] unrolledArguments = ((ParameterizedType) TypeUtils.unrollVariables(mapping, self)).getActualTypeArguments();
- assertSame(self, unrolledArguments[0]);
+ final Type[] unrolledArguments = ((ParameterizedType) TypeUtils.unrollVariables(mapping, map)).getActualTypeArguments();
+ assertSame(inner, unrolledArguments[0]);
assertEquals(String.class, unrolledArguments[1]);
}
@@ -1809,26 +1780,32 @@ void testUnrollVariablesCyclicOwnerChainFromEitherType() {
}
@Test
- void testUnrollVariablesSelfOwnedTypeWithTypeVariable() {
+ void testUnrollVariablesOwnerType() throws NoSuchFieldException {
// LANG-1836
final TypeVariable<?> t = Outer.class.getTypeParameters()[0];
- final CyclicParameterizedType self = new CyclicParameterizedType(Outer.InnerU.class, t);
- self.setOwnerType(self);
final Map<TypeVariable<?>, Type> mapping = Collections.<TypeVariable<?>, Type>singletonMap(t, String.class);
- assertSame(self, TypeUtils.unrollVariables(mapping, self));
+ final ParameterizedType outerString = TypeUtils.parameterize(Outer.class, String.class);
+ // Outer<T>.Inner -> Outer<String>.Inner
+ assertEquals(TypeUtils.parameterizeWithOwner(outerString, Outer.Inner.class),
+ TypeUtils.unrollVariables(mapping, Outer.class.getDeclaredField("inner").getGenericType()));
+ // Outer<T>.InnerU<Integer> -> Outer<String>.InnerU<Integer>
+ assertEquals(TypeUtils.parameterizeWithOwner(outerString, Outer.InnerU.class, Integer.class),
+ TypeUtils.unrollVariables(mapping, Outer.class.getDeclaredField("innerUInteger").getGenericType()));
+ // Outer<T>.InnerU<T> -> Outer<String>.InnerU<String>
+ assertEquals(TypeUtils.parameterizeWithOwner(outerString, Outer.InnerU.class, String.class),
+ TypeUtils.unrollVariables(mapping, Outer.class.getDeclaredField("innerUT").getGenericType()));
+ // Map.Entry<T, Integer> -> Map.Entry<String, Integer>: the raw owner Map.class is left unchanged.
+ assertEquals(TypeUtils.parameterizeWithOwner(Map.class, Map.Entry.class, String.class, Integer.class),
+ TypeUtils.unrollVariables(mapping, Outer.class.getDeclaredField("entry").getGenericType()));
}
@Test
- void testUnrollVariablesCycleThroughTypeArgumentAndOwner() {
- // LANG-1836: Map<Inner, T> whose argument Inner has the Map as its owner. The cycle is left as it is instead of overflowing the stack, while T is unrolled.
+ void testUnrollVariablesRepeatedVariable() throws NoSuchFieldException {
final TypeVariable<?> t = Outer.class.getTypeParameters()[0];
- final CyclicParameterizedType inner = new CyclicParameterizedType(Outer.Inner.class);
- final CyclicParameterizedType map = new CyclicParameterizedType(Map.class, inner, t);
- inner.setOwnerType(map);
final Map<TypeVariable<?>, Type> mapping = Collections.<TypeVariable<?>, Type>singletonMap(t, String.class);
- final Type[] unrolledArguments = ((ParameterizedType) TypeUtils.unrollVariables(mapping, map)).getActualTypeArguments();
- assertSame(inner, unrolledArguments[0]);
- assertEquals(String.class, unrolledArguments[1]);
+ // Map<T, T> -> Map<String, String>: every occurrence of T is unrolled, not only the first.
+ assertEquals(TypeUtils.parameterize(Map.class, String.class, String.class),
+ TypeUtils.unrollVariables(mapping, Outer.class.getDeclaredField("map").getGenericType()));
}
@Test
@@ -1842,12 +1819,35 @@ void testUnrollVariablesReturnsTheSameTypeWhenNothingChanges() {
}
@Test
- void testUnrollVariablesRepeatedVariable() throws NoSuchFieldException {
+ void testUnrollVariablesSelfOwnedTypeWithTypeVariable() {
+ // LANG-1836
final TypeVariable<?> t = Outer.class.getTypeParameters()[0];
+ final CyclicParameterizedType self = new CyclicParameterizedType(Outer.InnerU.class, t);
+ self.setOwnerType(self);
final Map<TypeVariable<?>, Type> mapping = Collections.<TypeVariable<?>, Type>singletonMap(t, String.class);
- // Map<T, T> -> Map<String, String>: every occurrence of T is unrolled, not only the first.
- assertEquals(TypeUtils.parameterize(Map.class, String.class, String.class),
- TypeUtils.unrollVariables(mapping, Outer.class.getDeclaredField("map").getGenericType()));
+ assertSame(self, TypeUtils.unrollVariables(mapping, self));
+ }
+
+ @Test
+ void testUnrollVariablesSelfReferencingTypeArgument() {
+ // LANG-1836: the cycle through the type argument is left as it is while the variable is still unrolled.
+ final TypeVariable<?> t = Outer.class.getTypeParameters()[0];
+ final CyclicParameterizedType self = new CyclicParameterizedType(Map.class);
+ self.setTypeArguments(self, t);
+ final Map<TypeVariable<?>, Type> mapping = Collections.<TypeVariable<?>, Type>singletonMap(t, String.class);
+ final Type[] unrolledArguments = ((ParameterizedType) TypeUtils.unrollVariables(mapping, self)).getActualTypeArguments();
+ assertSame(self, unrolledArguments[0]);
+ assertEquals(String.class, unrolledArguments[1]);
+ }
+
+ @Test
+ void testUnrollVariablesWildcardCycleThroughOwnerType() {
+ // LANG-1836: {T -> Outer<? extends T>.Inner} must not cause a StackOverflowError when T is unrolled.
+ final TypeVariable<?> t = Outer.class.getTypeParameters()[0];
+ final WildcardType extendsT = TypeUtils.wildcardType().withUpperBounds(t).build();
+ final Type outerExtendsT = TypeUtils.parameterizeWithOwner(TypeUtils.parameterize(Outer.class, extendsT), Outer.Inner.class);
+ final Map<TypeVariable<?>, Type> mapping = Collections.<TypeVariable<?>, Type>singletonMap(t, outerExtendsT);
+ assertEquals(outerExtendsT, TypeUtils.unrollVariables(mapping, t));
}
@SuppressWarnings("unlikely-arg-type")