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:
Sam Brannen
2012-08-05 19:09:38 +02:00
parent 3fbcebb82e
commit 826e565b7c
4 changed files with 53 additions and 54 deletions

View File

@@ -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...