From 3bfddc501def1b090828312fe3b3f2b5de8d8b3c Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Thu, 31 Oct 2013 17:39:39 +0100 Subject: [PATCH] Backported further GenericTypeResolver tests Issue: SPR-11052 --- .../core/GenericTypeResolver.java | 18 +- .../core/GenericTypeResolverTests.java | 178 ++++++++++++++---- 2 files changed, 156 insertions(+), 40 deletions(-) diff --git a/spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java b/spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java index 886549630a8..b860aaafca8 100644 --- a/spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java +++ b/spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java @@ -231,7 +231,7 @@ public abstract class GenericTypeResolver { * @return the resolved type of the argument, or {@code null} if not resolvable */ public static Class resolveTypeArgument(Class clazz, Class genericIfc) { - Class[] typeArgs = resolveTypeArguments(clazz, genericIfc); + Class[] typeArgs = resolveTypeArguments(clazz, genericIfc); if (typeArgs == null) { return null; } @@ -246,21 +246,25 @@ public abstract class GenericTypeResolver { * Resolve the type arguments of the given generic interface against the given * target class which is assumed to implement the generic interface and possibly * declare concrete types for its type variables. + *

Note: In Spring 3.2, this method doesn't return {@code null} in all scenarios + * where it should. To be fixed in Spring 4.0; for client code, this just means it + * might see {@code null} in a few more cases then where it now sees an array with + * a single {@link Object} type. * @param clazz the target class to check against * @param genericIfc the generic interface or superclass to resolve the type argument from * @return the resolved type of each argument, with the array size matching the * number of actual type arguments, or {@code null} if not resolvable */ - public static Class[] resolveTypeArguments(Class clazz, Class genericIfc) { + public static Class[] resolveTypeArguments(Class clazz, Class genericIfc) { return doResolveTypeArguments(clazz, clazz, genericIfc); } - private static Class[] doResolveTypeArguments(Class ownerClass, Class classToIntrospect, Class genericIfc) { + private static Class[] doResolveTypeArguments(Class ownerClass, Class classToIntrospect, Class genericIfc) { while (classToIntrospect != null) { if (genericIfc.isInterface()) { Type[] ifcs = classToIntrospect.getGenericInterfaces(); for (Type ifc : ifcs) { - Class[] result = doResolveTypeArguments(ownerClass, ifc, genericIfc); + Class[] result = doResolveTypeArguments(ownerClass, ifc, genericIfc); if (result != null) { return result; } @@ -268,7 +272,7 @@ public abstract class GenericTypeResolver { } else { try { - Class[] result = doResolveTypeArguments(ownerClass, classToIntrospect.getGenericSuperclass(), genericIfc); + Class[] result = doResolveTypeArguments(ownerClass, classToIntrospect.getGenericSuperclass(), genericIfc); if (result != null) { return result; } @@ -283,13 +287,13 @@ public abstract class GenericTypeResolver { return null; } - private static Class[] doResolveTypeArguments(Class ownerClass, Type ifc, Class genericIfc) { + private static Class[] doResolveTypeArguments(Class ownerClass, Type ifc, Class genericIfc) { if (ifc instanceof ParameterizedType) { ParameterizedType paramIfc = (ParameterizedType) ifc; Type rawType = paramIfc.getRawType(); if (genericIfc.equals(rawType)) { Type[] typeArgs = paramIfc.getActualTypeArguments(); - Class[] result = new Class[typeArgs.length]; + Class[] result = new Class[typeArgs.length]; for (int i = 0; i < typeArgs.length; i++) { Type arg = typeArgs[i]; result[i] = extractClass(ownerClass, arg); diff --git a/spring-core/src/test/java/org/springframework/core/GenericTypeResolverTests.java b/spring-core/src/test/java/org/springframework/core/GenericTypeResolverTests.java index 9f7d37b3d73..ca335500027 100644 --- a/spring-core/src/test/java/org/springframework/core/GenericTypeResolverTests.java +++ b/spring-core/src/test/java/org/springframework/core/GenericTypeResolverTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,10 +21,12 @@ import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import java.util.Collection; import java.util.HashMap; +import java.util.List; import java.util.Map; import org.junit.Test; +import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; import static org.springframework.core.GenericTypeResolver.*; import static org.springframework.util.ReflectionUtils.*; @@ -63,28 +65,33 @@ public class GenericTypeResolverTests { @Test public void methodReturnTypes() { - assertEquals(Integer.class, resolveReturnTypeArgument(findMethod(MyTypeWithMethods.class, "integer"), MyInterfaceType.class)); - assertEquals(String.class, resolveReturnTypeArgument(findMethod(MyTypeWithMethods.class, "string"), MyInterfaceType.class)); + assertEquals(Integer.class, + resolveReturnTypeArgument(findMethod(MyTypeWithMethods.class, "integer"), MyInterfaceType.class)); + assertEquals(String.class, + resolveReturnTypeArgument(findMethod(MyTypeWithMethods.class, "string"), MyInterfaceType.class)); assertEquals(null, resolveReturnTypeArgument(findMethod(MyTypeWithMethods.class, "raw"), MyInterfaceType.class)); - assertEquals(null, resolveReturnTypeArgument(findMethod(MyTypeWithMethods.class, "object"), MyInterfaceType.class)); + assertEquals(null, + resolveReturnTypeArgument(findMethod(MyTypeWithMethods.class, "object"), MyInterfaceType.class)); } @Test public void testResolveType() { - Method intMessageMethod = findMethod(MyTypeWithMethods.class, "readIntegerInputMessage", MyInterfaceType.class); - MethodParameter intMessageMethodParam = new MethodParameter(intMessageMethod, 0); - assertEquals(MyInterfaceType.class, - resolveType(intMessageMethodParam.getGenericParameterType(), new HashMap())); - - Method intArrMessageMethod = findMethod(MyTypeWithMethods.class, "readIntegerArrayInputMessage", MyInterfaceType[].class); - MethodParameter intArrMessageMethodParam = new MethodParameter(intArrMessageMethod, 0); - assertEquals(MyInterfaceType[].class, - resolveType(intArrMessageMethodParam.getGenericParameterType(), new HashMap())); - - Method genericArrMessageMethod = findMethod(MySimpleTypeWithMethods.class, "readGenericArrayInputMessage", Object[].class); - MethodParameter genericArrMessageMethodParam = new MethodParameter(genericArrMessageMethod, 0); - Map varMap = getTypeVariableMap(MySimpleTypeWithMethods.class); - assertEquals(Integer[].class, resolveType(genericArrMessageMethodParam.getGenericParameterType(), varMap)); + Method intMessageMethod = findMethod(MyTypeWithMethods.class, "readIntegerInputMessage", MyInterfaceType.class); + MethodParameter intMessageMethodParam = new MethodParameter(intMessageMethod, 0); + assertEquals(MyInterfaceType.class, + resolveType(intMessageMethodParam.getGenericParameterType(), new HashMap())); + + Method intArrMessageMethod = findMethod(MyTypeWithMethods.class, "readIntegerArrayInputMessage", + MyInterfaceType[].class); + MethodParameter intArrMessageMethodParam = new MethodParameter(intArrMessageMethod, 0); + assertEquals(MyInterfaceType[].class, + resolveType(intArrMessageMethodParam.getGenericParameterType(), new HashMap())); + + Method genericArrMessageMethod = findMethod(MySimpleTypeWithMethods.class, "readGenericArrayInputMessage", + Object[].class); + MethodParameter genericArrMessageMethodParam = new MethodParameter(genericArrMessageMethod, 0); + Map varMap = getTypeVariableMap(MySimpleTypeWithMethods.class); + assertEquals(Integer[].class, resolveType(genericArrMessageMethodParam.getGenericParameterType(), varMap)); } @Test @@ -92,7 +99,76 @@ public class GenericTypeResolverTests { assertEquals(B.class, resolveTypeArgument(TestImpl.class, ITest.class)); } + @Test + public void testGetTypeVariableMap() throws Exception { + Map map; + + map = GenericTypeResolver.getTypeVariableMap(MySimpleInterfaceType.class); + assertThat(map.toString(), equalTo("{T=class java.lang.String}")); + + map = GenericTypeResolver.getTypeVariableMap(MyCollectionInterfaceType.class); + assertThat(map.toString(), equalTo("{T=java.util.Collection}")); + + map = GenericTypeResolver.getTypeVariableMap(MyCollectionSuperclassType.class); + assertThat(map.toString(), equalTo("{T=java.util.Collection}")); + + map = GenericTypeResolver.getTypeVariableMap(MySimpleTypeWithMethods.class); + assertThat(map.toString(), equalTo("{T=class java.lang.Integer}")); + + map = GenericTypeResolver.getTypeVariableMap(TopLevelClass.class); + assertThat(map.toString(), equalTo("{}")); + + map = GenericTypeResolver.getTypeVariableMap(TypedTopLevelClass.class); + assertThat(map.toString(), equalTo("{T=class java.lang.Integer}")); + + map = GenericTypeResolver.getTypeVariableMap(TypedTopLevelClass.TypedNested.class); + assertThat(map.size(), equalTo(2)); + Type t = null; + Type x = null; + for (Map.Entry entry : map.entrySet()) { + if(entry.getKey().toString().equals("T")) { + t = entry.getValue(); + } + else { + x = entry.getValue(); + } + } + assertThat(t, equalTo((Type) Integer.class)); + assertThat(x, equalTo((Type) Long.class)); + } + + @Test + public void getGenericsCannotBeResolved() throws Exception { + // SPR-11030 + Class[] resolved = GenericTypeResolver.resolveTypeArguments(List.class, Iterable.class); + // Note: to be changed to return null in Spring 4.0 + assertThat(resolved, equalTo(new Class[] {Object.class})); + } + @Test + public void getRawMapTypeCannotBeResolved() throws Exception { + // SPR-11052 + Class[] resolved = GenericTypeResolver.resolveTypeArguments(Map.class, Map.class); + assertNull(resolved); + } + + @Test + public void getGenericsOnArrayFromParamCannotBeResolved() throws Exception { + // SPR-11044 + MethodParameter methodParameter = MethodParameter.forMethodOrConstructor( + WithArrayBase.class.getDeclaredMethod("array", Object[].class), 0); + Class resolved = GenericTypeResolver.resolveParameterType(methodParameter, WithArray.class); + assertThat(resolved, equalTo((Class) Object[].class)); + } + + @Test + public void getGenericsOnArrayFromReturnCannotBeResolved() throws Exception { + // SPR-11044 + Class resolved = GenericTypeResolver.resolveReturnType( + WithArrayBase.class.getDeclaredMethod("array", Object[].class), + WithArray.class); + assertThat(resolved, equalTo((Class) Object[].class)); + } public interface MyInterfaceType { } @@ -113,26 +189,44 @@ public class GenericTypeResolverTests { } public static class MyTypeWithMethods { - public MyInterfaceType integer() { return null; } - public MySimpleInterfaceType string() { return null; } - public Object object() { return null; } + + public MyInterfaceType integer() { + return null; + } + + public MySimpleInterfaceType string() { + return null; + } + + public Object object() { + return null; + } + @SuppressWarnings("rawtypes") - public MyInterfaceType raw() { return null; } - public String notParameterized() { return null; } - public String notParameterizedWithArguments(Integer x, Boolean b) { return null; } + public MyInterfaceType raw() { + return null; + } + + public String notParameterized() { + return null; + } + + public String notParameterizedWithArguments(Integer x, Boolean b) { + return null; + } /** - * Simulates a factory method that wraps the supplied object in a proxy - * of the same type. + * Simulates a factory method that wraps the supplied object in a proxy of the + * same type. */ public static T createProxy(T object) { return null; } /** - * Similar to {@link #createProxy(Object)} but adds an additional argument - * before the argument of type {@code T}. Note that they may potentially - * be of the same time when invoked! + * Similar to {@link #createProxy(Object)} but adds an additional argument before + * the argument of type {@code T}. Note that they may potentially be of the same + * time when invoked! */ public static T createNamedProxy(String name, T object) { return null; @@ -146,8 +240,8 @@ public class GenericTypeResolverTests { } /** - * Similar to {@link #createMock(Class)} but adds an additional method - * argument before the parameterized argument. + * Similar to {@link #createMock(Class)} but adds an additional method argument + * before the parameterized argument. */ public static T createNamedMock(String name, Class toMock) { return null; @@ -162,8 +256,8 @@ public class GenericTypeResolverTests { } /** - * Extract some value of the type supported by the interface (i.e., by - * a concrete, non-generic implementation of the interface). + * Extract some value of the type supported by the interface (i.e., by a concrete, + * non-generic implementation of the interface). */ public static T extractValueFrom(MyInterfaceType myInterfaceType) { return null; @@ -201,4 +295,22 @@ public class GenericTypeResolverTests { class TestImpl> extends ITest{ } + static class TopLevelClass { + class Nested { + } + } + + static class TypedTopLevelClass extends TopLevelClass { + class TypedNested extends Nested { + } + } + + static abstract class WithArrayBase { + + public abstract T[] array(T... args); + } + + static abstract class WithArray extends WithArrayBase { + } + }