Infer return type of parametrized factory methods
Currently, if a factory method is parameterized and the corresponding variable types are declared on the method itself instead of on the enclosing class or interface, Spring always predicts the return type to be Object, even if the return type can be explicitly inferred from the method signature and supplied arguments (which are available in the bean definition). This commit introduces a new resolveParameterizedReturnType() method in GenericTypeResolver that attempts to infer the concrete type for the generic return type of a given parameterized method, falling back to the standard return type if necessary. Furthermore, AbstractAutowireCapableBeanFactory now delegates to resolveParameterizedReturnType() when predicting the return type for factory methods. resolveParameterizedReturnType() is capable of inferring the concrete type for return type T for method signatures similar to the following. Such methods may potentially be static. Also, the formal argument list for such methods is not limited to a single argument. - public <T> T foo(Class<T> clazz) - public <T> T foo(Object obj, Class<T> clazz) - public <V, T> T foo(V obj, Class<T> clazz) - public <T> T foo(T obj) Issue: SPR-9493
This commit is contained in:
parent
9fc05a80d0
commit
c461455c7c
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
* Copyright 2002-2012 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.
|
||||
|
@ -63,6 +63,7 @@ import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
|
|||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
||||
import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder;
|
||||
import org.springframework.beans.factory.config.DependencyDescriptor;
|
||||
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor;
|
||||
import org.springframework.beans.factory.config.SmartInstantiationAwareBeanPostProcessor;
|
||||
|
@ -106,6 +107,7 @@ import org.springframework.util.StringUtils;
|
|||
* @author Mark Fisher
|
||||
* @author Costin Leau
|
||||
* @author Chris Beams
|
||||
* @author Sam Brannen
|
||||
* @since 13.02.2004
|
||||
* @see RootBeanDefinition
|
||||
* @see DefaultListableBeanFactory
|
||||
|
@ -628,16 +630,26 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
|
|||
return null;
|
||||
}
|
||||
|
||||
List<ValueHolder> argumentValues = mbd.getConstructorArgumentValues().getGenericArgumentValues();
|
||||
Object[] args = new Object[argumentValues.size()];
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
args[i] = argumentValues.get(i).getValue();
|
||||
}
|
||||
|
||||
// If all factory methods have the same return type, return that type.
|
||||
// Can't clearly figure out exact method due to type converting / autowiring!
|
||||
int minNrOfArgs = mbd.getConstructorArgumentValues().getArgumentCount();
|
||||
Method[] candidates = ReflectionUtils.getUniqueDeclaredMethods(factoryClass);
|
||||
Set<Class> returnTypes = new HashSet<Class>(1);
|
||||
Set<Class<?>> returnTypes = new HashSet<Class<?>>(1);
|
||||
for (Method factoryMethod : candidates) {
|
||||
if (Modifier.isStatic(factoryMethod.getModifiers()) == isStatic &&
|
||||
factoryMethod.getName().equals(mbd.getFactoryMethodName()) &&
|
||||
factoryMethod.getParameterTypes().length >= minNrOfArgs) {
|
||||
returnTypes.add(factoryMethod.getReturnType());
|
||||
if (Modifier.isStatic(factoryMethod.getModifiers()) == isStatic
|
||||
&& factoryMethod.getName().equals(mbd.getFactoryMethodName())
|
||||
&& factoryMethod.getParameterTypes().length >= minNrOfArgs) {
|
||||
|
||||
Class<?> returnType = GenericTypeResolver.resolveParameterizedReturnType(factoryMethod, args);
|
||||
if (returnType != null) {
|
||||
returnTypes.add(returnType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2009 the original author or authors.
|
||||
* Copyright 2002-2012 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.
|
||||
|
@ -29,6 +29,8 @@ import java.util.Map;
|
|||
import java.util.Set;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.easymock.EasyMock;
|
||||
import org.junit.Test;
|
||||
import test.beans.GenericBean;
|
||||
import test.beans.GenericIntegerBean;
|
||||
|
@ -46,6 +48,7 @@ import org.springframework.core.io.UrlResource;
|
|||
/**
|
||||
* @author Juergen Hoeller
|
||||
* @author Chris Beams
|
||||
* @author Sam Brannen
|
||||
* @since 20.01.2006
|
||||
*/
|
||||
public class BeanFactoryGenericsTests {
|
||||
|
@ -619,6 +622,30 @@ public class BeanFactoryGenericsTests {
|
|||
assertEquals(new URL("http://www.springframework.org"), us.iterator().next());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests support for parameterized {@code factory-method} declarations such
|
||||
* as EasyMock's {@code createMock()} method which has the following signature.
|
||||
*
|
||||
* <pre>{@code
|
||||
* public static <T> T createMock(Class<T> toMock)
|
||||
* }</pre>
|
||||
*
|
||||
* @since 3.2
|
||||
* @see SPR-9493
|
||||
*/
|
||||
@Test
|
||||
public void parameterizedFactoryMethod() {
|
||||
RootBeanDefinition rbd = new RootBeanDefinition(EasyMock.class);
|
||||
rbd.setFactoryMethodName("createMock");
|
||||
rbd.getConstructorArgumentValues().addGenericArgumentValue(Runnable.class);
|
||||
|
||||
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
|
||||
bf.registerBeanDefinition("easyMock", rbd);
|
||||
|
||||
Map<String, Runnable> beans = bf.getBeansOfType(Runnable.class);
|
||||
assertEquals(1, beans.size());
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public static class NamedUrlList extends LinkedList<URL> {
|
||||
|
|
|
@ -28,7 +28,6 @@ import test.beans.TestBean;
|
|||
import org.springframework.beans.factory.BeanCreationException;
|
||||
import org.springframework.beans.factory.BeanDefinitionStoreException;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
|
||||
/**
|
||||
|
|
|
@ -19,6 +19,10 @@
|
|||
<level value="debug" />
|
||||
</logger>
|
||||
|
||||
<logger name="org.springframework.core.GenericTypeResolver">
|
||||
<level value="warn" />
|
||||
</logger>
|
||||
|
||||
<!-- Root Logger -->
|
||||
<root>
|
||||
<priority value="warn" />
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
* Copyright 2002-2012 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.
|
||||
|
@ -30,7 +30,11 @@ import java.util.HashMap;
|
|||
import java.util.Map;
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* Helper class for resolving generic types against type variables.
|
||||
|
@ -40,11 +44,14 @@ import org.springframework.util.Assert;
|
|||
*
|
||||
* @author Juergen Hoeller
|
||||
* @author Rob Harrop
|
||||
* @author Sam Brannen
|
||||
* @since 2.5.2
|
||||
* @see GenericCollectionTypeResolver
|
||||
*/
|
||||
public abstract class GenericTypeResolver {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(GenericTypeResolver.class);
|
||||
|
||||
/** Cache from Class to TypeVariable Map */
|
||||
private static final Map<Class, Reference<Map<TypeVariable, Type>>> typeVariableCache =
|
||||
Collections.synchronizedMap(new WeakHashMap<Class, Reference<Map<TypeVariable, Type>>>());
|
||||
|
@ -88,18 +95,144 @@ public abstract class GenericTypeResolver {
|
|||
}
|
||||
|
||||
/**
|
||||
* Determine the target type for the generic return type of the given method.
|
||||
* Determine the target type for the generic return type of the given method,
|
||||
* where the type variable is 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
|
||||
*/
|
||||
public static Class<?> resolveReturnType(Method method, Class clazz) {
|
||||
public static Class<?> resolveReturnType(Method method, Class<?> clazz) {
|
||||
Assert.notNull(method, "Method must not be null");
|
||||
Type genericType = method.getGenericReturnType();
|
||||
Assert.notNull(clazz, "Class must not be null");
|
||||
Map<TypeVariable, Type> typeVariableMap = getTypeVariableMap(clazz);
|
||||
Type rawType = getRawType(genericType, typeVariableMap);
|
||||
return (rawType instanceof Class ? (Class) rawType : method.getReturnType());
|
||||
return (rawType instanceof Class ? (Class<?>) rawType : method.getReturnType());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* <p>For example, given a factory method with the following signature,
|
||||
* if {@code resolveParameterizedReturnType()} is invoked with the reflected
|
||||
* method for {@code creatProxy()} and an {@code Object[]} array containing
|
||||
* {@code MyService.class}, {@code resolveParameterizedReturnType()} 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
|
||||
* 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
|
||||
* Method#getGenericParameterTypes() formal argument list} for the given
|
||||
* method</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param method the method to introspect, never {@code null}
|
||||
* @param args the arguments that will be supplied to the method when it is
|
||||
* invoked, never {@code null}
|
||||
* @return the resolved target return type, the standard return type, or
|
||||
* {@code null}
|
||||
* @since 3.2
|
||||
* @see #resolveReturnType
|
||||
*/
|
||||
public static Class<?> resolveParameterizedReturnType(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].",
|
||||
method.toGenericString(), ObjectUtils.nullSafeToString(args)));
|
||||
}
|
||||
|
||||
// No declared generic types to inspect, so just return the standard return type.
|
||||
if (declaredGenericTypes.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) {
|
||||
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)) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(String.format(
|
||||
"Found declared generic type [%s] that matches the target return type [%s].",
|
||||
currentType, genericReturnType));
|
||||
}
|
||||
locallyDeclaredGenericTypeMatchesReturnType = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (locallyDeclaredGenericTypeMatchesReturnType) {
|
||||
for (int i = 0; i < genericArgumentTypes.length; i++) {
|
||||
final Type currentArgumentType = genericArgumentTypes[i];
|
||||
|
||||
if (currentArgumentType.equals(genericReturnType)) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(String.format(
|
||||
"Found generic method argument at index [%s] that matches the target return type.", i));
|
||||
}
|
||||
return args[i].getClass();
|
||||
}
|
||||
|
||||
if (currentArgumentType instanceof ParameterizedType) {
|
||||
ParameterizedType parameterizedType = (ParameterizedType) currentArgumentType;
|
||||
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
|
||||
|
||||
for (int j = 0; j < actualTypeArguments.length; j++) {
|
||||
final Type typeArg = actualTypeArguments[j];
|
||||
|
||||
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.",
|
||||
i));
|
||||
}
|
||||
|
||||
if (args[i] instanceof Class) {
|
||||
return (Class<?>) args[i];
|
||||
} else {
|
||||
// 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].",
|
||||
typeArg, method.toGenericString()));
|
||||
|
||||
// For now, just fall back...
|
||||
return method.getReturnType();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back...
|
||||
return method.getReturnType();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -128,7 +261,7 @@ public abstract class GenericTypeResolver {
|
|||
return null;
|
||||
}
|
||||
}
|
||||
return GenericTypeResolver.resolveTypeArgument((Class<?>) returnType, genericIfc);
|
||||
return resolveTypeArgument((Class<?>) returnType, genericIfc);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -186,7 +319,7 @@ public abstract class GenericTypeResolver {
|
|||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
private static Class[] doResolveTypeArguments(Class ownerClass, Type ifc, Class genericIfc) {
|
||||
if (ifc instanceof ParameterizedType) {
|
||||
ParameterizedType paramIfc = (ParameterizedType) ifc;
|
||||
|
@ -236,7 +369,6 @@ public abstract class GenericTypeResolver {
|
|||
return (arg instanceof Class ? (Class) arg : Object.class);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Resolve the specified generic type against the given TypeVariable map.
|
||||
* @param genericType the generic type to resolve
|
||||
|
@ -272,9 +404,9 @@ public abstract class GenericTypeResolver {
|
|||
}
|
||||
|
||||
/**
|
||||
* Build a mapping of {@link TypeVariable#getName TypeVariable names} to concrete
|
||||
* {@link Class} for the specified {@link Class}. Searches all super types,
|
||||
* enclosing types and interfaces.
|
||||
* Build a mapping of {@link TypeVariable#getName TypeVariable names} to
|
||||
* {@link Class concrete classes} for the specified {@link Class}. Searches
|
||||
* all super types, enclosing types and interfaces.
|
||||
*/
|
||||
public static Map<TypeVariable, Type> getTypeVariableMap(Class clazz) {
|
||||
Reference<Map<TypeVariable, Type>> ref = typeVariableCache.get(clazz);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
* Copyright 2002-2012 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.
|
||||
|
@ -16,51 +16,116 @@
|
|||
|
||||
package org.springframework.core;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import static org.springframework.core.GenericTypeResolver.*;
|
||||
import static org.springframework.util.ReflectionUtils.*;
|
||||
|
||||
/**
|
||||
* @author Juergen Hoeller
|
||||
* @author Sam Brannen
|
||||
*/
|
||||
public class GenericTypeResolverTests {
|
||||
|
||||
@Test
|
||||
public void testSimpleInterfaceType() {
|
||||
assertEquals(String.class, GenericTypeResolver.resolveTypeArgument(MySimpleInterfaceType.class, MyInterfaceType.class));
|
||||
public void simpleInterfaceType() {
|
||||
assertEquals(String.class, resolveTypeArgument(MySimpleInterfaceType.class, MyInterfaceType.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimpleCollectionInterfaceType() {
|
||||
assertEquals(Collection.class, GenericTypeResolver.resolveTypeArgument(MyCollectionInterfaceType.class, MyInterfaceType.class));
|
||||
public void simpleCollectionInterfaceType() {
|
||||
assertEquals(Collection.class, resolveTypeArgument(MyCollectionInterfaceType.class, MyInterfaceType.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimpleSuperclassType() {
|
||||
assertEquals(String.class, GenericTypeResolver.resolveTypeArgument(MySimpleSuperclassType.class, MySuperclassType.class));
|
||||
public void simpleSuperclassType() {
|
||||
assertEquals(String.class, resolveTypeArgument(MySimpleSuperclassType.class, MySuperclassType.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimpleCollectionSuperclassType() {
|
||||
assertEquals(Collection.class, GenericTypeResolver.resolveTypeArgument(MyCollectionSuperclassType.class, MySuperclassType.class));
|
||||
public void simpleCollectionSuperclassType() {
|
||||
assertEquals(Collection.class, resolveTypeArgument(MyCollectionSuperclassType.class, MySuperclassType.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMethodReturnType() {
|
||||
assertEquals(Integer.class, GenericTypeResolver.resolveReturnTypeArgument(ReflectionUtils.findMethod(MyTypeWithMethods.class, "integer"), MyInterfaceType.class));
|
||||
assertEquals(String.class, GenericTypeResolver.resolveReturnTypeArgument(ReflectionUtils.findMethod(MyTypeWithMethods.class, "string"), MyInterfaceType.class));
|
||||
assertEquals(null, GenericTypeResolver.resolveReturnTypeArgument(ReflectionUtils.findMethod(MyTypeWithMethods.class, "raw"), MyInterfaceType.class));
|
||||
assertEquals(null, GenericTypeResolver.resolveReturnTypeArgument(ReflectionUtils.findMethod(MyTypeWithMethods.class, "object"), MyInterfaceType.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNullIfNotResolvable() {
|
||||
public void nullIfNotResolvable() {
|
||||
GenericClass<String> obj = new GenericClass<String>();
|
||||
assertNull(GenericTypeResolver.resolveTypeArgument(obj.getClass(), GenericClass.class));
|
||||
assertNull(resolveTypeArgument(obj.getClass(), GenericClass.class));
|
||||
}
|
||||
|
||||
@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(null, resolveReturnTypeArgument(findMethod(MyTypeWithMethods.class, "raw"), MyInterfaceType.class));
|
||||
assertEquals(null, resolveReturnTypeArgument(findMethod(MyTypeWithMethods.class, "object"), MyInterfaceType.class));
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 3.2
|
||||
*/
|
||||
@Test
|
||||
public void parameterizedMethodReturnTypes() {
|
||||
|
||||
Method notParameterized = findMethod(MyTypeWithMethods.class, "notParameterized", new Class[] {});
|
||||
assertEquals(String.class, resolveParameterizedReturnType(notParameterized, new Object[] {}));
|
||||
|
||||
Method notParameterizedWithArguments = findMethod(MyTypeWithMethods.class, "notParameterizedWithArguments",
|
||||
new Class[] { Integer.class, Boolean.class });
|
||||
assertEquals(String.class,
|
||||
resolveParameterizedReturnType(notParameterizedWithArguments, new Object[] { 99, true }));
|
||||
|
||||
Method createProxy = findMethod(MyTypeWithMethods.class, "createProxy", new Class[] { Object.class });
|
||||
assertEquals(String.class, resolveParameterizedReturnType(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" }));
|
||||
assertEquals(Long.class,
|
||||
resolveParameterizedReturnType(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" }));
|
||||
|
||||
Method createMock = findMethod(MyTypeWithMethods.class, "createMock", new Class[] { Class.class });
|
||||
assertEquals(Runnable.class, resolveParameterizedReturnType(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 }));
|
||||
|
||||
Method createVMock = findMethod(MyTypeWithMethods.class, "createVMock",
|
||||
new Class[] { Object.class, Class.class });
|
||||
assertEquals(Runnable.class,
|
||||
resolveParameterizedReturnType(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
|
||||
// look-up.
|
||||
Method extractValueFrom = findMethod(MyTypeWithMethods.class, "extractValueFrom",
|
||||
new Class[] { MyInterfaceType.class });
|
||||
assertEquals(Object.class,
|
||||
resolveParameterizedReturnType(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.
|
||||
Map<Integer, Boolean> map = new HashMap<Integer, Boolean>();
|
||||
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 }));
|
||||
}
|
||||
|
||||
|
||||
|
@ -73,7 +138,6 @@ public class GenericTypeResolverTests {
|
|||
public class MyCollectionInterfaceType implements MyInterfaceType<Collection<String>> {
|
||||
}
|
||||
|
||||
|
||||
public abstract class MySuperclassType<T> {
|
||||
}
|
||||
|
||||
|
@ -83,12 +147,70 @@ public class GenericTypeResolverTests {
|
|||
public class MyCollectionSuperclassType extends MySuperclassType<Collection<String>> {
|
||||
}
|
||||
|
||||
public class MyTypeWithMethods {
|
||||
public static class MyTypeWithMethods {
|
||||
public MyInterfaceType<Integer> 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; }
|
||||
|
||||
/**
|
||||
* Simulates a factory method that wraps the supplied object in a proxy
|
||||
* of the same type.
|
||||
*/
|
||||
public static <T> 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!
|
||||
*/
|
||||
public static <T> T createNamedProxy(String name, T object) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulates factory methods found in libraries such as Mockito and EasyMock.
|
||||
*/
|
||||
public static <MOCK> MOCK createMock(Class<MOCK> toMock) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to {@link #createMock(Class)} but adds an additional method
|
||||
* argument before the parameterized argument.
|
||||
*/
|
||||
public static <T> T createNamedMock(String name, Class<T> toMock) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to {@link #createNamedMock(String, Class)} but adds an additional
|
||||
* parameterized type.
|
||||
*/
|
||||
public static <V extends Object, T> T createVMock(V name, Class<T> toMock) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract some value of the type supported by the interface (i.e., by
|
||||
* a concrete, non-generic implementation of the interface).
|
||||
*/
|
||||
public static <T> T extractValueFrom(MyInterfaceType<T> myInterfaceType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract some magic value from the supplied map.
|
||||
*/
|
||||
public static <K, V> V extractMagicValue(Map<K, V> map) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class GenericClass<T> {
|
||||
|
|
|
@ -15,6 +15,10 @@
|
|||
<level value="warn" />
|
||||
</logger>
|
||||
|
||||
<logger name="org.springframework.core.GenericTypeResolver">
|
||||
<level value="warn" />
|
||||
</logger>
|
||||
|
||||
<!-- Root Logger -->
|
||||
<root>
|
||||
<priority value="warn" />
|
||||
|
|
Loading…
Reference in New Issue