From 4c9731d57209a5c1d183731142fd44053b37cc7d Mon Sep 17 00:00:00 2001 From: Keith Donald Date: Thu, 6 Jan 2011 21:59:34 +0000 Subject: [PATCH] added forNestedType(MethodParameter) for resolution of nested parameter types for collection, array, and map parameter types --- .../beans/BeanWrapperImpl.java | 27 ++++++--- .../format/number/NumberFormattingTests.java | 19 +++++-- .../validation/DataBinderTests.java | 2 +- .../core/convert/TypeDescriptor.java | 57 +++++++++++++------ .../support/PropertyTypeDescriptor.java | 17 +++++- .../spel/support/ReflectionHelper.java | 4 +- 6 files changed, 91 insertions(+), 35 deletions(-) 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 8729b284752..bbf65a0216d 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 @@ -364,14 +364,27 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra public TypeDescriptor getPropertyTypeDescriptor(String propertyName) throws BeansException { try { - String actualPropertyName = PropertyAccessorUtils.getPropertyName(propertyName); - PropertyDescriptor pd = getPropertyDescriptorInternal(actualPropertyName); + PropertyTokenHolder tokens = getPropertyNameTokens(propertyName); + PropertyDescriptor pd = getPropertyDescriptorInternal(tokens.actualName); if (pd != null) { - if (pd.getReadMethod() != null) { - return new PropertyTypeDescriptor(new MethodParameter(pd.getReadMethod(), -1), pd); - } - else if (pd.getWriteMethod() != null) { - return new PropertyTypeDescriptor(BeanUtils.getWriteMethodParameter(pd), pd); + if (tokens.keys != null) { + if (pd.getReadMethod() != null) { + return PropertyTypeDescriptor.forNestedType(new MethodParameter(pd.getReadMethod(), -1, tokens.keys.length), pd); + } + else if (pd.getWriteMethod() != null) { + MethodParameter methodParameter = new MethodParameter(BeanUtils.getWriteMethodParameter(pd)); + for (int i = 0; i < tokens.keys.length; i++) { + methodParameter.increaseNestingLevel(); + } + return PropertyTypeDescriptor.forNestedType(methodParameter, pd); + } + } else { + if (pd.getReadMethod() != null) { + return new PropertyTypeDescriptor(new MethodParameter(pd.getReadMethod(), -1), pd); + } + else if (pd.getWriteMethod() != null) { + return new PropertyTypeDescriptor(BeanUtils.getWriteMethodParameter(pd), pd); + } } } } 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 5a809406805..26c86fd149e 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 @@ -116,7 +116,6 @@ public class NumberFormattingTests { } @Test - @Ignore public void testPatternArrayFormatting() { MutablePropertyValues propertyValues = new MutablePropertyValues(); propertyValues.add("patternArray", new String[] {"1,25.00", "2,35.00"}); @@ -135,7 +134,6 @@ public class NumberFormattingTests { } @Test - @Ignore public void testPatternListFormatting() { MutablePropertyValues propertyValues = new MutablePropertyValues(); propertyValues.add("patternList", new String[] {"1,25.00", "2,35.00"}); @@ -154,15 +152,24 @@ public class NumberFormattingTests { } @Test - @Ignore - public void testPatternList2Formatting() { + public void testPatternList2FormattingListElement() { MutablePropertyValues propertyValues = new MutablePropertyValues(); propertyValues.add("patternList2[0]", "1,25.00"); propertyValues.add("patternList2[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]")); + assertEquals("1,25.00", binder.getBindingResult().getFieldValue("patternList2[0]")); + assertEquals("2,35.00", binder.getBindingResult().getFieldValue("patternList2[1]")); + } + + @Test + public void testPatternList2FormattingList() { + MutablePropertyValues propertyValues = new MutablePropertyValues(); + propertyValues.add("patternList2[0]", "1,25.00"); + propertyValues.add("patternList2[1]", "2,35.00"); + binder.bind(propertyValues); + assertEquals(0, binder.getBindingResult().getErrorCount()); + assertEquals("1,25.00,2,35.00", binder.getBindingResult().getFieldValue("patternList2")); } diff --git a/org.springframework.context/src/test/java/org/springframework/validation/DataBinderTests.java b/org.springframework.context/src/test/java/org/springframework/validation/DataBinderTests.java index d1e29574afd..526f5a2ba3a 100644 --- a/org.springframework.context/src/test/java/org/springframework/validation/DataBinderTests.java +++ b/org.springframework.context/src/test/java/org/springframework/validation/DataBinderTests.java @@ -382,7 +382,7 @@ public class DataBinderTests extends TestCase { try { binder.bind(pvs); assertEquals(new Integer(1), tb.getIntegerList().get(0)); - // TODO add back assertEquals("1", binder.getBindingResult().getFieldValue("integerList[0]")); + assertEquals("1", binder.getBindingResult().getFieldValue("integerList[0]")); } finally { LocaleContextHolder.resetLocaleContext(); 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 965c18187f3..95b1e4a1158 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 @@ -105,10 +105,26 @@ public class TypeDescriptor { this.type = field.getType(); this.field = field; } + + /** + * Create a new type descriptor for the given class. + * Use this to instruct the conversion system to convert to an object to a specific target type, when no type location such as a method parameter or field is available to provide additional conversion context. + * Generally prefer use of {@link #forObject(Object)} for constructing source type descriptors for source objects. + * @param type the class + * @return the type descriptor + */ + public static TypeDescriptor valueOf(Class type) { + if (type == null) { + return NULL; + } + TypeDescriptor desc = typeDescriptorCache.get(type); + return (desc != null ? desc : new TypeDescriptor(type)); + } /** - * Create a new type descriptor for object. + * Create a new type descriptor for an object. * Use this factory method to introspect a source object's type before asking the conversion system to convert it to some another type. + * Builds in population of nested type descriptors for collection and map objects through object introspection. * If the object is null, returns {@link TypeDescriptor#NULL}. * If the object is not a collection, simply calls {@link #valueOf(Class)}. * If the object is a collection, this factory method will derive the element type(s) by introspecting the collection. @@ -132,6 +148,16 @@ public class TypeDescriptor { } } + /** + * Create a new type descriptor for a nested type declared on an array, collection, or map-based method parameter. + * Use this factory method when you've resolved a nested source object such as a collection element or map value and wish to have it converted. + * @param methodParameter the method parameter declaring the collection or map + * @return the nested type descriptor + */ + public static TypeDescriptor forNestedType(MethodParameter methodParameter) { + return new TypeDescriptor(resolveNestedType(methodParameter), methodParameter); + } + /** * Create a new type descriptor for a nested type declared on an array, collection, or map-based method parameter. * Use this factory method when you've resolved a nested source object such as a collection element or map value and wish to have it converted. @@ -142,22 +168,7 @@ public class TypeDescriptor { public static TypeDescriptor forNestedType(Class nestedType, MethodParameter methodParameter) { return new TypeDescriptor(nestedType, methodParameter); } - - /** - * Create a new type descriptor for the given class. - * Use this to instruct the conversion system to convert to an object to a specific target type, when no type location such as a method parameter or field is available. - * Generally prefer use of {@link #forObject(Object)} for constructing source type descriptors for source objects. - * @param type the class - * @return the type descriptor - */ - public static TypeDescriptor valueOf(Class type) { - if (type == null) { - return NULL; - } - TypeDescriptor desc = typeDescriptorCache.get(type); - return (desc != null ? desc : new TypeDescriptor(type)); - } - + /** * Determine the declared (non-generic) type of the wrapped parameter/field. * @return the declared type, or null if this is {@link TypeDescriptor#NULL} @@ -436,6 +447,18 @@ public class TypeDescriptor { return new TypeDescriptor(nestedType, nested); } + protected static Class resolveNestedType(MethodParameter methodParameter) { + if (Collection.class.isAssignableFrom(methodParameter.getParameterType())) { + return GenericCollectionTypeResolver.getCollectionParameterType(methodParameter); + } else if (Map.class.isAssignableFrom(methodParameter.getParameterType())) { + return GenericCollectionTypeResolver.getMapValueParameterType(methodParameter); + } else if (methodParameter.getParameterType().isArray()) { + return methodParameter.getParameterType().getComponentType(); + } else { + throw new IllegalStateException("Not a collection, map, or array method parameter type " + methodParameter.getParameterType()); + } + } + // internal helpers private TypeDescriptor resolveElementTypeDescriptor() { diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/support/PropertyTypeDescriptor.java b/org.springframework.core/src/main/java/org/springframework/core/convert/support/PropertyTypeDescriptor.java index be2d111af62..1c250547680 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/support/PropertyTypeDescriptor.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/support/PropertyTypeDescriptor.java @@ -20,9 +20,11 @@ import java.beans.PropertyDescriptor; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.Method; +import java.util.Collection; import java.util.LinkedHashMap; import java.util.Map; +import org.springframework.core.GenericCollectionTypeResolver; import org.springframework.core.MethodParameter; import org.springframework.core.convert.TypeDescriptor; import org.springframework.util.ReflectionUtils; @@ -51,18 +53,29 @@ public class PropertyTypeDescriptor extends TypeDescriptor { this.propertyDescriptor = propertyDescriptor; } + /** + * Create a new type descriptor for a nested type declared on an array, collection, or map-based property. + * Use this factory method when you've resolved a nested source object such as a collection element or map value and wish to have it converted. + * Builds in protection for increasing the nesting level of the provided MethodParameter if the nestedType is itself a collection. + * @param methodParameter the method parameter + * @return the property descriptor + */ + public static PropertyTypeDescriptor forNestedType(MethodParameter methodParameter, PropertyDescriptor propertyDescriptor) { + return forNestedType(resolveNestedType(methodParameter), methodParameter, propertyDescriptor); + } + /** * Create a new type descriptor for a nested type declared on an array, collection, or map-based property. * Use this factory method when you've resolved a nested source object such as a collection element or map value and wish to have it converted. * Builds in protection for increasing the nesting level of the provided MethodParameter if the nestedType is itself a collection. * @param nestedType the nested type - * @param parentMethodParameter the method parameter + * @param methodParameter the method parameter * @return the property descriptor */ public static PropertyTypeDescriptor forNestedType(Class nestedType, MethodParameter methodParameter, PropertyDescriptor propertyDescriptor) { return new PropertyTypeDescriptor(nestedType, methodParameter, propertyDescriptor); } - + /** * Return the underlying PropertyDescriptor. */ diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/ReflectionHelper.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/ReflectionHelper.java index a9e9a08a2e3..f37b0d1211b 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/ReflectionHelper.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/ReflectionHelper.java @@ -236,7 +236,7 @@ public class ReflectionHelper { TypeDescriptor targetType; if (varargsPosition != null && argPosition >= varargsPosition) { MethodParameter methodParam = MethodParameter.forMethodOrConstructor(methodOrCtor, varargsPosition); - targetType = TypeDescriptor.forNestedType(methodParam.getParameterType().getComponentType(), methodParam); + targetType = TypeDescriptor.forNestedType(methodParam); } else { targetType = new TypeDescriptor(MethodParameter.forMethodOrConstructor(methodOrCtor, argPosition)); @@ -268,7 +268,7 @@ public class ReflectionHelper { TypeDescriptor targetType; if (varargsPosition != null && argPosition >= varargsPosition) { MethodParameter methodParam = new MethodParameter(method, varargsPosition); - targetType = TypeDescriptor.forNestedType(methodParam.getParameterType().getComponentType(), methodParam); + targetType = TypeDescriptor.forNestedType(methodParam); } else { targetType = new TypeDescriptor(new MethodParameter(method, argPosition));