From e161c93f8dd9fdf7d7685e3ae7a31de03331ae86 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Fri, 4 Dec 2009 00:34:40 +0000 Subject: [PATCH] full support for formatters on array/collection elements (SPR-6504) --- .../beans/BeanTypeDescriptor.java | 7 ++- .../beans/BeanWrapperImpl.java | 11 ++-- .../AbstractPropertyBindingResult.java | 7 ++- .../format/number/NumberFormattingTests.java | 63 ++++++++++++++++++- .../convert/ConversionFailedException.java | 5 +- .../core/convert/TypeDescriptor.java | 29 +++++---- 6 files changed, 97 insertions(+), 25 deletions(-) diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/BeanTypeDescriptor.java b/org.springframework.beans/src/main/java/org/springframework/beans/BeanTypeDescriptor.java index b9099fe42f5..17adfa23502 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/BeanTypeDescriptor.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/BeanTypeDescriptor.java @@ -53,11 +53,12 @@ class BeanTypeDescriptor extends TypeDescriptor { /** * Create a new BeanTypeDescriptor for the given bean property. - * @param methodParameter the target method parameter * @param propertyDescriptor the corresponding JavaBean PropertyDescriptor + * @param methodParameter the target method parameter + * @param type the specific type to expose (may be an array/collection element) */ - public BeanTypeDescriptor(MethodParameter methodParameter, PropertyDescriptor propertyDescriptor) { - super(methodParameter); + public BeanTypeDescriptor(PropertyDescriptor propertyDescriptor, MethodParameter methodParameter, Class type) { + super(methodParameter, type); this.propertyDescriptor = propertyDescriptor; } diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/BeanWrapperImpl.java b/org.springframework.beans/src/main/java/org/springframework/beans/BeanWrapperImpl.java index 5b1b6a01e24..b6ab6458344 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/BeanWrapperImpl.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/BeanWrapperImpl.java @@ -354,13 +354,15 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra public TypeDescriptor getPropertyTypeDescriptor(String propertyName) throws BeansException { try { - PropertyDescriptor pd = getPropertyDescriptorInternal(propertyName); + String canonicalName = PropertyAccessorUtils.getPropertyName(propertyName); + PropertyDescriptor pd = getPropertyDescriptorInternal(canonicalName); if (pd != null) { + Class type = getPropertyType(propertyName); if (pd.getReadMethod() != null) { - return new BeanTypeDescriptor(new MethodParameter(pd.getReadMethod(), -1), pd); + return new BeanTypeDescriptor(pd, new MethodParameter(pd.getReadMethod(), -1), type); } else if (pd.getWriteMethod() != null) { - return new BeanTypeDescriptor(BeanUtils.getWriteMethodParameter(pd), pd); + return new BeanTypeDescriptor(pd, BeanUtils.getWriteMethodParameter(pd), type); } } } @@ -579,7 +581,8 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra } } catch (Exception ex) { - throw new NullValueInNestedPathException(getRootClass(), this.nestedPath + name, "Could not instantiate property type [" + type.getName() + "] to auto-grow nested property path: " + ex); + throw new NullValueInNestedPathException(getRootClass(), this.nestedPath + name, + "Could not instantiate property type [" + type.getName() + "] to auto-grow nested property path: " + ex); } } diff --git a/org.springframework.context/src/main/java/org/springframework/validation/AbstractPropertyBindingResult.java b/org.springframework.context/src/main/java/org/springframework/validation/AbstractPropertyBindingResult.java index 864d9b2c451..493863abdd1 100644 --- a/org.springframework.context/src/main/java/org/springframework/validation/AbstractPropertyBindingResult.java +++ b/org.springframework.context/src/main/java/org/springframework/validation/AbstractPropertyBindingResult.java @@ -117,10 +117,11 @@ public abstract class AbstractPropertyBindingResult extends AbstractBindingResul if (this.conversionService != null) { // Try custom formatter... TypeDescriptor td = getPropertyAccessor().getPropertyTypeDescriptor(fixedField); - return this.conversionService.convert(value, td, TypeDescriptor.valueOf(String.class)); - } else { - return value; + if (td != null) { + return this.conversionService.convert(value, td, TypeDescriptor.valueOf(String.class)); + } } + return value; } /** diff --git a/org.springframework.context/src/test/java/org/springframework/format/number/NumberFormattingTests.java b/org.springframework.context/src/test/java/org/springframework/format/number/NumberFormattingTests.java index 7518f116837..6fae27fd36c 100644 --- a/org.springframework.context/src/test/java/org/springframework/format/number/NumberFormattingTests.java +++ b/org.springframework.context/src/test/java/org/springframework/format/number/NumberFormattingTests.java @@ -18,6 +18,7 @@ package org.springframework.format.number; import java.math.BigDecimal; import java.util.Locale; +import java.util.List; import org.junit.After; import static org.junit.Assert.*; @@ -102,6 +103,43 @@ public class NumberFormattingTests { assertEquals("1,25.00", binder.getBindingResult().getFieldValue("pattern")); } + @Test + public void testPatternArrayFormatting() { + MutablePropertyValues propertyValues = new MutablePropertyValues(); + propertyValues.add("patternArray", new String[] {"1,25.00", "2,35.00"}); + binder.bind(propertyValues); + assertEquals(0, binder.getBindingResult().getErrorCount()); + assertEquals("1,25.00", binder.getBindingResult().getFieldValue("patternArray[0]")); + assertEquals("2,35.00", binder.getBindingResult().getFieldValue("patternArray[1]")); + + propertyValues = new MutablePropertyValues(); + propertyValues.add("patternArray[0]", "1,25.00"); + propertyValues.add("patternArray[1]", "2,35.00"); + binder.bind(propertyValues); + assertEquals(0, binder.getBindingResult().getErrorCount()); + assertEquals("1,25.00", binder.getBindingResult().getFieldValue("patternArray[0]")); + assertEquals("2,35.00", binder.getBindingResult().getFieldValue("patternArray[1]")); + } + + @Test + public void testPatternListFormatting() { + MutablePropertyValues propertyValues = new MutablePropertyValues(); + propertyValues.add("patternList", new String[] {"1,25.00", "2,35.00"}); + binder.bind(propertyValues); + assertEquals(0, binder.getBindingResult().getErrorCount()); + assertEquals("1,25.00", binder.getBindingResult().getFieldValue("patternList[0]")); + assertEquals("2,35.00", binder.getBindingResult().getFieldValue("patternList[1]")); + + propertyValues = new MutablePropertyValues(); + propertyValues.add("patternList[0]", "1,25.00"); + propertyValues.add("patternList[1]", "2,35.00"); + binder.bind(propertyValues); + assertEquals(0, binder.getBindingResult().getErrorCount()); + assertEquals("1,25.00", binder.getBindingResult().getFieldValue("patternList[0]")); + assertEquals("2,35.00", binder.getBindingResult().getFieldValue("patternList[1]")); + } + + @SuppressWarnings("unused") private static class TestBean { @@ -119,6 +157,12 @@ public class NumberFormattingTests { @NumberFormat(pattern="#,##.00") private BigDecimal pattern; + @NumberFormat(pattern="#,##.00") + private BigDecimal[] patternArray; + + @NumberFormat(pattern="#,##.00") + private List[] patternList; + public Integer getNumberDefault() { return numberDefault; } @@ -159,6 +203,21 @@ public class NumberFormattingTests { this.pattern = pattern; } - + public BigDecimal[] getPatternArray() { + return patternArray; + } + + public void setPatternArray(BigDecimal[] patternArray) { + this.patternArray = patternArray; + } + + public List[] getPatternList() { + return patternList; + } + + public void setPatternList(List[] patternList) { + this.patternList = patternList; + } } -} \ No newline at end of file + +} diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/ConversionFailedException.java b/org.springframework.core/src/main/java/org/springframework/core/convert/ConversionFailedException.java index 0f83803f434..47a011db169 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/ConversionFailedException.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/ConversionFailedException.java @@ -20,6 +20,7 @@ package org.springframework.core.convert; * Thrown when an attempt to execute a type conversion fails. * * @author Keith Donald + * @author Juergen Hoeller * @since 3.0 */ public final class ConversionFailedException extends ConversionException { @@ -37,8 +38,8 @@ public final class ConversionFailedException extends ConversionException { * @param cause the cause of the conversion failure */ public ConversionFailedException(TypeDescriptor sourceType, TypeDescriptor targetType, Object value, Throwable cause) { - super("Unable to convert value " + value + " from type [" + sourceType.getName() + "] to type [" + - targetType.getName() + "]; reason = '" + cause.getMessage() + "'", cause); + super("Unable to convert value " + value + " from type '" + sourceType.getName() + + "' to type '" + targetType.getName() + "'", cause); this.sourceType = sourceType; this.targetType = targetType; } diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/TypeDescriptor.java b/org.springframework.core/src/main/java/org/springframework/core/convert/TypeDescriptor.java index 8448b1e841a..0a4ec7188d7 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/TypeDescriptor.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/TypeDescriptor.java @@ -77,6 +77,19 @@ public class TypeDescriptor { this.methodParameter = methodParameter; } + /** + * Create a new type descriptor from a method or constructor parameter. + *

Use this constructor when a target conversion point originates from a method parameter, + * such as a setter method argument. + * @param methodParameter the MethodParameter to wrap + * @param type the specific type to expose (may be an array/collection element) + */ + protected TypeDescriptor(MethodParameter methodParameter, Class type) { + Assert.notNull(methodParameter, "MethodParameter must not be null"); + this.methodParameter = methodParameter; + this.type = type; + } + /** * Create a new type descriptor for a field. * Use this constructor when a target conversion point originates from a field. @@ -158,16 +171,11 @@ public class TypeDescriptor { } /** - * Returns the name of this type; the fully qualified classname. + * Returns the name of this type: the fully qualified class name. */ public String getName() { Class type = getType(); - if (type != null) { - return getType().getName(); - } - else { - return null; - } + return (type != null ? ClassUtils.getQualifiedName(type) : null); } /** @@ -396,17 +404,16 @@ public class TypeDescriptor { public String toString() { if (this == TypeDescriptor.NULL) { - return "[TypeDescriptor.NULL]"; + return "TypeDescriptor.NULL"; } else { StringBuilder builder = new StringBuilder(); - builder.append("[TypeDescriptor "); + builder.append("TypeDescriptor "); Annotation[] anns = getAnnotations(); for (Annotation ann : anns) { builder.append("@").append(ann.annotationType().getName()).append(' '); } - builder.append(getType().getName()); - builder.append("]"); + builder.append(ClassUtils.getQualifiedName(getType())); return builder.toString(); } }