diff --git a/org.springframework.context/src/main/java/org/springframework/ui/binding/support/GenericBinder.java b/org.springframework.context/src/main/java/org/springframework/ui/binding/support/GenericBinder.java index 3374ae2a501..4bf83426398 100644 --- a/org.springframework.context/src/main/java/org/springframework/ui/binding/support/GenericBinder.java +++ b/org.springframework.context/src/main/java/org/springframework/ui/binding/support/GenericBinder.java @@ -240,6 +240,7 @@ public class GenericBinder implements Binder { public GenericBindingRule(String property, Class modelClass) { this.modelClass = modelClass; this.property = findPropertyDescriptor(property); + nestedBindingRules = new HashMap(); } // implementing BindingContext diff --git a/org.springframework.context/src/main/java/org/springframework/ui/binding/support/PropertyBinding.java b/org.springframework.context/src/main/java/org/springframework/ui/binding/support/PropertyBinding.java index 324501feec4..0e85b41c1ac 100644 --- a/org.springframework.context/src/main/java/org/springframework/ui/binding/support/PropertyBinding.java +++ b/org.springframework.context/src/main/java/org/springframework/ui/binding/support/PropertyBinding.java @@ -1,6 +1,3 @@ -/** - * - */ package org.springframework.ui.binding.support; import java.beans.PropertyDescriptor; @@ -9,6 +6,7 @@ import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import java.text.ParseException; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Locale; @@ -51,7 +49,7 @@ public class PropertyBinding implements Binding { private Map listElementBindings; private Class elementType; - + public PropertyBinding(PropertyDescriptor property, Object object, BindingContext bindingContext) { this.property = property; this.object = object; @@ -148,7 +146,7 @@ public class PropertyBinding implements Binding { } catch (ConversionFailedException e) { this.sourceValue = sourceValue; invalidSourceValueCause = e; - status = BindingStatus.INVALID_SOURCE_VALUE; + status = BindingStatus.INVALID_SOURCE_VALUE; } } } @@ -267,7 +265,7 @@ public class PropertyBinding implements Binding { public Class getValueType() { return property.getPropertyType(); } - + public TypeDescriptor getValueTypeDescriptor() { return new TypeDescriptor(new MethodParameter(property.getReadMethod(), -1)); } @@ -281,7 +279,7 @@ public class PropertyBinding implements Binding { public Binding getBinding(String property) { assertScalarProperty(); if (getValue() == null) { - createValue(); + getModel().setValue(newValue(getValueType())); } return bindingContext.getBinding(property, getValue()); } @@ -292,6 +290,10 @@ public class PropertyBinding implements Binding { public Binding getListElementBinding(int index) { assertListProperty(); + if (index < 0) { + throw new IllegalArgumentException("Invalid index " + index); + } + growListIfNecessary(index); ListElementBinding binding = listElementBindings.get(index); if (binding == null) { binding = new ListElementBinding(index); @@ -327,35 +329,24 @@ public class PropertyBinding implements Binding { return format(value, formatter); } + // subclassing hooks + + protected Formatter getFormatter() { + return bindingContext.getFormatter(); + } + + // internal helpers + private String format(Object value, Formatter formatter) { Class formattedType = getFormattedObjectType(formatter.getClass()); value = bindingContext.getTypeConverter().convert(value, formattedType); return formatter.format(value, getLocale()); } - // internal helpers - - protected Formatter getFormatter() { - return bindingContext.getFormatter(); - } - private Locale getLocale() { return LocaleContextHolder.getLocale(); } - - private void createValue() { - try { - Object value = getModel().getValueType().newInstance(); - getModel().setValue(value); - } catch (InstantiationException e) { - throw new IllegalStateException("Could not lazily instantiate object of type [" - + getModel().getValueType().getName() + "] to access property" + property, e); - } catch (IllegalAccessException e) { - throw new IllegalStateException("Could not lazily instantiate object of type [" - + getModel().getValueType().getName() + "] to access property" + property, e); - } - } - + private Class getFormattedObjectType(Class formatterClass) { Class classToIntrospect = formatterClass; while (classToIntrospect != null) { @@ -431,12 +422,43 @@ public class PropertyBinding implements Binding { return property.getWriteMethod() != null; } + private void growListIfNecessary(int index) { + List list = (List) getValue(); + if (list == null) { + getModel().setValue(newListValue(getValueType())); + list = (List) getValue(); + } + if (index >= list.size()) { + for (int i = list.size(); i <= index; i++) { + list.add(newValue(elementType)); + } + } + } + + private Object newListValue(Class type) { + if (type.isInterface()) { + return newValue(ArrayList.class); + } else { + return newValue(type); + } + } + + private Object newValue(Class type) { + try { + return type.newInstance(); + } catch (InstantiationException e) { + throw new IllegalStateException("Could not instantiate element of type [" + type.getName() + "]", e); + } catch (IllegalAccessException e) { + throw new IllegalStateException("Could not instantiate element of type [" + type.getName() + "]", e); + } + } + static abstract class AbstractAlert implements Alert { public String toString() { return getCode() + " - " + getMessage(); } } - + class ListElementBinding extends PropertyBinding { private int index; @@ -444,13 +466,12 @@ public class PropertyBinding implements Binding { public ListElementBinding(int index) { super(property, object, bindingContext); this.index = index; - growListIfNecessary(); } protected Formatter getFormatter() { return bindingContext.getElementFormatter(); } - + public Model getModel() { return new Model() { public Object getValue() { @@ -464,7 +485,7 @@ public class PropertyBinding implements Binding { return getValue().getClass(); } } - + public TypeDescriptor getValueTypeDescriptor() { return TypeDescriptor.valueOf(getValueType()); } @@ -474,34 +495,12 @@ public class PropertyBinding implements Binding { } }; } - + // internal helpers - - private void growListIfNecessary() { - if (index >= getList().size()) { - for (int i = getList().size(); i <= index; i++) { - addValue(); - } - } - } - + private List getList() { return (List) PropertyBinding.this.getValue(); } - - private void addValue() { - try { - Object value = getValueType().newInstance(); - getList().add(value); - } catch (InstantiationException e) { - throw new IllegalStateException("Could not lazily instantiate model of type [" - + getValueType().getName() + "] to grow List", e); - } catch (IllegalAccessException e) { - throw new IllegalStateException("Could not lazily instantiate model of type [" - + getValueType().getName() + "] to grow List", e); - } - } - } } \ No newline at end of file diff --git a/org.springframework.context/src/test/java/org/springframework/ui/binding/support/GenericBinderTests.java b/org.springframework.context/src/test/java/org/springframework/ui/binding/support/GenericBinderTests.java index d9574395625..84e88f47e6e 100644 --- a/org.springframework.context/src/test/java/org/springframework/ui/binding/support/GenericBinderTests.java +++ b/org.springframework.context/src/test/java/org/springframework/ui/binding/support/GenericBinderTests.java @@ -15,8 +15,11 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import junit.framework.Assert; + import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.ui.binding.Binding; @@ -50,11 +53,6 @@ public class GenericBinderTests { LocaleContextHolder.setLocale(null); } - @Test - public void testPlaceholder() { - - } - @Test public void bindSingleValuesWithDefaultTypeConverterConversion() { GenericBinder binder = new GenericBinder(bean); @@ -192,6 +190,7 @@ public class GenericBinderTests { assertEquals("Please enter an integer in format ### for the integer field; you entered bogus", b.getStatusAlert().getMessage()); } + @SuppressWarnings("unchecked") @Test public void getBindingMultiValued() { Binding b = binder.getBinding("foos"); @@ -223,25 +222,22 @@ public class GenericBinderTests { assertEquals(FooEnum.BAZ, b.getValue()); } - /* @Test public void getBindingMultiValuedTypeConversionFailure() { - binder.addBinding("foos"); Binding b = binder.getBinding("foos"); - assertTrue(b.isIndexable()); - assertEquals(0, b.getCollectionValues().length); - BindingResult result = b.setValue(new String[] { "BAR", "BOGUS", "BOOP" }); - assertTrue(result.isFailure()); - assertEquals("conversionFailed", result.getAlert().getCode()); + assertTrue(b.isList()); + assertEquals(null, b.getValue()); + b.applySourceValue(new String[] { "BAR", "BOGUS", "BOOP" }); + assertEquals(BindingStatus.INVALID_SOURCE_VALUE, b.getStatus()); + assertEquals("typeMismatch", b.getStatusAlert().getCode()); } @Test public void bindToList() { - binder.addBinding("addresses"); Map values = new LinkedHashMap(); values.put("addresses", new String[] { "4655 Macy Lane:Melbourne:FL:35452", "1234 Rostock Circle:Palm Bay:FL:32901", "1977 Bel Aire Estates:Coker:AL:12345" }); binder.bind(values); - Assert.assertEquals(3, bean.addresses.size()); + assertEquals(3, bean.addresses.size()); assertEquals("4655 Macy Lane", bean.addresses.get(0).street); assertEquals("Melbourne", bean.addresses.get(0).city); assertEquals("FL", bean.addresses.get(0).state); @@ -250,7 +246,6 @@ public class GenericBinderTests { @Test public void bindToListElements() { - binder.addBinding("addresses"); Map values = new LinkedHashMap(); values.put("addresses[0]", "4655 Macy Lane:Melbourne:FL:35452"); values.put("addresses[1]", "1234 Rostock Circle:Palm Bay:FL:32901"); @@ -265,11 +260,12 @@ public class GenericBinderTests { @Test public void bindToListSingleString() { - binder.addBinding("addresses"); - binder.registerFormatter(new GenericCollectionPropertyType(List.class, Address.class), new AddressListFormatter()); + GenericFormatterRegistry formatterRegistry = new GenericFormatterRegistry(); + formatterRegistry.add(new GenericCollectionPropertyType(List.class, Address.class), new AddressListFormatter()); + binder.setFormatterRegistry(formatterRegistry); Map values = new LinkedHashMap(); values.put("addresses", "4655 Macy Lane:Melbourne:FL:35452,1234 Rostock Circle:Palm Bay:FL:32901,1977 Bel Aire Estates:Coker:AL:12345"); - BindingResults results = binder.bind(values); + binder.bind(values); Assert.assertEquals(3, bean.addresses.size()); assertEquals("4655 Macy Lane", bean.addresses.get(0).street); assertEquals("Melbourne", bean.addresses.get(0).city); @@ -288,8 +284,6 @@ public class GenericBinderTests { @Test @Ignore public void bindToListSingleStringNoListFormatter() { - binder.addBinding("addresses"); - //binder.registerFormatter(new GenericCollectionPropertyType(List.class, Address.class), new AddressListFormatter()); Map values = new LinkedHashMap(); values.put("addresses", "4655 Macy Lane:Melbourne:FL:35452,1234 Rostock Circle:Palm Bay:FL:32901,1977 Bel Aire Estates:Coker:AL:12345"); BindingResults results = binder.bind(values); @@ -310,8 +304,9 @@ public class GenericBinderTests { @Test public void getListAsSingleString() { - binder.addBinding("addresses"); - binder.registerFormatter(new GenericCollectionPropertyType(List.class, Address.class), new AddressListFormatter()); + GenericFormatterRegistry formatterRegistry = new GenericFormatterRegistry(); + formatterRegistry.add(new GenericCollectionPropertyType(List.class, Address.class), new AddressListFormatter()); + binder.setFormatterRegistry(formatterRegistry); Address address1 = new Address(); address1.setStreet("s1"); address1.setCity("c1"); @@ -326,14 +321,13 @@ public class GenericBinderTests { addresses.add(address1); addresses.add(address2); bean.addresses = addresses; - String value = binder.getBinding("addresses").getValue(); + String value = binder.getBinding("addresses").getRenderValue(); assertEquals("s1:c1:st1:z1,s2:c2:st2:z2,", value); } @Test @Ignore public void getListAsSingleStringNoFormatter() { - binder.addBinding("addresses"); Address address1 = new Address(); address1.setStreet("s1"); address1.setCity("c1"); @@ -348,26 +342,17 @@ public class GenericBinderTests { addresses.add(address1); addresses.add(address2); bean.addresses = addresses; - String value = binder.getBinding("addresses").getValue(); + String value = binder.getBinding("addresses").getRenderValue(); assertEquals("s1:c1:st1:z1,s2:c2:st2:z2,", value); } @Test + @Ignore public void bindToListHandleNullValueInNestedPath() { - binder.addBinding("addresses.street"); - binder.addBinding("addresses.city"); - binder.addBinding("addresses.state"); - binder.addBinding("addresses.zip"); - Map values = new LinkedHashMap(); - - // EL configured with some options from SpelExpressionParserConfiguration: - // (see where Binder creates the parser) // - new addresses List is created if null // - new entries automatically built if List is currently too short - all new entries // are new instances of the type of the list entry, they are not null. - // not currently doing anything for maps or arrays - values.put("addresses[0].street", "4655 Macy Lane"); values.put("addresses[0].city", "Melbourne"); values.put("addresses[0].state", "FL"); @@ -386,6 +371,7 @@ public class GenericBinderTests { values.put("addresses[5].zip", "32901"); BindingResults results = binder.bind(values); + System.out.println(results); Assert.assertEquals(6, bean.addresses.size()); Assert.assertEquals("Palm Bay", bean.addresses.get(1).city); Assert.assertNotNull(bean.addresses.get(2)); @@ -393,8 +379,8 @@ public class GenericBinderTests { } @Test + @Ignore public void bindToMap() { - binder.addBinding("favoriteFoodsByGroup"); Map values = new LinkedHashMap(); values.put("favoriteFoodsByGroup", new String[] { "DAIRY=Milk", "FRUIT=Peaches", "MEAT=Ham" }); BindingResults results = binder.bind(values); @@ -405,8 +391,8 @@ public class GenericBinderTests { } @Test + @Ignore public void bindToMapElements() { - binder.addBinding("favoriteFoodsByGroup"); Map values = new LinkedHashMap(); values.put("favoriteFoodsByGroup['DAIRY']", "Milk"); values.put("favoriteFoodsByGroup['FRUIT']", "Peaches"); @@ -420,7 +406,6 @@ public class GenericBinderTests { @Test public void bindToMapSingleString() { - binder.addBinding("favoriteFoodsByGroup"); Map values = new LinkedHashMap(); values.put("favoriteFoodsByGroup", "DAIRY=Milk FRUIT=Peaches MEAT=Ham"); BindingResults results = binder.bind(values); @@ -433,22 +418,17 @@ public class GenericBinderTests { @Test @Ignore public void getMapAsSingleString() { - binder.addBinding("favoriteFoodsByGroup"); Map foods = new LinkedHashMap(); foods.put(FoodGroup.DAIRY, "Milk"); foods.put(FoodGroup.FRUIT, "Peaches"); foods.put(FoodGroup.MEAT, "Ham"); bean.favoriteFoodsByGroup = foods; - String value = binder.getBinding("favoriteFoodsByGroup").getValue(); + String value = binder.getBinding("favoriteFoodsByGroup").getRenderValue(); assertEquals("DAIRY=Milk FRUIT=Peaches MEAT=Ham", value); } @Test public void bindToNullObjectPath() { - binder.addBinding("primaryAddress.street"); - binder.addBinding("primaryAddress.city"); - binder.addBinding("primaryAddress.state"); - binder.addBinding("primaryAddress.zip"); Map values = new LinkedHashMap(); values.put("primaryAddress.city", "Melbourne"); binder.bind(values); @@ -457,11 +437,10 @@ public class GenericBinderTests { @Test public void formatPossibleValue() { - binder.addBinding("currency").formatWith(new CurrencyFormatter()); + binder.bindingRule("currency").formatWith(new CurrencyFormatter()); Binding b = binder.getBinding("currency"); - assertEquals("$5.00", b.format(new BigDecimal("5"))); + assertEquals("$5.00", b.formatValue(new BigDecimal("5"))); } - */ public static enum FooEnum { BAR, BAZ, BOOP; diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/support/ConversionUtils.java b/org.springframework.core/src/main/java/org/springframework/core/convert/support/ConversionUtils.java index 31ad92f8995..85666d1f534 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/support/ConversionUtils.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/support/ConversionUtils.java @@ -32,7 +32,7 @@ import java.util.TreeSet; * @author Keith Donald * @since 3.0 */ -class ConversionUtils { +public class ConversionUtils { /** * Get the java.util.Collection implementation class that should be used for the given target collection type.