Skip to content

Commit 045f78e

Browse files
committed
Fixed detection of setter in case of getter with covariant return type narrowing
Issue: SPR-10995
1 parent 42568af commit 045f78e

File tree

4 files changed

+81
-36
lines changed

4 files changed

+81
-36
lines changed

spring-beans/src/main/java/org/springframework/beans/GenericTypeAwarePropertyDescriptor.java

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2012 the original author or authors.
2+
* Copyright 2002-2013 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -31,32 +31,32 @@
3131
import org.springframework.util.StringUtils;
3232

3333
/**
34-
* Extension of the standard JavaBeans PropertyDescriptor class,
35-
* overriding {@code getPropertyType()} such that a generically
36-
* declared type will be resolved against the containing bean class.
34+
* Extension of the standard JavaBeans {@link PropertyDescriptor} class,
35+
* overriding {@code getPropertyType()} such that a generically declared
36+
* type variable will be resolved against the containing bean class.
3737
*
3838
* @author Juergen Hoeller
3939
* @since 2.5.2
4040
*/
4141
class GenericTypeAwarePropertyDescriptor extends PropertyDescriptor {
4242

43-
private final Class beanClass;
43+
private final Class<?> beanClass;
4444

4545
private final Method readMethod;
4646

4747
private final Method writeMethod;
4848

49-
private final Class propertyEditorClass;
49+
private final Class<?> propertyEditorClass;
5050

5151
private volatile Set<Method> ambiguousWriteMethods;
5252

53-
private Class propertyType;
53+
private Class<?> propertyType;
5454

5555
private MethodParameter writeMethodParameter;
5656

5757

58-
public GenericTypeAwarePropertyDescriptor(Class beanClass, String propertyName,
59-
Method readMethod, Method writeMethod, Class propertyEditorClass)
58+
public GenericTypeAwarePropertyDescriptor(Class<?> beanClass, String propertyName,
59+
Method readMethod, Method writeMethod, Class<?> propertyEditorClass)
6060
throws IntrospectionException {
6161

6262
super(propertyName, null, null);
@@ -69,8 +69,11 @@ public GenericTypeAwarePropertyDescriptor(Class beanClass, String propertyName,
6969
// Fallback: Original JavaBeans introspection might not have found matching setter
7070
// method due to lack of bridge method resolution, in case of the getter using a
7171
// covariant return type whereas the setter is defined for the concrete property type.
72-
writeMethodToUse = ClassUtils.getMethodIfAvailable(this.beanClass,
73-
"set" + StringUtils.capitalize(getName()), readMethodToUse.getReturnType());
72+
Method candidate = ClassUtils.getMethodIfAvailable(
73+
this.beanClass, "set" + StringUtils.capitalize(getName()), (Class<?>[]) null);
74+
if (candidate != null && candidate.getParameterTypes().length == 1) {
75+
writeMethodToUse = candidate;
76+
}
7477
}
7578
this.readMethod = readMethodToUse;
7679
this.writeMethod = writeMethodToUse;
@@ -118,12 +121,12 @@ public Method getWriteMethodForActualAccess() {
118121
}
119122

120123
@Override
121-
public Class getPropertyEditorClass() {
124+
public Class<?> getPropertyEditorClass() {
122125
return this.propertyEditorClass;
123126
}
124127

125128
@Override
126-
public synchronized Class getPropertyType() {
129+
public synchronized Class<?> getPropertyType() {
127130
if (this.propertyType == null) {
128131
if (this.readMethod != null) {
129132
this.propertyType = GenericTypeResolver.resolveReturnType(this.readMethod, this.beanClass);

spring-beans/src/test/java/org/springframework/tests/sample/beans/DerivedTestBean.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2012 the original author or authors.
2+
* Copyright 2002-2013 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -71,6 +71,11 @@ public void setSpouseRef(String name) {
7171
setSpouse(new TestBean(name));
7272
}
7373

74+
@Override
75+
public TestBean getSpouse() {
76+
return (TestBean) super.getSpouse();
77+
}
78+
7479

7580
public void initialize() {
7681
this.initialized = true;

spring-beans/src/test/resources/org/springframework/beans/factory/xml/collections.xml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,10 @@
3535
</property>
3636
</bean>
3737

38-
<bean id="pJenny" class="org.springframework.tests.sample.beans.TestBean" scope="prototype">
38+
<bean id="pJenny" class="org.springframework.tests.sample.beans.DerivedTestBean" scope="prototype">
3939
<property name="name"><value>Jenny</value></property>
4040
<property name="age"><value>30</value></property>
4141
<property name="spouse">
42-
<!-- Could use id and href -->
4342
<ref local="david"/>
4443
</property>
4544
</bean>

spring-core/src/main/java/org/springframework/util/ClassUtils.java

Lines changed: 58 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2012 the original author or authors.
2+
* Copyright 2002-2013 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,15 +17,12 @@
1717
package org.springframework.util;
1818

1919
import java.beans.Introspector;
20-
2120
import java.lang.reflect.Array;
2221
import java.lang.reflect.Constructor;
2322
import java.lang.reflect.Method;
2423
import java.lang.reflect.Modifier;
2524
import java.lang.reflect.Proxy;
26-
2725
import java.security.AccessControlException;
28-
2926
import java.util.Arrays;
3027
import java.util.Collection;
3128
import java.util.Collections;
@@ -578,7 +575,7 @@ public static boolean matchesTypeName(Class<?> clazz, String typeName) {
578575
/**
579576
* Determine whether the given class has a public constructor with the given signature.
580577
* <p>Essentially translates {@code NoSuchMethodException} to "false".
581-
* @param clazz the clazz to analyze
578+
* @param clazz the clazz to analyze
582579
* @param paramTypes the parameter types of the method
583580
* @return whether the class has a corresponding constructor
584581
* @see Class#getMethod
@@ -591,7 +588,7 @@ public static boolean hasConstructor(Class<?> clazz, Class<?>... paramTypes) {
591588
* Determine whether the given class has a public constructor with the given signature,
592589
* and return it if available (else return {@code null}).
593590
* <p>Essentially translates {@code NoSuchMethodException} to {@code null}.
594-
* @param clazz the clazz to analyze
591+
* @param clazz the clazz to analyze
595592
* @param paramTypes the parameter types of the method
596593
* @return the constructor, or {@code null} if not found
597594
* @see Class#getConstructor
@@ -607,9 +604,9 @@ public static <T> Constructor<T> getConstructorIfAvailable(Class<T> clazz, Class
607604
}
608605

609606
/**
610-
* Determine whether the given class has a method with the given signature.
607+
* Determine whether the given class has a public method with the given signature.
611608
* <p>Essentially translates {@code NoSuchMethodException} to "false".
612-
* @param clazz the clazz to analyze
609+
* @param clazz the clazz to analyze
613610
* @param methodName the name of the method
614611
* @param paramTypes the parameter types of the method
615612
* @return whether the class has a corresponding method
@@ -620,44 +617,85 @@ public static boolean hasMethod(Class<?> clazz, String methodName, Class<?>... p
620617
}
621618

622619
/**
623-
* Determine whether the given class has a method with the given signature,
620+
* Determine whether the given class has a public method with the given signature,
624621
* and return it if available (else throws an {@code IllegalStateException}).
622+
* <p>In case of any signature specified, only returns the method if there is a
623+
* unique candidate, i.e. a single public method with the specified name.
625624
* <p>Essentially translates {@code NoSuchMethodException} to {@code IllegalStateException}.
626-
* @param clazz the clazz to analyze
625+
* @param clazz the clazz to analyze
627626
* @param methodName the name of the method
628627
* @param paramTypes the parameter types of the method
628+
* (may be {@code null} to indicate any signature)
629629
* @return the method (never {@code null})
630630
* @throws IllegalStateException if the method has not been found
631631
* @see Class#getMethod
632632
*/
633633
public static Method getMethod(Class<?> clazz, String methodName, Class<?>... paramTypes) {
634634
Assert.notNull(clazz, "Class must not be null");
635635
Assert.notNull(methodName, "Method name must not be null");
636-
try {
637-
return clazz.getMethod(methodName, paramTypes);
636+
if (paramTypes != null) {
637+
try {
638+
return clazz.getMethod(methodName, paramTypes);
639+
}
640+
catch (NoSuchMethodException ex) {
641+
throw new IllegalStateException("Expected method not found: " + ex);
642+
}
638643
}
639-
catch (NoSuchMethodException ex) {
640-
throw new IllegalStateException("Expected method not found: " + ex);
644+
else {
645+
Set<Method> candidates = new HashSet<Method>(1);
646+
Method[] methods = clazz.getMethods();
647+
for (Method method : methods) {
648+
if (methodName.equals(method.getName())) {
649+
candidates.add(method);
650+
}
651+
}
652+
if (candidates.size() == 1) {
653+
return candidates.iterator().next();
654+
}
655+
else if (candidates.isEmpty()) {
656+
throw new IllegalStateException("Expected method not found: " + clazz + "." + methodName);
657+
}
658+
else {
659+
throw new IllegalStateException("No unique method found: " + clazz + "." + methodName);
660+
}
641661
}
642662
}
643663

644664
/**
645-
* Determine whether the given class has a method with the given signature,
665+
* Determine whether the given class has a public method with the given signature,
646666
* and return it if available (else return {@code null}).
667+
* <p>In case of any signature specified, only returns the method if there is a
668+
* unique candidate, i.e. a single public method with the specified name.
647669
* <p>Essentially translates {@code NoSuchMethodException} to {@code null}.
648-
* @param clazz the clazz to analyze
670+
* @param clazz the clazz to analyze
649671
* @param methodName the name of the method
650672
* @param paramTypes the parameter types of the method
673+
* (may be {@code null} to indicate any signature)
651674
* @return the method, or {@code null} if not found
652675
* @see Class#getMethod
653676
*/
654677
public static Method getMethodIfAvailable(Class<?> clazz, String methodName, Class<?>... paramTypes) {
655678
Assert.notNull(clazz, "Class must not be null");
656679
Assert.notNull(methodName, "Method name must not be null");
657-
try {
658-
return clazz.getMethod(methodName, paramTypes);
680+
if (paramTypes != null) {
681+
try {
682+
return clazz.getMethod(methodName, paramTypes);
683+
}
684+
catch (NoSuchMethodException ex) {
685+
return null;
686+
}
659687
}
660-
catch (NoSuchMethodException ex) {
688+
else {
689+
Set<Method> candidates = new HashSet<Method>(1);
690+
Method[] methods = clazz.getMethods();
691+
for (Method method : methods) {
692+
if (methodName.equals(method.getName())) {
693+
candidates.add(method);
694+
}
695+
}
696+
if (candidates.size() == 1) {
697+
return candidates.iterator().next();
698+
}
661699
return null;
662700
}
663701
}
@@ -1025,7 +1063,7 @@ public static Class<?>[] toClassArray(Collection<Class<?>> collection) {
10251063
* @param instance the instance to analyze for interfaces
10261064
* @return all interfaces that the given instance implements as array
10271065
*/
1028-
public static Class[] getAllInterfaces(Object instance) {
1066+
public static Class<?>[] getAllInterfaces(Object instance) {
10291067
Assert.notNull(instance, "Instance must not be null");
10301068
return getAllInterfacesForClass(instance.getClass());
10311069
}

0 commit comments

Comments
 (0)