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: + *
{@code
+ * public class Bean {
+ *     private Foo foo;
+ *
+ *     public Foo getFoo() {
+ *         return this.foo;
+ *     }
+ *
+ *     public Bean setFoo(Foo foo) {
+ *         this.foo = foo;
+ *         return this;
+ *     }
+ * }
+ * The standard JavaBeans {@code Introspector} will discover the {@code getFoo} read + * method, but will bypass the {@code #setFoo(Foo)} write method, because its non-void + * returning signature does not comply with the JavaBeans specification. + * {@code ExtendedBeanInfo}, on the other hand, will recognize and include it. This is + * designed to allow APIs with "builder" or method-chaining style setter signatures to be + * used within Spring {@code } 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))); + } +}