Polish GenericTypeResolver
- renamed resolveParameterizedReturnType() to resolveReturnTypeForGenericMethod() - fleshed out Javadoc for resolveReturnType() and resolveReturnTypeForGenericMethod() regarding declaration of formal type variables - improved wording in log statements and naming of local variables within resolveReturnTypeForGenericMethod() Issue: SPR-9493
This commit is contained in:
parent
3fbcebb82e
commit
826e565b7c
|
|
@ -646,7 +646,7 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
|
|||
&& factoryMethod.getName().equals(mbd.getFactoryMethodName())
|
||||
&& factoryMethod.getParameterTypes().length >= minNrOfArgs) {
|
||||
|
||||
Class<?> returnType = GenericTypeResolver.resolveParameterizedReturnType(factoryMethod, args);
|
||||
Class<?> returnType = GenericTypeResolver.resolveReturnTypeForGenericMethod(factoryMethod, args);
|
||||
if (returnType != null) {
|
||||
returnTypes.add(returnType);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -96,12 +96,12 @@ public abstract class GenericTypeResolver {
|
|||
|
||||
/**
|
||||
* Determine the target type for the generic return type of the given method,
|
||||
* where the type variable is declared on the given class.
|
||||
* where formal type variables are declared on the given class.
|
||||
*
|
||||
* @param method the method to introspect
|
||||
* @param clazz the class to resolve type variables against
|
||||
* @return the corresponding generic parameter or return type
|
||||
* @see #resolveParameterizedReturnType
|
||||
* @see #resolveReturnTypeForGenericMethod
|
||||
*/
|
||||
public static Class<?> resolveReturnType(Method method, Class<?> clazz) {
|
||||
Assert.notNull(method, "Method must not be null");
|
||||
|
|
@ -114,27 +114,27 @@ public abstract class GenericTypeResolver {
|
|||
|
||||
/**
|
||||
* Determine the target type for the generic return type of the given
|
||||
* <em>parameterized</em> method, where the type variable is declared
|
||||
* on the given method.
|
||||
* <em>generic method</em>, where formal type variables are declared on
|
||||
* the given method itself.
|
||||
*
|
||||
* <p>For example, given a factory method with the following signature,
|
||||
* if {@code resolveParameterizedReturnType()} is invoked with the reflected
|
||||
* if {@code resolveReturnTypeForGenericMethod()} is invoked with the reflected
|
||||
* method for {@code creatProxy()} and an {@code Object[]} array containing
|
||||
* {@code MyService.class}, {@code resolveParameterizedReturnType()} will
|
||||
* {@code MyService.class}, {@code resolveReturnTypeForGenericMethod()} will
|
||||
* infer that the target return type is {@code MyService}.
|
||||
*
|
||||
* <pre>{@code public static <T> T createProxy(Class<T> clazz)}</pre>
|
||||
*
|
||||
* <h4>Possible Return Values</h4>
|
||||
* <ul>
|
||||
* <li>the target return type if it can be inferred</li>
|
||||
* <li>the {@link Method#getReturnType() standard return type}, if
|
||||
* the given {@code method} does not declare any {@link
|
||||
* Method#getTypeParameters() generic types}</li>
|
||||
* <li>the {@link Method#getReturnType() standard return type}, if the
|
||||
* <li>the target return type, if it can be inferred</li>
|
||||
* <li>the {@linkplain Method#getReturnType() standard return type}, if
|
||||
* the given {@code method} does not declare any {@linkplain
|
||||
* Method#getTypeParameters() formal type variables}</li>
|
||||
* <li>the {@linkplain Method#getReturnType() standard return type}, if the
|
||||
* target return type cannot be inferred (e.g., due to type erasure)</li>
|
||||
* <li>{@code null}, if the length of the given arguments array is shorter
|
||||
* than the length of the {@link
|
||||
* than the length of the {@linkplain
|
||||
* Method#getGenericParameterTypes() formal argument list} for the given
|
||||
* method</li>
|
||||
* </ul>
|
||||
|
|
@ -147,60 +147,59 @@ public abstract class GenericTypeResolver {
|
|||
* @since 3.2
|
||||
* @see #resolveReturnType
|
||||
*/
|
||||
public static Class<?> resolveParameterizedReturnType(Method method, Object[] args) {
|
||||
public static Class<?> resolveReturnTypeForGenericMethod(Method method, Object[] args) {
|
||||
Assert.notNull(method, "method must not be null");
|
||||
Assert.notNull(args, "args must not be null");
|
||||
|
||||
final TypeVariable<Method>[] declaredGenericTypes = method.getTypeParameters();
|
||||
final Type genericReturnType = method.getGenericReturnType();
|
||||
final Type[] genericArgumentTypes = method.getGenericParameterTypes();
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(String.format(
|
||||
"Resolving parameterized return type for [%s] with concrete method arguments [%s].",
|
||||
logger.debug(String.format("Resolving return type for [%s] with concrete method arguments [%s].",
|
||||
method.toGenericString(), ObjectUtils.nullSafeToString(args)));
|
||||
}
|
||||
|
||||
// No declared generic types to inspect, so just return the standard return type.
|
||||
if (declaredGenericTypes.length == 0) {
|
||||
final TypeVariable<Method>[] declaredTypeVariables = method.getTypeParameters();
|
||||
final Type genericReturnType = method.getGenericReturnType();
|
||||
final Type[] methodArgumentTypes = method.getGenericParameterTypes();
|
||||
|
||||
// No declared type variables to inspect, so just return the standard return type.
|
||||
if (declaredTypeVariables.length == 0) {
|
||||
return method.getReturnType();
|
||||
}
|
||||
|
||||
// The supplied argument list is too short for the method's signature, so
|
||||
// return null, since such a method invocation would fail.
|
||||
if (args.length < genericArgumentTypes.length) {
|
||||
if (args.length < methodArgumentTypes.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Ensure that the generic type is declared directly on the method
|
||||
// itself, not on the enclosing class or interface.
|
||||
boolean locallyDeclaredGenericTypeMatchesReturnType = false;
|
||||
for (TypeVariable<Method> currentType : declaredGenericTypes) {
|
||||
if (currentType.equals(genericReturnType)) {
|
||||
// Ensure that the type variable (e.g., T) is declared directly on the method
|
||||
// itself (e.g., via <T>), not on the enclosing class or interface.
|
||||
boolean locallyDeclaredTypeVariableMatchesReturnType = false;
|
||||
for (TypeVariable<Method> currentTypeVariable : declaredTypeVariables) {
|
||||
if (currentTypeVariable.equals(genericReturnType)) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(String.format(
|
||||
"Found declared generic type [%s] that matches the target return type [%s].",
|
||||
currentType, genericReturnType));
|
||||
"Found declared type variable [%s] that matches the target return type [%s].",
|
||||
currentTypeVariable, genericReturnType));
|
||||
}
|
||||
locallyDeclaredGenericTypeMatchesReturnType = true;
|
||||
locallyDeclaredTypeVariableMatchesReturnType = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (locallyDeclaredGenericTypeMatchesReturnType) {
|
||||
for (int i = 0; i < genericArgumentTypes.length; i++) {
|
||||
final Type currentArgumentType = genericArgumentTypes[i];
|
||||
if (locallyDeclaredTypeVariableMatchesReturnType) {
|
||||
for (int i = 0; i < methodArgumentTypes.length; i++) {
|
||||
final Type currentMethodArgumentType = methodArgumentTypes[i];
|
||||
|
||||
if (currentArgumentType.equals(genericReturnType)) {
|
||||
if (currentMethodArgumentType.equals(genericReturnType)) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(String.format(
|
||||
"Found generic method argument at index [%s] that matches the target return type.", i));
|
||||
"Found method argument type at index [%s] that matches the target return type.", i));
|
||||
}
|
||||
return args[i].getClass();
|
||||
}
|
||||
|
||||
if (currentArgumentType instanceof ParameterizedType) {
|
||||
ParameterizedType parameterizedType = (ParameterizedType) currentArgumentType;
|
||||
if (currentMethodArgumentType instanceof ParameterizedType) {
|
||||
ParameterizedType parameterizedType = (ParameterizedType) currentMethodArgumentType;
|
||||
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
|
||||
|
||||
for (int j = 0; j < actualTypeArguments.length; j++) {
|
||||
|
|
@ -209,7 +208,7 @@ public abstract class GenericTypeResolver {
|
|||
if (typeArg.equals(genericReturnType)) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(String.format(
|
||||
"Found method argument at index [%s] that is parameterized with a type that matches the target return type.",
|
||||
"Found method argument type at index [%s] that is parameterized with a type argument that matches the target return type.",
|
||||
i));
|
||||
}
|
||||
|
||||
|
|
@ -219,7 +218,7 @@ public abstract class GenericTypeResolver {
|
|||
// Consider adding logic to determine the class of the
|
||||
// J'th typeArg, if possible.
|
||||
logger.info(String.format(
|
||||
"Could not determine the target type for parameterized type [%s] for method [%s].",
|
||||
"Could not determine the target type for type argument [%s] for method [%s].",
|
||||
typeArg, method.toGenericString()));
|
||||
|
||||
// For now, just fall back...
|
||||
|
|
|
|||
|
|
@ -73,51 +73,51 @@ public class GenericTypeResolverTests {
|
|||
* @since 3.2
|
||||
*/
|
||||
@Test
|
||||
public void parameterizedMethodReturnTypes() {
|
||||
public void genericMethodReturnTypes() {
|
||||
|
||||
Method notParameterized = findMethod(MyTypeWithMethods.class, "notParameterized", new Class[] {});
|
||||
assertEquals(String.class, resolveParameterizedReturnType(notParameterized, new Object[] {}));
|
||||
assertEquals(String.class, resolveReturnTypeForGenericMethod(notParameterized, new Object[] {}));
|
||||
|
||||
Method notParameterizedWithArguments = findMethod(MyTypeWithMethods.class, "notParameterizedWithArguments",
|
||||
new Class[] { Integer.class, Boolean.class });
|
||||
assertEquals(String.class,
|
||||
resolveParameterizedReturnType(notParameterizedWithArguments, new Object[] { 99, true }));
|
||||
resolveReturnTypeForGenericMethod(notParameterizedWithArguments, new Object[] { 99, true }));
|
||||
|
||||
Method createProxy = findMethod(MyTypeWithMethods.class, "createProxy", new Class[] { Object.class });
|
||||
assertEquals(String.class, resolveParameterizedReturnType(createProxy, new Object[] { "foo" }));
|
||||
assertEquals(String.class, resolveReturnTypeForGenericMethod(createProxy, new Object[] { "foo" }));
|
||||
|
||||
Method createNamedProxyWithDifferentTypes = findMethod(MyTypeWithMethods.class, "createNamedProxy",
|
||||
new Class[] { String.class, Object.class });
|
||||
// one argument to few
|
||||
assertNull(resolveParameterizedReturnType(createNamedProxyWithDifferentTypes, new Object[] { "enigma" }));
|
||||
assertNull(resolveReturnTypeForGenericMethod(createNamedProxyWithDifferentTypes, new Object[] { "enigma" }));
|
||||
assertEquals(Long.class,
|
||||
resolveParameterizedReturnType(createNamedProxyWithDifferentTypes, new Object[] { "enigma", 99L }));
|
||||
resolveReturnTypeForGenericMethod(createNamedProxyWithDifferentTypes, new Object[] { "enigma", 99L }));
|
||||
|
||||
Method createNamedProxyWithDuplicateTypes = findMethod(MyTypeWithMethods.class, "createNamedProxy",
|
||||
new Class[] { String.class, Object.class });
|
||||
assertEquals(String.class,
|
||||
resolveParameterizedReturnType(createNamedProxyWithDuplicateTypes, new Object[] { "enigma", "foo" }));
|
||||
resolveReturnTypeForGenericMethod(createNamedProxyWithDuplicateTypes, new Object[] { "enigma", "foo" }));
|
||||
|
||||
Method createMock = findMethod(MyTypeWithMethods.class, "createMock", new Class[] { Class.class });
|
||||
assertEquals(Runnable.class, resolveParameterizedReturnType(createMock, new Object[] { Runnable.class }));
|
||||
assertEquals(Runnable.class, resolveReturnTypeForGenericMethod(createMock, new Object[] { Runnable.class }));
|
||||
|
||||
Method createNamedMock = findMethod(MyTypeWithMethods.class, "createNamedMock", new Class[] { String.class,
|
||||
Class.class });
|
||||
assertEquals(Runnable.class,
|
||||
resolveParameterizedReturnType(createNamedMock, new Object[] { "foo", Runnable.class }));
|
||||
resolveReturnTypeForGenericMethod(createNamedMock, new Object[] { "foo", Runnable.class }));
|
||||
|
||||
Method createVMock = findMethod(MyTypeWithMethods.class, "createVMock",
|
||||
new Class[] { Object.class, Class.class });
|
||||
assertEquals(Runnable.class,
|
||||
resolveParameterizedReturnType(createVMock, new Object[] { "foo", Runnable.class }));
|
||||
resolveReturnTypeForGenericMethod(createVMock, new Object[] { "foo", Runnable.class }));
|
||||
|
||||
// Ideally we would expect String.class instead of Object.class, but
|
||||
// resolveParameterizedReturnType() does not currently support this form of
|
||||
// resolveReturnTypeForGenericMethod() does not currently support this form of
|
||||
// look-up.
|
||||
Method extractValueFrom = findMethod(MyTypeWithMethods.class, "extractValueFrom",
|
||||
new Class[] { MyInterfaceType.class });
|
||||
assertEquals(Object.class,
|
||||
resolveParameterizedReturnType(extractValueFrom, new Object[] { new MySimpleInterfaceType() }));
|
||||
resolveReturnTypeForGenericMethod(extractValueFrom, new Object[] { new MySimpleInterfaceType() }));
|
||||
|
||||
// Ideally we would expect Boolean.class instead of Object.class, but this
|
||||
// information is not available at run-time due to type erasure.
|
||||
|
|
@ -125,7 +125,7 @@ public class GenericTypeResolverTests {
|
|||
map.put(0, false);
|
||||
map.put(1, true);
|
||||
Method extractMagicValue = findMethod(MyTypeWithMethods.class, "extractMagicValue", new Class[] { Map.class });
|
||||
assertEquals(Object.class, resolveParameterizedReturnType(extractMagicValue, new Object[] { map }));
|
||||
assertEquals(Object.class, resolveReturnTypeForGenericMethod(extractMagicValue, new Object[] { map }));
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ Changes in version 3.2 M2 (2012-08-xx)
|
|||
--------------------------------------
|
||||
|
||||
* spring-test module now depends on junit:junit-dep (SPR-6966)
|
||||
* now inferring return type of parameterized factory methods (SPR-9493)
|
||||
* now inferring return type of generic factory methods (SPR-9493)
|
||||
* SpEL Tokenizer now supports methods on integers (SPR-9612)
|
||||
* introduced support for case-insensitive null literals in SpEL expressions (SPR-9613)
|
||||
* now using BufferedInputStream in SimpleMetaDataReader to double performance (SPR-9528)
|
||||
|
|
|
|||
Loading…
Reference in New Issue