From cc6beb2d05347fba64d933906ae2b712b1a43302 Mon Sep 17 00:00:00 2001 From: mbusso Date: Thu, 28 Sep 2017 21:51:24 -0300 Subject: [PATCH] LANG-1348 - StackOverflowError on TypeUtils.toString(...) for a generic return type of Enum.valueOf (closes #292) --- .../commons/lang3/reflect/TypeUtils.java | 46 +++++++++++++++++-- .../commons/lang3/reflect/TypeUtilsTest.java | 6 +++ 2 files changed, 49 insertions(+), 3 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 54c810d04..9e07c33b1 100644 --- a/src/main/java/org/apache/commons/lang3/reflect/TypeUtils.java +++ b/src/main/java/org/apache/commons/lang3/reflect/TypeUtils.java @@ -1788,7 +1788,7 @@ public class TypeUtils { final Type useOwner = p.getOwnerType(); final Class raw = (Class) p.getRawType(); - final Type[] typeArguments = p.getActualTypeArguments(); + if (useOwner == null) { buf.append(raw.getName()); } else { @@ -1800,10 +1800,46 @@ public class TypeUtils { buf.append('.').append(raw.getSimpleName()); } - appendAllTo(buf.append('<'), ", ", typeArguments).append('>'); + final int[] recursiveTypeIndexes = findRecursiveTypes(p); + + if (recursiveTypeIndexes.length > 0) { + appendRecursiveTypes(buf, recursiveTypeIndexes, p.getActualTypeArguments()); + } else { + appendAllTo(buf.append('<'), ", ", p.getActualTypeArguments()).append('>'); + } + return buf.toString(); } + private static void appendRecursiveTypes(StringBuilder buf, int[] recursiveTypeIndexes, Type[] argumentTypes) { + for (int i = 0; i < recursiveTypeIndexes.length; i++) { + appendAllTo(buf.append('<'), ", ", argumentTypes[i].toString()).append('>'); + } + + final Type[] argumentsFiltered = ArrayUtils.removeAll(argumentTypes, recursiveTypeIndexes); + + if (argumentsFiltered.length > 0) { + appendAllTo(buf.append('<'), ", ", argumentsFiltered).append('>'); + } + } + + private static int[] findRecursiveTypes(ParameterizedType p) { + Type[] filteredArgumentTypes = Arrays.copyOf(p.getActualTypeArguments(), p.getActualTypeArguments().length); + int[] indexesToRemove = new int[] {}; + for (int i = 0; i < filteredArgumentTypes.length; i++) { + if (filteredArgumentTypes[i] instanceof TypeVariable) { + if (containsVariableTypeSameParametrizedTypeBound(((TypeVariable) filteredArgumentTypes[i]), p)) { + indexesToRemove = ArrayUtils.add(indexesToRemove, i); + } + } + } + return indexesToRemove; + } + + private static boolean containsVariableTypeSameParametrizedTypeBound(TypeVariable typeVariable, ParameterizedType p) { + return ArrayUtils.contains(typeVariable.getBounds(), p); + } + /** * Format a {@link WildcardType} as a {@link String}. * @param w {@code WildcardType} to format @@ -1840,7 +1876,7 @@ public class TypeUtils { * @return {@code buf} * @since 3.2 */ - private static StringBuilder appendAllTo(final StringBuilder buf, final String sep, final Type... types) { + private static StringBuilder appendAllTo(final StringBuilder buf, final String sep, final T... types) { Validate.notEmpty(Validate.noNullElements(types)); if (types.length > 0) { buf.append(toString(types[0])); @@ -1851,4 +1887,8 @@ public class TypeUtils { return buf; } + private static String toString(T object) { + return object instanceof Type ? toString((Type) object) : object.toString(); + } + } 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 1b88147a3..eea6b87c5 100644 --- a/src/test/java/org/apache/commons/lang3/reflect/TypeUtilsTest.java +++ b/src/test/java/org/apache/commons/lang3/reflect/TypeUtilsTest.java @@ -781,6 +781,12 @@ public class TypeUtilsTest { Assert.assertTrue(TypeUtils.isAssignable(fromType, failingToType)); } + @Test + public void testLANG1348() throws Exception { + final Method method = Enum.class.getMethod("valueOf", Class.class, String.class); + Assert.assertEquals("T extends java.lang.Enum", TypeUtils.toString(method.getGenericReturnType())); + } + public Iterable>> iterable; public static > G stub() {