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")

Reply via email to