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 <[email protected]> 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")
