Fixed detection of setter in case of getter with covariant return type narrowing
Issue: SPR-10995
This commit is contained in:
parent
42568afb37
commit
045f78e6c5
|
@ -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.
|
||||
|
@ -31,32 +31,32 @@ import org.springframework.util.ClassUtils;
|
|||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Extension of the standard JavaBeans PropertyDescriptor class,
|
||||
* overriding {@code getPropertyType()} such that a generically
|
||||
* declared type will be resolved against the containing bean class.
|
||||
* Extension of the standard JavaBeans {@link PropertyDescriptor} class,
|
||||
* overriding {@code getPropertyType()} such that a generically declared
|
||||
* type variable will be resolved against the containing bean class.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 2.5.2
|
||||
*/
|
||||
class GenericTypeAwarePropertyDescriptor extends PropertyDescriptor {
|
||||
|
||||
private final Class beanClass;
|
||||
private final Class<?> beanClass;
|
||||
|
||||
private final Method readMethod;
|
||||
|
||||
private final Method writeMethod;
|
||||
|
||||
private final Class propertyEditorClass;
|
||||
private final Class<?> propertyEditorClass;
|
||||
|
||||
private volatile Set<Method> ambiguousWriteMethods;
|
||||
|
||||
private Class propertyType;
|
||||
private Class<?> propertyType;
|
||||
|
||||
private MethodParameter writeMethodParameter;
|
||||
|
||||
|
||||
public GenericTypeAwarePropertyDescriptor(Class beanClass, String propertyName,
|
||||
Method readMethod, Method writeMethod, Class propertyEditorClass)
|
||||
public GenericTypeAwarePropertyDescriptor(Class<?> beanClass, String propertyName,
|
||||
Method readMethod, Method writeMethod, Class<?> propertyEditorClass)
|
||||
throws IntrospectionException {
|
||||
|
||||
super(propertyName, null, null);
|
||||
|
@ -69,8 +69,11 @@ class GenericTypeAwarePropertyDescriptor extends PropertyDescriptor {
|
|||
// Fallback: Original JavaBeans introspection might not have found matching setter
|
||||
// method due to lack of bridge method resolution, in case of the getter using a
|
||||
// covariant return type whereas the setter is defined for the concrete property type.
|
||||
writeMethodToUse = ClassUtils.getMethodIfAvailable(this.beanClass,
|
||||
"set" + StringUtils.capitalize(getName()), readMethodToUse.getReturnType());
|
||||
Method candidate = ClassUtils.getMethodIfAvailable(
|
||||
this.beanClass, "set" + StringUtils.capitalize(getName()), (Class<?>[]) null);
|
||||
if (candidate != null && candidate.getParameterTypes().length == 1) {
|
||||
writeMethodToUse = candidate;
|
||||
}
|
||||
}
|
||||
this.readMethod = readMethodToUse;
|
||||
this.writeMethod = writeMethodToUse;
|
||||
|
@ -118,12 +121,12 @@ class GenericTypeAwarePropertyDescriptor extends PropertyDescriptor {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Class getPropertyEditorClass() {
|
||||
public Class<?> getPropertyEditorClass() {
|
||||
return this.propertyEditorClass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized Class getPropertyType() {
|
||||
public synchronized Class<?> getPropertyType() {
|
||||
if (this.propertyType == null) {
|
||||
if (this.readMethod != null) {
|
||||
this.propertyType = GenericTypeResolver.resolveReturnType(this.readMethod, this.beanClass);
|
||||
|
|
|
@ -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.
|
||||
|
@ -71,6 +71,11 @@ public class DerivedTestBean extends TestBean implements Serializable, BeanNameA
|
|||
setSpouse(new TestBean(name));
|
||||
}
|
||||
|
||||
@Override
|
||||
public TestBean getSpouse() {
|
||||
return (TestBean) super.getSpouse();
|
||||
}
|
||||
|
||||
|
||||
public void initialize() {
|
||||
this.initialized = true;
|
||||
|
|
|
@ -35,11 +35,10 @@
|
|||
</property>
|
||||
</bean>
|
||||
|
||||
<bean id="pJenny" class="org.springframework.tests.sample.beans.TestBean" scope="prototype">
|
||||
<bean id="pJenny" class="org.springframework.tests.sample.beans.DerivedTestBean" scope="prototype">
|
||||
<property name="name"><value>Jenny</value></property>
|
||||
<property name="age"><value>30</value></property>
|
||||
<property name="spouse">
|
||||
<!-- Could use id and href -->
|
||||
<ref local="david"/>
|
||||
</property>
|
||||
</bean>
|
||||
|
|
|
@ -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.
|
||||
|
@ -17,15 +17,12 @@
|
|||
package org.springframework.util;
|
||||
|
||||
import java.beans.Introspector;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.lang.reflect.Proxy;
|
||||
|
||||
import java.security.AccessControlException;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
@ -578,7 +575,7 @@ public abstract class ClassUtils {
|
|||
/**
|
||||
* Determine whether the given class has a public constructor with the given signature.
|
||||
* <p>Essentially translates {@code NoSuchMethodException} to "false".
|
||||
* @param clazz the clazz to analyze
|
||||
* @param clazz the clazz to analyze
|
||||
* @param paramTypes the parameter types of the method
|
||||
* @return whether the class has a corresponding constructor
|
||||
* @see Class#getMethod
|
||||
|
@ -591,7 +588,7 @@ public abstract class ClassUtils {
|
|||
* Determine whether the given class has a public constructor with the given signature,
|
||||
* and return it if available (else return {@code null}).
|
||||
* <p>Essentially translates {@code NoSuchMethodException} to {@code null}.
|
||||
* @param clazz the clazz to analyze
|
||||
* @param clazz the clazz to analyze
|
||||
* @param paramTypes the parameter types of the method
|
||||
* @return the constructor, or {@code null} if not found
|
||||
* @see Class#getConstructor
|
||||
|
@ -607,9 +604,9 @@ public abstract class ClassUtils {
|
|||
}
|
||||
|
||||
/**
|
||||
* Determine whether the given class has a method with the given signature.
|
||||
* Determine whether the given class has a public method with the given signature.
|
||||
* <p>Essentially translates {@code NoSuchMethodException} to "false".
|
||||
* @param clazz the clazz to analyze
|
||||
* @param clazz the clazz to analyze
|
||||
* @param methodName the name of the method
|
||||
* @param paramTypes the parameter types of the method
|
||||
* @return whether the class has a corresponding method
|
||||
|
@ -620,12 +617,15 @@ public abstract class ClassUtils {
|
|||
}
|
||||
|
||||
/**
|
||||
* Determine whether the given class has a method with the given signature,
|
||||
* Determine whether the given class has a public method with the given signature,
|
||||
* and return it if available (else throws an {@code IllegalStateException}).
|
||||
* <p>In case of any signature specified, only returns the method if there is a
|
||||
* unique candidate, i.e. a single public method with the specified name.
|
||||
* <p>Essentially translates {@code NoSuchMethodException} to {@code IllegalStateException}.
|
||||
* @param clazz the clazz to analyze
|
||||
* @param clazz the clazz to analyze
|
||||
* @param methodName the name of the method
|
||||
* @param paramTypes the parameter types of the method
|
||||
* (may be {@code null} to indicate any signature)
|
||||
* @return the method (never {@code null})
|
||||
* @throws IllegalStateException if the method has not been found
|
||||
* @see Class#getMethod
|
||||
|
@ -633,31 +633,69 @@ public abstract class ClassUtils {
|
|||
public static Method getMethod(Class<?> clazz, String methodName, Class<?>... paramTypes) {
|
||||
Assert.notNull(clazz, "Class must not be null");
|
||||
Assert.notNull(methodName, "Method name must not be null");
|
||||
try {
|
||||
return clazz.getMethod(methodName, paramTypes);
|
||||
if (paramTypes != null) {
|
||||
try {
|
||||
return clazz.getMethod(methodName, paramTypes);
|
||||
}
|
||||
catch (NoSuchMethodException ex) {
|
||||
throw new IllegalStateException("Expected method not found: " + ex);
|
||||
}
|
||||
}
|
||||
catch (NoSuchMethodException ex) {
|
||||
throw new IllegalStateException("Expected method not found: " + ex);
|
||||
else {
|
||||
Set<Method> candidates = new HashSet<Method>(1);
|
||||
Method[] methods = clazz.getMethods();
|
||||
for (Method method : methods) {
|
||||
if (methodName.equals(method.getName())) {
|
||||
candidates.add(method);
|
||||
}
|
||||
}
|
||||
if (candidates.size() == 1) {
|
||||
return candidates.iterator().next();
|
||||
}
|
||||
else if (candidates.isEmpty()) {
|
||||
throw new IllegalStateException("Expected method not found: " + clazz + "." + methodName);
|
||||
}
|
||||
else {
|
||||
throw new IllegalStateException("No unique method found: " + clazz + "." + methodName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the given class has a method with the given signature,
|
||||
* Determine whether the given class has a public method with the given signature,
|
||||
* and return it if available (else return {@code null}).
|
||||
* <p>In case of any signature specified, only returns the method if there is a
|
||||
* unique candidate, i.e. a single public method with the specified name.
|
||||
* <p>Essentially translates {@code NoSuchMethodException} to {@code null}.
|
||||
* @param clazz the clazz to analyze
|
||||
* @param clazz the clazz to analyze
|
||||
* @param methodName the name of the method
|
||||
* @param paramTypes the parameter types of the method
|
||||
* (may be {@code null} to indicate any signature)
|
||||
* @return the method, or {@code null} if not found
|
||||
* @see Class#getMethod
|
||||
*/
|
||||
public static Method getMethodIfAvailable(Class<?> clazz, String methodName, Class<?>... paramTypes) {
|
||||
Assert.notNull(clazz, "Class must not be null");
|
||||
Assert.notNull(methodName, "Method name must not be null");
|
||||
try {
|
||||
return clazz.getMethod(methodName, paramTypes);
|
||||
if (paramTypes != null) {
|
||||
try {
|
||||
return clazz.getMethod(methodName, paramTypes);
|
||||
}
|
||||
catch (NoSuchMethodException ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
catch (NoSuchMethodException ex) {
|
||||
else {
|
||||
Set<Method> candidates = new HashSet<Method>(1);
|
||||
Method[] methods = clazz.getMethods();
|
||||
for (Method method : methods) {
|
||||
if (methodName.equals(method.getName())) {
|
||||
candidates.add(method);
|
||||
}
|
||||
}
|
||||
if (candidates.size() == 1) {
|
||||
return candidates.iterator().next();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -1025,7 +1063,7 @@ public abstract class ClassUtils {
|
|||
* @param instance the instance to analyze for interfaces
|
||||
* @return all interfaces that the given instance implements as array
|
||||
*/
|
||||
public static Class[] getAllInterfaces(Object instance) {
|
||||
public static Class<?>[] getAllInterfaces(Object instance) {
|
||||
Assert.notNull(instance, "Instance must not be null");
|
||||
return getAllInterfacesForClass(instance.getClass());
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue