diff --git a/spring-beans/src/main/java/org/springframework/beans/ExtendedBeanInfo.java b/spring-beans/src/main/java/org/springframework/beans/ExtendedBeanInfo.java
index 1e2b3c398b..c383a52b5b 100644
--- a/spring-beans/src/main/java/org/springframework/beans/ExtendedBeanInfo.java
+++ b/spring-beans/src/main/java/org/springframework/beans/ExtendedBeanInfo.java
@@ -16,9 +16,8 @@
package org.springframework.beans;
-import static java.lang.String.format;
-
import java.awt.Image;
+
import java.beans.BeanDescriptor;
import java.beans.BeanInfo;
import java.beans.EventSetDescriptor;
@@ -27,310 +26,191 @@ import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.MethodDescriptor;
import java.beans.PropertyDescriptor;
+
import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+
+import java.util.ArrayList;
import java.util.Comparator;
-import java.util.SortedSet;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Set;
import java.util.TreeSet;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.springframework.core.BridgeMethodResolver;
-import org.springframework.util.Assert;
-import org.springframework.util.ClassUtils;
-import org.springframework.util.ReflectionUtils;
-import org.springframework.util.StringUtils;
+import static org.springframework.beans.PropertyDescriptorUtils.*;
/**
- * Decorates a standard {@link BeanInfo} object (likely created created by
- * {@link Introspector#getBeanInfo(Class)}) by including non-void returning setter
- * methods in the collection of {@link #getPropertyDescriptors() property descriptors}.
- * Both regular and
+ * Decorator for a standard {@link BeanInfo} object, e.g. as created by
+ * {@link Introspector#getBeanInfo(Class)}, designed to discover and register non-void
+ * returning setter methods. For example:
+ *
} XML. {@link #getPropertyDescriptors()} returns all
+ * existing property descriptors from the wrapped {@code BeanInfo} as well any added for
+ * non-void returning setters. Both standard ("non-indexed") and
*
* indexed properties are fully supported.
*
- * The wrapped {@code BeanInfo} object is not modified in any way.
- *
* @author Chris Beams
* @since 3.1
+ * @see #ExtendedBeanInfo(BeanInfo)
+ * @see ExtendedBeanInfoFactory
* @see CachedIntrospectionResults
*/
class ExtendedBeanInfo implements BeanInfo {
- private final Log logger = LogFactory.getLog(getClass());
-
private final BeanInfo delegate;
- private final SortedSet propertyDescriptors =
- new TreeSet(new PropertyDescriptorComparator());
+ private final Set propertyDescriptors =
+ new TreeSet(new PropertyDescriptorComparator());
/**
- * Wrap the given delegate {@link BeanInfo} instance and find any non-void returning
- * setter methods, creating and adding a {@link PropertyDescriptor} for each.
- *
- * Note that the wrapped {@code BeanInfo} is modified by this process.
- *
+ * Wrap the given {@link BeanInfo} instance; copy all its existing property descriptors
+ * locally, wrapping each in a custom {@link SimpleIndexedPropertyDescriptor indexed} or
+ * {@link SimpleNonIndexedPropertyDescriptor non-indexed} {@code PropertyDescriptor}
+ * variant that bypasses default JDK weak/soft reference management; then search
+ * through its method descriptors to find any non-void returning write methods and
+ * update or create the corresponding {@link PropertyDescriptor} for each one found.
+ * @param delegate the wrapped {@code BeanInfo}, which is never modified
+ * @throws IntrospectionException if any problems occur creating and adding new
+ * property descriptors
* @see #getPropertyDescriptors()
- * @throws IntrospectionException if any problems occur creating and adding new {@code PropertyDescriptors}
*/
public ExtendedBeanInfo(BeanInfo delegate) throws IntrospectionException {
this.delegate = delegate;
- ALL_METHODS:
- for (MethodDescriptor md : delegate.getMethodDescriptors()) {
- Method method = resolveMethod(md.getMethod());
+ for (PropertyDescriptor pd : delegate.getPropertyDescriptors()) {
+ this.propertyDescriptors.add(pd instanceof IndexedPropertyDescriptor ?
+ new SimpleIndexedPropertyDescriptor((IndexedPropertyDescriptor) pd) :
+ new SimpleNonIndexedPropertyDescriptor(pd));
+ }
- // bypass non-getter java.lang.Class methods for efficiency
- if (ReflectionUtils.isObjectMethod(method) && !method.getName().startsWith("get")) {
- continue ALL_METHODS;
- }
-
- // is the method a NON-INDEXED setter? ignore return type in order to capture non-void signatures
- if (method.getName().startsWith("set") && method.getParameterTypes().length == 1) {
- String propertyName = propertyNameFor(method);
- if(propertyName.length() == 0) {
- continue ALL_METHODS;
- }
- for (PropertyDescriptor pd : delegate.getPropertyDescriptors()) {
- Method readMethod = readMethodFor(pd);
- Method writeMethod = writeMethodFor(pd);
- // has the setter already been found by the wrapped BeanInfo?
- if (writeMethod != null
- && writeMethod.getName().equals(method.getName())) {
- // yes -> copy it, including corresponding getter method (if any -- may be null)
- this.addOrUpdatePropertyDescriptor(pd, propertyName, readMethod, writeMethod);
- continue ALL_METHODS;
- }
- // has a getter corresponding to this setter already been found by the wrapped BeanInfo?
- if (readMethod != null
- && readMethod.getName().equals(getterMethodNameFor(propertyName))
- && readMethod.getReturnType().equals(method.getParameterTypes()[0])) {
- this.addOrUpdatePropertyDescriptor(pd, propertyName, readMethod, method);
- continue ALL_METHODS;
- }
- }
- // the setter method was not found by the wrapped BeanInfo -> add a new PropertyDescriptor for it
- // no corresponding getter was detected, so the 'read method' parameter is null.
- this.addOrUpdatePropertyDescriptor(null, propertyName, null, method);
- continue ALL_METHODS;
- }
-
- // is the method an INDEXED setter? ignore return type in order to capture non-void signatures
- if (method.getName().startsWith("set") && method.getParameterTypes().length == 2 && method.getParameterTypes()[0].equals(int.class)) {
- String propertyName = propertyNameFor(method);
- if(propertyName.length() == 0) {
- continue ALL_METHODS;
- }
- DELEGATE_PD:
- for (PropertyDescriptor pd : delegate.getPropertyDescriptors()) {
- if (!(pd instanceof IndexedPropertyDescriptor)) {
- continue DELEGATE_PD;
- }
- IndexedPropertyDescriptor ipd = (IndexedPropertyDescriptor) pd;
- Method readMethod = readMethodFor(ipd);
- Method writeMethod = writeMethodFor(ipd);
- Method indexedReadMethod = indexedReadMethodFor(ipd);
- Method indexedWriteMethod = indexedWriteMethodFor(ipd);
- // has the setter already been found by the wrapped BeanInfo?
- if (!(indexedWriteMethod != null
- && indexedWriteMethod.getName().equals(method.getName()))) {
- indexedWriteMethod = method;
- }
- // yes -> copy it, including corresponding getter method (if any -- may be null)
- this.addOrUpdatePropertyDescriptor(pd, propertyName, readMethod, writeMethod, indexedReadMethod, indexedWriteMethod);
- continue ALL_METHODS;
- }
- // the INDEXED setter method was not found by the wrapped BeanInfo -> add a new PropertyDescriptor
- // for it. no corresponding INDEXED getter was detected, so the 'indexed read method' parameter is null.
- this.addOrUpdatePropertyDescriptor(null, propertyName, null, null, null, method);
- continue ALL_METHODS;
- }
-
- // the method is not a setter, but is it a getter?
- for (PropertyDescriptor pd : delegate.getPropertyDescriptors()) {
- // have we already copied this read method to a property descriptor locally?
- String propertyName = pd.getName();
- Method readMethod = readMethodFor(pd);
- Method mostSpecificReadMethod = ClassUtils.getMostSpecificMethod(readMethod, method.getDeclaringClass());
- for (PropertyDescriptor existingPD : this.propertyDescriptors) {
- if (method.equals(mostSpecificReadMethod)
- && existingPD.getName().equals(propertyName)) {
- if (readMethodFor(existingPD) == null) {
- // no -> add it now
- this.addOrUpdatePropertyDescriptor(pd, propertyName, method, writeMethodFor(pd));
- }
- // yes -> do not add a duplicate
- continue ALL_METHODS;
- }
- }
- if (method.equals(mostSpecificReadMethod)
- || (pd instanceof IndexedPropertyDescriptor && method.equals(indexedReadMethodFor((IndexedPropertyDescriptor) pd)))) {
- // yes -> copy it, including corresponding setter method (if any -- may be null)
- if (pd instanceof IndexedPropertyDescriptor) {
- this.addOrUpdatePropertyDescriptor(pd, propertyName, readMethod, writeMethodFor(pd), indexedReadMethodFor((IndexedPropertyDescriptor)pd), indexedWriteMethodFor((IndexedPropertyDescriptor)pd));
- } else {
- this.addOrUpdatePropertyDescriptor(pd, propertyName, readMethod, writeMethodFor(pd));
- }
- continue ALL_METHODS;
- }
- }
+ for (Method method : findNonVoidWriteMethods(delegate.getMethodDescriptors())) {
+ handleNonVoidWriteMethod(method);
}
}
- private static Method resolveMethod(Method method) {
- return BridgeMethodResolver.findBridgedMethod(method);
- }
-
- private static Method readMethodFor(PropertyDescriptor pd) {
- return resolveMethod(pd.getReadMethod());
- }
-
- private static Method writeMethodFor(PropertyDescriptor pd) {
- return resolveMethod(pd.getWriteMethod());
- }
-
- private static Method indexedReadMethodFor(IndexedPropertyDescriptor ipd) {
- return resolveMethod(ipd.getIndexedReadMethod());
- }
-
- private static Method indexedWriteMethodFor(IndexedPropertyDescriptor ipd) {
- return resolveMethod(ipd.getIndexedWriteMethod());
- }
-
- private void addOrUpdatePropertyDescriptor(PropertyDescriptor pd, String propertyName, Method readMethod, Method writeMethod) throws IntrospectionException {
- addOrUpdatePropertyDescriptor(pd, propertyName, readMethod, writeMethod, null, null);
- }
-
- private void addOrUpdatePropertyDescriptor(PropertyDescriptor pd, String propertyName, Method readMethod, Method writeMethod, Method indexedReadMethod, Method indexedWriteMethod) throws IntrospectionException {
- Assert.notNull(propertyName, "propertyName may not be null");
- propertyName = pd == null ? propertyName : pd.getName();
- for (PropertyDescriptor existingPD : this.propertyDescriptors) {
- if (existingPD.getName().equals(propertyName)) {
- // is there already a descriptor that captures this read method or its corresponding write method?
- if (readMethodFor(existingPD) != null) {
- if (readMethod != null && readMethodFor(existingPD).getReturnType() != readMethod.getReturnType()
- || writeMethod != null && readMethodFor(existingPD).getReturnType() != writeMethod.getParameterTypes()[0]) {
- // no -> add a new descriptor for it below
- break;
- }
- }
- // update the existing descriptor's read method
- if (readMethod != null) {
- try {
- existingPD.setReadMethod(readMethod);
- } catch (IntrospectionException ex) {
- // there is a conflicting setter method present -> null it out and try again
- existingPD.setWriteMethod(null);
- existingPD.setReadMethod(readMethod);
- }
- }
-
- // is there already a descriptor that captures this write method or its corresponding read method?
- if (writeMethodFor(existingPD) != null) {
- if (readMethod != null && writeMethodFor(existingPD).getParameterTypes()[0] != readMethod.getReturnType()
- || writeMethod != null && writeMethodFor(existingPD).getParameterTypes()[0] != writeMethod.getParameterTypes()[0]) {
- // no -> add a new descriptor for it below
- break;
- }
- }
- // update the existing descriptor's write method
- if (writeMethod != null
- && !(existingPD instanceof IndexedPropertyDescriptor &&
- !writeMethod.getParameterTypes()[0].isArray())) {
- existingPD.setWriteMethod(writeMethod);
- }
-
- // is this descriptor indexed?
- if (existingPD instanceof IndexedPropertyDescriptor) {
- IndexedPropertyDescriptor existingIPD = (IndexedPropertyDescriptor) existingPD;
-
- // is there already a descriptor that captures this indexed read method or its corresponding indexed write method?
- if (indexedReadMethodFor(existingIPD) != null) {
- if (indexedReadMethod != null && indexedReadMethodFor(existingIPD).getReturnType() != indexedReadMethod.getReturnType()
- || indexedWriteMethod != null && indexedReadMethodFor(existingIPD).getReturnType() != indexedWriteMethod.getParameterTypes()[1]) {
- // no -> add a new descriptor for it below
- break;
- }
- }
- // update the existing descriptor's indexed read method
- try {
- if (indexedReadMethod != null) {
- existingIPD.setIndexedReadMethod(indexedReadMethod);
- }
- } catch (IntrospectionException ex) {
- // there is a conflicting indexed setter method present -> null it out and try again
- existingIPD.setIndexedWriteMethod(null);
- existingIPD.setIndexedReadMethod(indexedReadMethod);
- }
-
- // is there already a descriptor that captures this indexed write method or its corresponding indexed read method?
- if (indexedWriteMethodFor(existingIPD) != null) {
- if (indexedReadMethod != null && indexedWriteMethodFor(existingIPD).getParameterTypes()[1] != indexedReadMethod.getReturnType()
- || indexedWriteMethod != null && indexedWriteMethodFor(existingIPD).getParameterTypes()[1] != indexedWriteMethod.getParameterTypes()[1]) {
- // no -> add a new descriptor for it below
- break;
- }
- }
- // update the existing descriptor's indexed write method
- if (indexedWriteMethod != null) {
- existingIPD.setIndexedWriteMethod(indexedWriteMethod);
- }
- }
-
- // the descriptor has been updated -> return immediately
- return;
+ private List findNonVoidWriteMethods(MethodDescriptor[] methodDescriptors) {
+ List matches = new ArrayList();
+ for (MethodDescriptor methodDescriptor : methodDescriptors) {
+ Method method = methodDescriptor.getMethod();
+ if (isNonVoidWriteMethod(method)) {
+ matches.add(method);
}
}
+ return matches;
+ }
- // we haven't yet seen read or write methods for this property -> add a new descriptor
- if (pd == null) {
- try {
- if (indexedReadMethod == null && indexedWriteMethod == null) {
- pd = new PropertyDescriptor(propertyName, readMethod, writeMethod);
- }
- else {
- pd = new IndexedPropertyDescriptor(propertyName, readMethod, writeMethod, indexedReadMethod, indexedWriteMethod);
- }
- this.propertyDescriptors.add(pd);
- } catch (IntrospectionException ex) {
- logger.debug(format("Could not create new PropertyDescriptor for readMethod [%s] writeMethod [%s] " +
- "indexedReadMethod [%s] indexedWriteMethod [%s] for property [%s]. Reason: %s",
- readMethod, writeMethod, indexedReadMethod, indexedWriteMethod, propertyName, ex.getMessage()));
- // suppress exception and attempt to continue
+ public static boolean isNonVoidWriteMethod(Method method) {
+ String methodName = method.getName();
+ Class>[] parameterTypes = method.getParameterTypes();
+ int nParams = parameterTypes.length;
+ if (methodName.length() > 3 && methodName.startsWith("set") &&
+ Modifier.isPublic(method.getModifiers()) &&
+ !void.class.isAssignableFrom(method.getReturnType()) &&
+ (nParams == 1 || (nParams == 2 && parameterTypes[0].equals(int.class)))) {
+ return true;
+ }
+ return false;
+ }
+
+ private void handleNonVoidWriteMethod(Method method) throws IntrospectionException {
+ int nParams = method.getParameterTypes().length;
+ String propertyName = propertyNameFor(method);
+ Class> propertyType = method.getParameterTypes()[nParams-1];
+ PropertyDescriptor existingPD = findExistingPropertyDescriptor(propertyName, propertyType);
+ if (nParams == 1) {
+ if (existingPD == null) {
+ this.propertyDescriptors.add(
+ new SimpleNonIndexedPropertyDescriptor(propertyName, null, method));
+ }
+ else {
+ existingPD.setWriteMethod(method);
+ }
+ }
+ else if (nParams == 2) {
+ if (existingPD == null) {
+ this.propertyDescriptors.add(
+ new SimpleIndexedPropertyDescriptor(
+ propertyName, null, null, null, method));
+ }
+ else if (existingPD instanceof IndexedPropertyDescriptor) {
+ ((IndexedPropertyDescriptor)existingPD).setIndexedWriteMethod(method);
+ }
+ else {
+ this.propertyDescriptors.remove(existingPD);
+ this.propertyDescriptors.add(
+ new SimpleIndexedPropertyDescriptor(
+ propertyName, existingPD.getReadMethod(),
+ existingPD.getWriteMethod(), null, method));
}
}
else {
- pd.setWriteMethod(null);
- pd.setReadMethod(readMethod);
- try {
- pd.setWriteMethod(writeMethod);
- } catch (IntrospectionException ex) {
- logger.debug(format("Could not add write method [%s] for property [%s]. Reason: %s",
- writeMethod, propertyName, ex.getMessage()));
- // fall through -> add property descriptor as best we can
- }
- if (pd instanceof IndexedPropertyDescriptor) {
- ((IndexedPropertyDescriptor)pd).setIndexedWriteMethod(null);
- ((IndexedPropertyDescriptor)pd).setIndexedReadMethod(indexedReadMethod);
- try {
- ((IndexedPropertyDescriptor)pd).setIndexedWriteMethod(indexedWriteMethod);
- } catch (IntrospectionException ex) {
- logger.debug(format("Could not add indexed write method [%s] for property [%s]. Reason: %s",
- indexedWriteMethod, propertyName, ex.getMessage()));
- // fall through -> add property descriptor as best we can
- }
- }
- this.propertyDescriptors.add(pd);
+ throw new IllegalArgumentException(
+ "write method must have exactly 1 or 2 parameters: " + method);
}
}
- private String propertyNameFor(Method method) {
- return Introspector.decapitalize(method.getName().substring(3,method.getName().length()));
+ private PropertyDescriptor findExistingPropertyDescriptor(
+ String propertyName, Class> propertyType) {
+
+ for (PropertyDescriptor pd : this.propertyDescriptors) {
+ final Class> candidateType;
+ final String candidateName = pd.getName();
+ if (pd instanceof IndexedPropertyDescriptor) {
+ IndexedPropertyDescriptor ipd = (IndexedPropertyDescriptor) pd;
+ candidateType = ipd.getIndexedPropertyType();
+ if (candidateName.equals(propertyName) &&
+ (candidateType.equals(propertyType) ||
+ candidateType.equals(propertyType.getComponentType()))) {
+ return pd;
+ }
+ }
+ else {
+ candidateType = pd.getPropertyType();
+ if (candidateName.equals(propertyName) &&
+ (candidateType.equals(propertyType) ||
+ propertyType.equals(candidateType.getComponentType()))) {
+ return pd;
+ }
+ }
+ }
+ return null;
}
- private Object getterMethodNameFor(String name) {
- return "get" + StringUtils.capitalize(name);
+ private String propertyNameFor(Method method) {
+ return Introspector.decapitalize(
+ method.getName().substring(3, method.getName().length()));
+ }
+
+
+ /**
+ * Return the set of {@link PropertyDescriptor}s from the wrapped {@link BeanInfo}
+ * object as well as {@code PropertyDescriptor}s for each non-void returning setter
+ * method found during construction.
+ * @see #ExtendedBeanInfo(BeanInfo)
+ */
+ public PropertyDescriptor[] getPropertyDescriptors() {
+ return this.propertyDescriptors.toArray(
+ new PropertyDescriptor[this.propertyDescriptors.size()]);
}
public BeanInfo[] getAdditionalBeanInfo() {
@@ -353,45 +233,431 @@ class ExtendedBeanInfo implements BeanInfo {
return delegate.getEventSetDescriptors();
}
- public Image getIcon(int arg0) {
- return delegate.getIcon(arg0);
+ public Image getIcon(int iconKind) {
+ return delegate.getIcon(iconKind);
}
public MethodDescriptor[] getMethodDescriptors() {
return delegate.getMethodDescriptors();
}
+}
- /**
- * Return the set of {@link PropertyDescriptor}s from the wrapped {@link BeanInfo}
- * object as well as {@code PropertyDescriptor}s for each non-void returning setter
- * method found during construction.
- * @see #ExtendedBeanInfo(BeanInfo)
- */
- public PropertyDescriptor[] getPropertyDescriptors() {
- return this.propertyDescriptors.toArray(new PropertyDescriptor[this.propertyDescriptors.size()]);
+
+class SimpleNonIndexedPropertyDescriptor extends PropertyDescriptor {
+
+ private Method readMethod;
+ private Method writeMethod;
+ private Class> propertyType;
+ private Class> propertyEditorClass;
+
+
+ public SimpleNonIndexedPropertyDescriptor(PropertyDescriptor original)
+ throws IntrospectionException {
+
+ this(original.getName(), original.getReadMethod(), original.getWriteMethod());
+ copyNonMethodProperties(original, this);
+ }
+
+ public SimpleNonIndexedPropertyDescriptor(String propertyName,
+ Method readMethod, Method writeMethod) throws IntrospectionException {
+
+ super(propertyName, readMethod, writeMethod);
+ this.setReadMethod(readMethod);
+ this.setWriteMethod(writeMethod);
+ this.propertyType = findPropertyType(readMethod, writeMethod);
}
- /**
- * Sorts PropertyDescriptor instances alpha-numerically to emulate the behavior of
- * {@link java.beans.BeanInfo#getPropertyDescriptors()}.
- *
- * @see ExtendedBeanInfo#propertyDescriptors
- */
- static class PropertyDescriptorComparator implements Comparator {
- public int compare(PropertyDescriptor desc1, PropertyDescriptor desc2) {
- String left = desc1.getName();
- String right = desc2.getName();
- for (int i = 0; i < left.length(); i++) {
- if (right.length() == i) {
- return 1;
- }
- int result = left.getBytes()[i] - right.getBytes()[i];
- if (result != 0) {
- return result;
- }
+ @Override
+ public Method getReadMethod() {
+ return this.readMethod;
+ }
+
+ @Override
+ public void setReadMethod(Method readMethod) {
+ this.readMethod = readMethod;
+ }
+
+ @Override
+ public Method getWriteMethod() {
+ return this.writeMethod;
+ }
+
+ @Override
+ public void setWriteMethod(Method writeMethod) {
+ this.writeMethod = writeMethod;
+ }
+
+ @Override
+ public Class> getPropertyType() {
+ if (this.propertyType == null) {
+ try {
+ this.propertyType = findPropertyType(this.readMethod, this.writeMethod);
+ } catch (IntrospectionException ex) {
+ // ignore, as does PropertyDescriptor#getPropertyType
}
- return left.length() - right.length();
}
+ return this.propertyType;
}
-}
\ No newline at end of file
+
+ @Override
+ public Class> getPropertyEditorClass() {
+ return this.propertyEditorClass;
+ }
+
+ @Override
+ public void setPropertyEditorClass(Class> propertyEditorClass) {
+ this.propertyEditorClass = propertyEditorClass;
+ }
+
+
+ @Override
+ public boolean equals(Object obj) {
+ return PropertyDescriptorUtils.equals(this, obj);
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s[name=%s, propertyType=%s, readMethod=%s, writeMethod=%s]",
+ this.getClass().getSimpleName(), this.getName(), this.getPropertyType(),
+ this.readMethod, this.writeMethod);
+ }
+}
+
+
+class SimpleIndexedPropertyDescriptor extends IndexedPropertyDescriptor {
+
+ private Method readMethod;
+ private Method writeMethod;
+ private Class> propertyType;
+ private Class> propertyEditorClass;
+
+ private Method indexedReadMethod;
+ private Method indexedWriteMethod;
+ private Class> indexedPropertyType;
+
+
+ public SimpleIndexedPropertyDescriptor(IndexedPropertyDescriptor original)
+ throws IntrospectionException {
+
+ this(original.getName(), original.getReadMethod(), original.getWriteMethod(),
+ original.getIndexedReadMethod(), original.getIndexedWriteMethod());
+ copyNonMethodProperties(original, this);
+ }
+
+ public SimpleIndexedPropertyDescriptor(String propertyName,
+ Method readMethod, Method writeMethod,
+ Method indexedReadMethod, Method indexedWriteMethod)
+ throws IntrospectionException {
+
+ super(propertyName, readMethod, writeMethod, indexedReadMethod, indexedWriteMethod);
+ this.setReadMethod(readMethod);
+ this.setWriteMethod(writeMethod);
+ this.propertyType = findPropertyType(readMethod, writeMethod);
+
+ this.setIndexedReadMethod(indexedReadMethod);
+ this.setIndexedWriteMethod(indexedWriteMethod);
+ this.indexedPropertyType = findIndexedPropertyType(
+ this.getName(), this.propertyType, indexedReadMethod, indexedWriteMethod);
+ }
+
+
+ @Override
+ public Method getReadMethod() {
+ return this.readMethod;
+ }
+
+ @Override
+ public void setReadMethod(Method readMethod) {
+ this.readMethod = readMethod;
+ }
+
+ @Override
+ public Method getWriteMethod() {
+ return this.writeMethod;
+ }
+
+ @Override
+ public void setWriteMethod(Method writeMethod) {
+ this.writeMethod = writeMethod;
+ }
+
+ @Override
+ public Class> getPropertyType() {
+ if (this.propertyType == null) {
+ try {
+ this.propertyType = findPropertyType(this.readMethod, this.writeMethod);
+ } catch (IntrospectionException ex) {
+ // ignore, as does IndexedPropertyDescriptor#getPropertyType
+ }
+ }
+ return this.propertyType;
+ }
+
+ @Override
+ public Method getIndexedReadMethod() {
+ return this.indexedReadMethod;
+ }
+
+ @Override
+ public void setIndexedReadMethod(Method indexedReadMethod) throws IntrospectionException {
+ this.indexedReadMethod = indexedReadMethod;
+ }
+
+ @Override
+ public Method getIndexedWriteMethod() {
+ return this.indexedWriteMethod;
+ }
+
+ @Override
+ public void setIndexedWriteMethod(Method indexedWriteMethod) throws IntrospectionException {
+ this.indexedWriteMethod = indexedWriteMethod;
+ }
+
+ @Override
+ public Class> getIndexedPropertyType() {
+ if (this.indexedPropertyType == null) {
+ try {
+ this.indexedPropertyType = findIndexedPropertyType(
+ this.getName(), this.getPropertyType(),
+ this.indexedReadMethod, this.indexedWriteMethod);
+ } catch (IntrospectionException ex) {
+ // ignore, as does IndexedPropertyDescriptor#getIndexedPropertyType
+ }
+ }
+ return this.indexedPropertyType;
+ }
+
+ @Override
+ public Class> getPropertyEditorClass() {
+ return this.propertyEditorClass;
+ }
+
+ @Override
+ public void setPropertyEditorClass(Class> propertyEditorClass) {
+ this.propertyEditorClass = propertyEditorClass;
+ }
+
+
+ /*
+ * @see java.beans.IndexedPropertyDescriptor#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+
+ if (obj != null && obj instanceof IndexedPropertyDescriptor) {
+ IndexedPropertyDescriptor other = (IndexedPropertyDescriptor) obj;
+ if (!compareMethods(getIndexedReadMethod(), other.getIndexedReadMethod())) {
+ return false;
+ }
+
+ if (!compareMethods(getIndexedWriteMethod(), other.getIndexedWriteMethod())) {
+ return false;
+ }
+
+ if (getIndexedPropertyType() != other.getIndexedPropertyType()) {
+ return false;
+ }
+ return PropertyDescriptorUtils.equals(this, obj);
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s[name=%s, propertyType=%s, indexedPropertyType=%s, " +
+ "readMethod=%s, writeMethod=%s, indexedReadMethod=%s, indexedWriteMethod=%s]",
+ this.getClass().getSimpleName(), this.getName(), this.getPropertyType(),
+ this.getIndexedPropertyType(), this.readMethod, this.writeMethod,
+ this.indexedReadMethod, this.indexedWriteMethod);
+ }
+}
+
+
+class PropertyDescriptorUtils {
+
+ /*
+ * see java.beans.FeatureDescriptor#FeatureDescriptor(FeatureDescriptor)
+ */
+ public static void copyNonMethodProperties(PropertyDescriptor source, PropertyDescriptor target)
+ throws IntrospectionException {
+
+ target.setExpert(source.isExpert());
+ target.setHidden(source.isHidden());
+ target.setPreferred(source.isPreferred());
+ target.setName(source.getName());
+ target.setShortDescription(source.getShortDescription());
+ target.setDisplayName(source.getDisplayName());
+
+ // copy all attributes (emulating behavior of private FeatureDescriptor#addTable)
+ Enumeration keys = source.attributeNames();
+ while (keys.hasMoreElements()) {
+ String key = (String)keys.nextElement();
+ target.setValue(key, source.getValue(key));
+ }
+
+ // see java.beans.PropertyDescriptor#PropertyDescriptor(PropertyDescriptor)
+ target.setPropertyEditorClass(source.getPropertyEditorClass());
+ target.setBound(source.isBound());
+ target.setConstrained(source.isConstrained());
+ }
+
+ /*
+ * See PropertyDescriptor#findPropertyType
+ */
+ public static Class> findPropertyType(Method readMethod, Method writeMethod)
+ throws IntrospectionException {
+
+ Class> propertyType = null;
+
+ if (readMethod != null) {
+ Class>[] params = readMethod.getParameterTypes();
+ if (params.length != 0) {
+ throw new IntrospectionException("bad read method arg count: " + readMethod);
+ }
+ propertyType = readMethod.getReturnType();
+ if (propertyType == Void.TYPE) {
+ throw new IntrospectionException("read method "
+ + readMethod.getName() + " returns void");
+ }
+ }
+ if (writeMethod != null) {
+ Class> params[] = writeMethod.getParameterTypes();
+ if (params.length != 1) {
+ throw new IntrospectionException("bad write method arg count: " + writeMethod);
+ }
+ if (propertyType != null
+ && !params[0].isAssignableFrom(propertyType)) {
+ throw new IntrospectionException("type mismatch between read and write methods");
+ }
+ propertyType = params[0];
+ }
+ return propertyType;
+ }
+
+ /*
+ * See IndexedPropertyDescriptor#findIndexedPropertyType
+ */
+ public static Class> findIndexedPropertyType(String name, Class> propertyType,
+ Method indexedReadMethod, Method indexedWriteMethod)
+ throws IntrospectionException {
+
+ Class> indexedPropertyType = null;
+
+ if (indexedReadMethod != null) {
+ Class> params[] = indexedReadMethod.getParameterTypes();
+ if (params.length != 1) {
+ throw new IntrospectionException(
+ "bad indexed read method arg count");
+ }
+ if (params[0] != Integer.TYPE) {
+ throw new IntrospectionException(
+ "non int index to indexed read method");
+ }
+ indexedPropertyType = indexedReadMethod.getReturnType();
+ if (indexedPropertyType == Void.TYPE) {
+ throw new IntrospectionException(
+ "indexed read method returns void");
+ }
+ }
+ if (indexedWriteMethod != null) {
+ Class> params[] = indexedWriteMethod.getParameterTypes();
+ if (params.length != 2) {
+ throw new IntrospectionException(
+ "bad indexed write method arg count");
+ }
+ if (params[0] != Integer.TYPE) {
+ throw new IntrospectionException(
+ "non int index to indexed write method");
+ }
+ if (indexedPropertyType != null && indexedPropertyType != params[1]) {
+ throw new IntrospectionException(
+ "type mismatch between indexed read and indexed write methods: " + name);
+ }
+ indexedPropertyType = params[1];
+ }
+ if (propertyType != null
+ && (!propertyType.isArray() ||
+ propertyType.getComponentType() != indexedPropertyType)) {
+ throw new IntrospectionException(
+ "type mismatch between indexed and non-indexed methods: " + name);
+ }
+ return indexedPropertyType;
+ }
+
+ /**
+ * Compare the given {@link PropertyDescriptor} against the given {@link Object} and
+ * return {@code true} if they are objects are equivalent, i.e. both are {@code
+ * PropertyDescriptor}s whose read method, write method, property types, property
+ * editor and flags are equivalent.
+ *
+ * @see PropertyDescriptor#equals(Object)
+ */
+ public static boolean equals(PropertyDescriptor pd1, Object obj) {
+ if (pd1 == obj) {
+ return true;
+ }
+ if (obj != null && obj instanceof PropertyDescriptor) {
+ PropertyDescriptor pd2 = (PropertyDescriptor) obj;
+ if (!compareMethods(pd1.getReadMethod(), pd2.getReadMethod())) {
+ return false;
+ }
+
+ if (!compareMethods(pd1.getWriteMethod(), pd2.getWriteMethod())) {
+ return false;
+ }
+
+ if (pd1.getPropertyType() == pd2.getPropertyType()
+ && pd1.getPropertyEditorClass() == pd2.getPropertyEditorClass()
+ && pd1.isBound() == pd2.isBound()
+ && pd1.isConstrained() == pd2.isConstrained()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /*
+ * see PropertyDescriptor#compareMethods
+ */
+ public static boolean compareMethods(Method a, Method b) {
+ if ((a == null) != (b == null)) {
+ return false;
+ }
+
+ if (a != null && b != null) {
+ if (!a.equals(b)) {
+ return false;
+ }
+ }
+ return true;
+ }
+}
+
+
+/**
+ * Sorts PropertyDescriptor instances alpha-numerically to emulate the behavior of
+ * {@link java.beans.BeanInfo#getPropertyDescriptors()}.
+ *
+ * @see ExtendedBeanInfo#propertyDescriptors
+ */
+class PropertyDescriptorComparator implements Comparator {
+
+ public int compare(PropertyDescriptor desc1, PropertyDescriptor desc2) {
+ String left = desc1.getName();
+ String right = desc2.getName();
+ for (int i = 0; i < left.length(); i++) {
+ if (right.length() == i) {
+ return 1;
+ }
+ int result = left.getBytes()[i] - right.getBytes()[i];
+ if (result != 0) {
+ return result;
+ }
+ }
+ return left.length() - right.length();
+ }
+}
diff --git a/spring-beans/src/main/java/org/springframework/beans/ExtendedBeanInfoFactory.java b/spring-beans/src/main/java/org/springframework/beans/ExtendedBeanInfoFactory.java
index 2eafdea317..7e46211922 100644
--- a/spring-beans/src/main/java/org/springframework/beans/ExtendedBeanInfoFactory.java
+++ b/spring-beans/src/main/java/org/springframework/beans/ExtendedBeanInfoFactory.java
@@ -20,7 +20,6 @@ import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
import org.springframework.core.Ordered;
@@ -52,14 +51,7 @@ public class ExtendedBeanInfoFactory implements Ordered, BeanInfoFactory {
*/
private boolean supports(Class> beanClass) {
for (Method method : beanClass.getMethods()) {
- String methodName = method.getName();
- Class>[] parameterTypes = method.getParameterTypes();
- if (Modifier.isPublic(method.getModifiers())
- && methodName.length() > 3
- && methodName.startsWith("set")
- && (parameterTypes.length == 1
- || (parameterTypes.length == 2 && parameterTypes[0].equals(int.class)))
- && !void.class.isAssignableFrom(method.getReturnType())) {
+ if (ExtendedBeanInfo.isNonVoidWriteMethod(method)) {
return true;
}
}
diff --git a/spring-beans/src/test/java/org/springframework/beans/ExtendedBeanInfoTests.java b/spring-beans/src/test/java/org/springframework/beans/ExtendedBeanInfoTests.java
index 5187ae2d9e..5e05fc91db 100644
--- a/spring-beans/src/test/java/org/springframework/beans/ExtendedBeanInfoTests.java
+++ b/spring-beans/src/test/java/org/springframework/beans/ExtendedBeanInfoTests.java
@@ -24,22 +24,22 @@ import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
-import org.junit.Ignore;
import org.junit.Test;
-import org.springframework.beans.ExtendedBeanInfo.PropertyDescriptorComparator;
import org.springframework.core.JdkVersion;
import org.springframework.util.ClassUtils;
import test.beans.TestBean;
-import static org.junit.Assert.*;
-import static org.hamcrest.CoreMatchers.equalTo;
-import static org.hamcrest.CoreMatchers.instanceOf;
-import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.lessThan;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.is;
+
+import static org.junit.Assert.*;
+
/**
* Unit tests for {@link ExtendedBeanInfo}.
*
@@ -128,12 +128,32 @@ public class ExtendedBeanInfoTests {
ExtendedBeanInfo ebi = new ExtendedBeanInfo(bi);
assertThat(hasReadMethodForProperty(bi, "foo"), is(true));
- assertThat(hasWriteMethodForProperty(bi, "foo"), is(true));
+ assertThat(hasWriteMethodForProperty(bi, "foo"), is(false));
assertThat(hasReadMethodForProperty(ebi, "foo"), is(true));
assertThat(hasWriteMethodForProperty(ebi, "foo"), is(true));
}
+ @Test
+ public void standardReadAndNonStandardIndexedWriteMethod() throws IntrospectionException {
+ @SuppressWarnings("unused") class C {
+ public String[] getFoo() { return null; }
+ public C setFoo(int i, String foo) { return this; }
+ }
+
+ BeanInfo bi = Introspector.getBeanInfo(C.class);
+
+ assertThat(hasReadMethodForProperty(bi, "foo"), is(true));
+ assertThat(hasWriteMethodForProperty(bi, "foo"), is(false));
+ assertThat(hasIndexedWriteMethodForProperty(bi, "foo"), is(trueUntilJdk17()));
+
+ BeanInfo ebi = new ExtendedBeanInfo(bi);
+
+ assertThat(hasReadMethodForProperty(ebi, "foo"), is(true));
+ assertThat(hasWriteMethodForProperty(ebi, "foo"), is(false));
+ assertThat(hasIndexedWriteMethodForProperty(ebi, "foo"), is(true));
+ }
+
@Test
public void standardReadMethodsAndOverloadedNonStandardWriteMethods() throws Exception {
@SuppressWarnings("unused") class C {
@@ -150,7 +170,7 @@ public class ExtendedBeanInfoTests {
ExtendedBeanInfo ebi = new ExtendedBeanInfo(bi);
assertThat(hasReadMethodForProperty(bi, "foo"), is(true));
- assertThat(hasWriteMethodForProperty(bi, "foo"), is(trueUntilJdk17()));
+ assertThat(hasWriteMethodForProperty(bi, "foo"), is(false));
assertThat(hasReadMethodForProperty(ebi, "foo"), is(true));
assertThat(hasWriteMethodForProperty(ebi, "foo"), is(true));
@@ -225,7 +245,7 @@ public class ExtendedBeanInfoTests {
ExtendedBeanInfo ebi = new ExtendedBeanInfo(bi);
assertThat(hasReadMethodForProperty(bi, "foo"), is(true));
- assertThat(hasWriteMethodForProperty(bi, "foo"), is(true));
+ assertThat(hasWriteMethodForProperty(bi, "foo"), is(false));
assertThat(hasReadMethodForProperty(ebi, "foo"), is(true));
assertThat(hasWriteMethodForProperty(ebi, "foo"), is(true));
@@ -268,13 +288,13 @@ public class ExtendedBeanInfoTests {
assertThat(hasReadMethodForProperty(bi, "bar"), is(true));
assertThat(hasWriteMethodForProperty(bi, "bar"), is(false));
- ExtendedBeanInfo ebi = new ExtendedBeanInfo(bi);
+ BeanInfo ebi = new ExtendedBeanInfo(bi);
assertThat(hasReadMethodForProperty(bi, "foo"), is(true));
- assertThat(hasWriteMethodForProperty(bi, "foo"), is(true));
+ assertThat(hasWriteMethodForProperty(bi, "foo"), is(false));
assertThat(hasReadMethodForProperty(bi, "bar"), is(true));
- assertThat(hasWriteMethodForProperty(bi, "bar"), is(true));
+ assertThat(hasWriteMethodForProperty(bi, "bar"), is(false));
assertThat(hasReadMethodForProperty(ebi, "foo"), is(true));
assertThat(hasWriteMethodForProperty(ebi, "foo"), is(true));
@@ -291,7 +311,7 @@ public class ExtendedBeanInfoTests {
}
BeanInfo bi = Introspector.getBeanInfo(C.class);
- ExtendedBeanInfo ebi = new ExtendedBeanInfo(bi);
+ BeanInfo ebi = new ExtendedBeanInfo(bi);
assertThat(hasReadMethodForProperty(bi, "foo"), is(false));
assertThat(hasWriteMethodForProperty(bi, "foo"), is(false));
@@ -312,7 +332,7 @@ public class ExtendedBeanInfoTests {
}
BeanInfo bi = Introspector.getBeanInfo(C.class);
- ExtendedBeanInfo ebi = new ExtendedBeanInfo(bi);
+ BeanInfo ebi = new ExtendedBeanInfo(bi);
assertThat(hasReadMethodForProperty(bi, "foo"), is(true));
assertThat(hasWriteMethodForProperty(bi, "foo"), is(false));
@@ -329,7 +349,7 @@ public class ExtendedBeanInfoTests {
}
BeanInfo bi = Introspector.getBeanInfo(C.class);
- ExtendedBeanInfo ebi = new ExtendedBeanInfo(bi);
+ BeanInfo ebi = new ExtendedBeanInfo(bi);
assertThat(hasIndexedReadMethodForProperty(bi, "foos"), is(true));
assertThat(hasIndexedWriteMethodForProperty(bi, "foos"), is(false));
@@ -350,7 +370,7 @@ public class ExtendedBeanInfoTests {
}
BeanInfo bi = Introspector.getBeanInfo(C.class);
- ExtendedBeanInfo ebi = new ExtendedBeanInfo(bi);
+ BeanInfo ebi = new ExtendedBeanInfo(bi);
assertThat(hasReadMethodForProperty(bi, "foo"), is(true));
assertThat(hasWriteMethodForProperty(bi, "foo"), is(false));
@@ -367,7 +387,7 @@ public class ExtendedBeanInfoTests {
}
BeanInfo bi = Introspector.getBeanInfo(C.class);
- ExtendedBeanInfo ebi = new ExtendedBeanInfo(bi);
+ BeanInfo ebi = new ExtendedBeanInfo(bi);
assertThat(hasIndexedReadMethodForProperty(bi, "foos"), is(true));
assertThat(hasIndexedWriteMethodForProperty(bi, "foos"), is(false));
@@ -508,15 +528,14 @@ public class ExtendedBeanInfoTests {
BeanInfo ebi = new ExtendedBeanInfo(Introspector.getBeanInfo(C.class));
assertThat(hasIndexedReadMethodForProperty(bi, "foos"), is(true));
- assertThat(hasWriteMethodForProperty(bi, "foos"), is(true));
- assertThat(hasIndexedWriteMethodForProperty(bi, "foos"), is(true));
+ assertThat(hasWriteMethodForProperty(bi, "foos"), is(false));
+ assertThat(hasIndexedWriteMethodForProperty(bi, "foos"), is(trueUntilJdk17()));
assertThat(hasIndexedReadMethodForProperty(ebi, "foos"), is(true));
assertThat(hasWriteMethodForProperty(ebi, "foos"), is(true));
assertThat(hasIndexedWriteMethodForProperty(ebi, "foos"), is(true));
}
- @Ignore // see comments at SPR-9702
@Test
public void cornerSpr9702() throws IntrospectionException {
{ // baseline with standard write method
@@ -579,10 +598,10 @@ public class ExtendedBeanInfoTests {
assertThat(hasReadMethodForProperty(bi, "foo"), is(true));
assertThat(hasWriteMethodForProperty(bi, "foo"), is(false));
- ExtendedBeanInfo ebi = new ExtendedBeanInfo(bi);
+ BeanInfo ebi = new ExtendedBeanInfo(bi);
assertThat(hasReadMethodForProperty(bi, "foo"), is(true));
- assertThat(hasWriteMethodForProperty(bi, "foo"), is(true));
+ assertThat(hasWriteMethodForProperty(bi, "foo"), is(false));
assertThat(hasReadMethodForProperty(ebi, "foo"), is(true));
assertThat(hasWriteMethodForProperty(ebi, "foo"), is(true));
@@ -598,7 +617,7 @@ public class ExtendedBeanInfoTests {
}
BeanInfo bi = Introspector.getBeanInfo(C.class);
- ExtendedBeanInfo ebi = new ExtendedBeanInfo(bi);
+ BeanInfo ebi = new ExtendedBeanInfo(bi);
assertThat(hasReadMethodForProperty(bi, "foo"), is(false));
assertThat(hasWriteMethodForProperty(bi, "foo"), is(true));
@@ -619,11 +638,67 @@ public class ExtendedBeanInfoTests {
}
BeanInfo bi = Introspector.getBeanInfo(C.class);
- ExtendedBeanInfo ebi = new ExtendedBeanInfo(bi);
+ BeanInfo ebi = new ExtendedBeanInfo(bi);
assertThat(ebi.getPropertyDescriptors(), equalTo(bi.getPropertyDescriptors()));
}
+ @Test
+ public void overloadedNonStandardWriteMethodsOnly_orderA() throws IntrospectionException, SecurityException, NoSuchMethodException {
+ @SuppressWarnings("unused") class C {
+ public Object setFoo(String p) { return new Object(); }
+ public Object setFoo(int p) { return new Object(); }
+ }
+ BeanInfo bi = Introspector.getBeanInfo(C.class);
+
+ assertThat(hasReadMethodForProperty(bi, "foo"), is(false));
+ assertThat(hasWriteMethodForProperty(bi, "foo"), is(false));
+
+ BeanInfo ebi = new ExtendedBeanInfo(bi);
+
+ assertThat(hasReadMethodForProperty(bi, "foo"), is(false));
+ assertThat(hasWriteMethodForProperty(bi, "foo"), is(false));
+
+ assertThat(hasReadMethodForProperty(ebi, "foo"), is(false));
+ assertThat(hasWriteMethodForProperty(ebi, "foo"), is(true));
+
+ for (PropertyDescriptor pd : ebi.getPropertyDescriptors()) {
+ if (pd.getName().equals("foo")) {
+ assertThat(pd.getWriteMethod(), is(C.class.getMethod("setFoo", String.class)));
+ return;
+ }
+ }
+ fail("never matched write method");
+ }
+
+ @Test
+ public void overloadedNonStandardWriteMethodsOnly_orderB() throws IntrospectionException, SecurityException, NoSuchMethodException {
+ @SuppressWarnings("unused") class C {
+ public Object setFoo(int p) { return new Object(); }
+ public Object setFoo(String p) { return new Object(); }
+ }
+ BeanInfo bi = Introspector.getBeanInfo(C.class);
+
+ assertThat(hasReadMethodForProperty(bi, "foo"), is(false));
+ assertThat(hasWriteMethodForProperty(bi, "foo"), is(false));
+
+ BeanInfo ebi = new ExtendedBeanInfo(bi);
+
+ assertThat(hasReadMethodForProperty(bi, "foo"), is(false));
+ assertThat(hasWriteMethodForProperty(bi, "foo"), is(false));
+
+ assertThat(hasReadMethodForProperty(ebi, "foo"), is(false));
+ assertThat(hasWriteMethodForProperty(ebi, "foo"), is(true));
+
+ for (PropertyDescriptor pd : ebi.getPropertyDescriptors()) {
+ if (pd.getName().equals("foo")) {
+ assertThat(pd.getWriteMethod(), is(C.class.getMethod("setFoo", int.class)));
+ return;
+ }
+ }
+ fail("never matched write method");
+ }
+
/**
* Corners the bug revealed by SPR-8522, in which an (apparently) indexed write method
* without a corresponding indexed read method would fail to be processed correctly by
@@ -645,7 +720,7 @@ public class ExtendedBeanInfoTests {
assertThat(hasIndexedReadMethodForProperty(bi, "dateFormat"), is(false));
assertThat(hasIndexedWriteMethodForProperty(bi, "dateFormat"), is(trueUntilJdk17()));
- ExtendedBeanInfo ebi = new ExtendedBeanInfo(bi);
+ BeanInfo ebi = new ExtendedBeanInfo(bi);
assertThat(hasReadMethodForProperty(bi, "dateFormat"), is(false));
assertThat(hasWriteMethodForProperty(bi, "dateFormat"), is(false));
@@ -653,7 +728,7 @@ public class ExtendedBeanInfoTests {
assertThat(hasIndexedWriteMethodForProperty(bi, "dateFormat"), is(trueUntilJdk17()));
assertThat(hasReadMethodForProperty(ebi, "dateFormat"), is(false));
- assertThat(hasWriteMethodForProperty(ebi, "dateFormat"), is(false));
+ assertThat(hasWriteMethodForProperty(ebi, "dateFormat"), is(true));
assertThat(hasIndexedReadMethodForProperty(ebi, "dateFormat"), is(false));
assertThat(hasIndexedWriteMethodForProperty(ebi, "dateFormat"), is(true));
}
@@ -661,7 +736,7 @@ public class ExtendedBeanInfoTests {
@Test
public void propertyCountsMatch() throws IntrospectionException {
BeanInfo bi = Introspector.getBeanInfo(TestBean.class);
- ExtendedBeanInfo ebi = new ExtendedBeanInfo(bi);
+ BeanInfo ebi = new ExtendedBeanInfo(bi);
assertThat(ebi.getPropertyDescriptors().length, equalTo(bi.getPropertyDescriptors().length));
}
@@ -673,7 +748,7 @@ public class ExtendedBeanInfoTests {
public ExtendedTestBean setFoo(String s) { return this; }
}
BeanInfo bi = Introspector.getBeanInfo(ExtendedTestBean.class);
- ExtendedBeanInfo ebi = new ExtendedBeanInfo(bi);
+ BeanInfo ebi = new ExtendedBeanInfo(bi);
boolean found = false;
for (PropertyDescriptor pd : ebi.getPropertyDescriptors()) {
@@ -692,7 +767,7 @@ public class ExtendedBeanInfoTests {
@Test
public void propertyDescriptorOrderIsEqual() throws IntrospectionException {
BeanInfo bi = Introspector.getBeanInfo(TestBean.class);
- ExtendedBeanInfo ebi = new ExtendedBeanInfo(bi);
+ BeanInfo ebi = new ExtendedBeanInfo(bi);
for (int i = 0; i < bi.getPropertyDescriptors().length; i++) {
assertThat("element " + i + " in BeanInfo and ExtendedBeanInfo propertyDescriptor arrays do not match",
@@ -739,7 +814,9 @@ public class ExtendedBeanInfoTests {
private boolean hasIndexedWriteMethodForProperty(BeanInfo beanInfo, String propertyName) {
for (PropertyDescriptor pd : beanInfo.getPropertyDescriptors()) {
if (pd.getName().equals(propertyName)) {
- assertThat(propertyName + " property is not indexed", pd, instanceOf(IndexedPropertyDescriptor.class));
+ if (!(pd instanceof IndexedPropertyDescriptor)) {
+ return false;
+ }
return ((IndexedPropertyDescriptor)pd).getIndexedWriteMethod() != null;
}
}
@@ -749,7 +826,9 @@ public class ExtendedBeanInfoTests {
private boolean hasIndexedReadMethodForProperty(BeanInfo beanInfo, String propertyName) {
for (PropertyDescriptor pd : beanInfo.getPropertyDescriptors()) {
if (pd.getName().equals(propertyName)) {
- assertThat(propertyName + " property is not indexed", pd, instanceOf(IndexedPropertyDescriptor.class));
+ if (!(pd instanceof IndexedPropertyDescriptor)) {
+ return false;
+ }
return ((IndexedPropertyDescriptor)pd).getIndexedReadMethod() != null;
}
}
@@ -830,7 +909,7 @@ public class ExtendedBeanInfoTests {
}
// and now demonstrate that we've indeed fixed the problem
- ExtendedBeanInfo ebi = new ExtendedBeanInfo(bi);
+ BeanInfo ebi = new ExtendedBeanInfo(bi);
assertThat(hasReadMethodForProperty(bi, "targetMethod"), is(true));
assertThat(hasWriteMethodForProperty(bi, "targetMethod"), is(false));
@@ -855,12 +934,11 @@ public class ExtendedBeanInfoTests {
assertThat(hasIndexedWriteMethodForProperty(bi, "address"), is(true));
}
{
- ExtendedBeanInfo bi = new ExtendedBeanInfo(Introspector.getBeanInfo(A.class));
+ BeanInfo bi = new ExtendedBeanInfo(Introspector.getBeanInfo(A.class));
assertThat(hasReadMethodForProperty(bi, "address"), is(false));
assertThat(hasWriteMethodForProperty(bi, "address"), is(false));
assertThat(hasIndexedReadMethodForProperty(bi, "address"), is(true));
assertThat(hasIndexedWriteMethodForProperty(bi, "address"), is(true));
}
}
-
}
diff --git a/spring-beans/src/test/java/org/springframework/beans/SimplePropertyDescriptorTests.java b/spring-beans/src/test/java/org/springframework/beans/SimplePropertyDescriptorTests.java
new file mode 100644
index 0000000000..8627083279
--- /dev/null
+++ b/spring-beans/src/test/java/org/springframework/beans/SimplePropertyDescriptorTests.java
@@ -0,0 +1,150 @@
+/*
+ * 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.beans;
+
+import java.beans.IndexedPropertyDescriptor;
+import java.beans.IntrospectionException;
+import java.beans.PropertyDescriptor;
+
+import java.lang.reflect.Method;
+
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.*;
+import static org.junit.Assert.*;
+
+/**
+ * Unit tests for {@link SimpleNonIndexedPropertyDescriptor} and
+ * {@link SimpleIndexedPropertyDescriptor}.
+ *
+ * @author Chris Beams
+ * @see ExtendedBeanInfoTests
+ */
+public class SimplePropertyDescriptorTests {
+
+ @Test
+ public void toStringOutput() throws IntrospectionException, SecurityException, NoSuchMethodException {
+ {
+ Object pd = new SimpleNonIndexedPropertyDescriptor("foo", null, null);
+ assertThat(pd.toString(), containsString(
+ "PropertyDescriptor[name=foo, propertyType=null, readMethod=null"));
+ }
+ {
+ class C {
+ @SuppressWarnings("unused")
+ public Object setFoo(String foo) { return null; }
+ }
+ Method m = C.class.getMethod("setFoo", String.class);
+ Object pd = new SimpleNonIndexedPropertyDescriptor("foo", null, m);
+ assertThat(pd.toString(), allOf(
+ containsString("PropertyDescriptor[name=foo"),
+ containsString("propertyType=class java.lang.String"),
+ containsString("readMethod=null, writeMethod=public java.lang.Object")));
+ }
+ {
+ Object pd = new SimpleIndexedPropertyDescriptor("foo", null, null, null, null);
+ assertThat(pd.toString(), containsString(
+ "PropertyDescriptor[name=foo, propertyType=null, indexedPropertyType=null"));
+ }
+ {
+ class C {
+ @SuppressWarnings("unused")
+ public Object setFoo(int i, String foo) { return null; }
+ }
+ Method m = C.class.getMethod("setFoo", int.class, String.class);
+ Object pd = new SimpleIndexedPropertyDescriptor("foo", null, null, null, m);
+ assertThat(pd.toString(), allOf(
+ containsString("PropertyDescriptor[name=foo, propertyType=null"),
+ containsString("indexedPropertyType=class java.lang.String"),
+ containsString("indexedWriteMethod=public java.lang.Object")));
+ }
+ }
+
+ @Test
+ public void nonIndexedEquality() throws IntrospectionException, SecurityException, NoSuchMethodException {
+ Object pd1 = new SimpleNonIndexedPropertyDescriptor("foo", null, null);
+ assertThat(pd1, equalTo(pd1));
+
+ Object pd2 = new SimpleNonIndexedPropertyDescriptor("foo", null, null);
+ assertThat(pd1, equalTo(pd2));
+ assertThat(pd2, equalTo(pd1));
+
+ @SuppressWarnings("unused")
+ class C {
+ public Object setFoo(String foo) { return null; }
+ public String getFoo() { return null; }
+ }
+ Method wm1 = C.class.getMethod("setFoo", String.class);
+ Object pd3 = new SimpleNonIndexedPropertyDescriptor("foo", null, wm1);
+ assertThat(pd1, not(equalTo(pd3)));
+ assertThat(pd3, not(equalTo(pd1)));
+
+ Method rm1 = C.class.getMethod("getFoo");
+ Object pd4 = new SimpleNonIndexedPropertyDescriptor("foo", rm1, null);
+ assertThat(pd1, not(equalTo(pd4)));
+ assertThat(pd4, not(equalTo(pd1)));
+
+ Object pd5 = new PropertyDescriptor("foo", null, null);
+ assertThat(pd1, equalTo(pd5));
+ assertThat(pd5, equalTo(pd1));
+
+ Object pd6 = "not a PD";
+ assertThat(pd1, not(equalTo(pd6)));
+ assertThat(pd6, not(equalTo(pd1)));
+
+ Object pd7 = null;
+ assertThat(pd1, not(equalTo(pd7)));
+ assertThat(pd7, not(equalTo(pd1)));
+ }
+
+ @Test
+ public void indexedEquality() throws IntrospectionException, SecurityException, NoSuchMethodException {
+ Object pd1 = new SimpleIndexedPropertyDescriptor("foo", null, null, null, null);
+ assertThat(pd1, equalTo(pd1));
+
+ Object pd2 = new SimpleIndexedPropertyDescriptor("foo", null, null, null, null);
+ assertThat(pd1, equalTo(pd2));
+ assertThat(pd2, equalTo(pd1));
+
+ @SuppressWarnings("unused")
+ class C {
+ public Object setFoo(int i, String foo) { return null; }
+ public String getFoo(int i) { return null; }
+ }
+ Method wm1 = C.class.getMethod("setFoo", int.class, String.class);
+ Object pd3 = new SimpleIndexedPropertyDescriptor("foo", null, null, null, wm1);
+ assertThat(pd1, not(equalTo(pd3)));
+ assertThat(pd3, not(equalTo(pd1)));
+
+ Method rm1 = C.class.getMethod("getFoo", int.class);
+ Object pd4 = new SimpleIndexedPropertyDescriptor("foo", null, null, rm1, null);
+ assertThat(pd1, not(equalTo(pd4)));
+ assertThat(pd4, not(equalTo(pd1)));
+
+ Object pd5 = new IndexedPropertyDescriptor("foo", null, null, null, null);
+ assertThat(pd1, equalTo(pd5));
+ assertThat(pd5, equalTo(pd1));
+
+ Object pd6 = "not a PD";
+ assertThat(pd1, not(equalTo(pd6)));
+ assertThat(pd6, not(equalTo(pd1)));
+
+ Object pd7 = null;
+ assertThat(pd1, not(equalTo(pd7)));
+ assertThat(pd7, not(equalTo(pd1)));
+ }
+}