diff --git a/spring-beans/src/main/java/org/springframework/beans/GenericTypeAwarePropertyDescriptor.java b/spring-beans/src/main/java/org/springframework/beans/GenericTypeAwarePropertyDescriptor.java index 0f0c593162..096bae5a61 100644 --- a/spring-beans/src/main/java/org/springframework/beans/GenericTypeAwarePropertyDescriptor.java +++ b/spring-beans/src/main/java/org/springframework/beans/GenericTypeAwarePropertyDescriptor.java @@ -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 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); diff --git a/spring-beans/src/test/java/org/springframework/tests/sample/beans/DerivedTestBean.java b/spring-beans/src/test/java/org/springframework/tests/sample/beans/DerivedTestBean.java index 91416208a2..d12db5b7ad 100644 --- a/spring-beans/src/test/java/org/springframework/tests/sample/beans/DerivedTestBean.java +++ b/spring-beans/src/test/java/org/springframework/tests/sample/beans/DerivedTestBean.java @@ -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; diff --git a/spring-beans/src/test/resources/org/springframework/beans/factory/xml/collections.xml b/spring-beans/src/test/resources/org/springframework/beans/factory/xml/collections.xml index 974ae6c3d9..73294dfbae 100644 --- a/spring-beans/src/test/resources/org/springframework/beans/factory/xml/collections.xml +++ b/spring-beans/src/test/resources/org/springframework/beans/factory/xml/collections.xml @@ -35,11 +35,10 @@ - + Jenny 30 - diff --git a/spring-core/src/main/java/org/springframework/util/ClassUtils.java b/spring-core/src/main/java/org/springframework/util/ClassUtils.java index 467b0f7cb6..ed84ba02a6 100644 --- a/spring-core/src/main/java/org/springframework/util/ClassUtils.java +++ b/spring-core/src/main/java/org/springframework/util/ClassUtils.java @@ -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. *

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}). *

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

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}). + *

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

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 candidates = new HashSet(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}). + *

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

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 candidates = new HashSet(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()); }