diff --git a/org.springframework.context/src/main/java/org/springframework/ui/binding/Binding.java b/org.springframework.context/src/main/java/org/springframework/ui/binding/Binding.java index 0e3b697f9a67d641b6aff1f948e3ddc753de2dd7..51d2d803a10a86c6cb170f6ae3b10a028ac96104 100644 --- a/org.springframework.context/src/main/java/org/springframework/ui/binding/Binding.java +++ b/org.springframework.context/src/main/java/org/springframework/ui/binding/Binding.java @@ -15,6 +15,7 @@ */ package org.springframework.ui.binding; +import org.springframework.core.convert.TypeDescriptor; import org.springframework.ui.alert.Alert; import org.springframework.ui.alert.Severity; @@ -26,7 +27,7 @@ import org.springframework.ui.alert.Severity; public interface Binding { /** - * The value to display in the UI. + * The model value formatted for display in a single field in the UI. * Is the formatted model value if {@link BindingStatus#CLEAN} or {@link BindingStatus#COMMITTED}. * Is the formatted buffered value if {@link BindingStatus#DIRTY} or {@link BindingStatus#COMMIT_FAILURE}. */ @@ -129,6 +130,11 @@ public interface Binding { */ Class getValueType(); + /** + * The model value type descriptor. + */ + TypeDescriptor getValueTypeDescriptor(); + /** * Set the model value. */ 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 2350aed32ea364926351c5c793bf08b22e742840..324501feec494695fbbb35c56dbc85f9ed8fbfd6 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 @@ -9,6 +9,7 @@ import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import java.text.ParseException; +import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; @@ -47,16 +48,24 @@ public class PropertyBinding implements Binding { private BindingStatus status; + private Map listElementBindings; + + private Class elementType; + public PropertyBinding(PropertyDescriptor property, Object object, BindingContext bindingContext) { this.property = property; this.object = object; this.bindingContext = bindingContext; buffer = new ValueBuffer(getModel()); status = BindingStatus.CLEAN; + if (isList()) { + listElementBindings = new HashMap(); + elementType = GenericCollectionTypeResolver.getCollectionReturnType(property.getReadMethod()); + } } public String getRenderValue() { - return format(getValue(), bindingContext.getFormatter()); + return format(getValue(), getFormatter()); } public Object getValue() { @@ -88,7 +97,7 @@ public class PropertyBinding implements Binding { assertEnabled(); if (sourceValue instanceof String) { try { - Object parsed = bindingContext.getFormatter().parse((String) sourceValue, getLocale()); + Object parsed = getFormatter().parse((String) sourceValue, getLocale()); buffer.setValue(coerseToValueType(parsed)); sourceValue = null; status = BindingStatus.DIRTY; @@ -123,13 +132,23 @@ public class PropertyBinding implements Binding { if (status != BindingStatus.INVALID_SOURCE_VALUE) { try { buffer.setValue(coerseToValueType(parsed)); + sourceValue = null; + status = BindingStatus.DIRTY; } catch (ConversionFailedException e) { this.sourceValue = sourceValue; invalidSourceValueCause = e; status = BindingStatus.INVALID_SOURCE_VALUE; } + } + } else { + try { + buffer.setValue(coerseToValueType(sourceValue)); sourceValue = null; status = BindingStatus.DIRTY; + } catch (ConversionFailedException e) { + this.sourceValue = sourceValue; + invalidSourceValueCause = e; + status = BindingStatus.INVALID_SOURCE_VALUE; } } } @@ -179,19 +198,19 @@ public class PropertyBinding implements Binding { } }; } else if (status == BindingStatus.COMMIT_FAILURE) { - return new AbstractAlert() { - public String getCode() { - return "internalError"; - } + return new AbstractAlert() { + public String getCode() { + return "internalError"; + } - public String getMessage() { - return "Internal error occurred; message = [" + buffer.getFlushException().getMessage() + "]"; - } + public String getMessage() { + return "Internal error occurred; message = [" + buffer.getFlushException().getMessage() + "]"; + } - public Severity getSeverity() { - return Severity.FATAL; - } - }; + public Severity getSeverity() { + return Severity.FATAL; + } + }; } else if (status == BindingStatus.COMMITTED) { return new AbstractAlert() { public String getCode() { @@ -248,6 +267,10 @@ public class PropertyBinding implements Binding { public Class getValueType() { return property.getPropertyType(); } + + public TypeDescriptor getValueTypeDescriptor() { + return new TypeDescriptor(new MethodParameter(property.getReadMethod(), -1)); + } public void setValue(Object value) { ReflectionUtils.invokeMethod(property.getWriteMethod(), object, value); @@ -264,17 +287,21 @@ public class PropertyBinding implements Binding { } public boolean isList() { - return getModel().getValueType().isArray() || List.class.isAssignableFrom(getModel().getValueType()); + return List.class.isAssignableFrom(getValueType()); } public Binding getListElementBinding(int index) { assertListProperty(); - //return new IndexedBinding(index, (List) getValue(), getCollectionTypeDescriptor(), typeConverter); - return null; + ListElementBinding binding = listElementBindings.get(index); + if (binding == null) { + binding = new ListElementBinding(index); + listElementBindings.put(index, binding); + } + return binding; } public boolean isMap() { - return Map.class.isAssignableFrom(getModel().getValueType()); + return Map.class.isAssignableFrom(getValueType()); } public Binding getMapValueBinding(Object key) { @@ -299,7 +326,7 @@ public class PropertyBinding implements Binding { } return format(value, formatter); } - + private String format(Object value, Formatter formatter) { Class formattedType = getFormattedObjectType(formatter.getClass()); value = bindingContext.getTypeConverter().convert(value, formattedType); @@ -308,6 +335,10 @@ public class PropertyBinding implements Binding { // internal helpers + protected Formatter getFormatter() { + return bindingContext.getFormatter(); + } + private Locale getLocale() { return LocaleContextHolder.getLocale(); } @@ -354,7 +385,7 @@ public class PropertyBinding implements Binding { } private Object coerseToValueType(Object parsed) { - TypeDescriptor targetType = new TypeDescriptor(new MethodParameter(property.getWriteMethod(), 0)); + TypeDescriptor targetType = getModel().getValueTypeDescriptor(); TypeConverter converter = bindingContext.getTypeConverter(); if (parsed != null && converter.canConvert(parsed.getClass(), targetType)) { return converter.convert(parsed, targetType); @@ -363,12 +394,6 @@ public class PropertyBinding implements Binding { } } - @SuppressWarnings("unused") - private CollectionTypeDescriptor getCollectionTypeDescriptor() { - Class elementType = GenericCollectionTypeResolver.getCollectionReturnType(property.getReadMethod()); - return new CollectionTypeDescriptor(getModel().getValueType(), elementType); - } - private void assertScalarProperty() { if (isList()) { throw new IllegalArgumentException("Is a Collection but should be a scalar"); @@ -411,5 +436,72 @@ public class PropertyBinding implements Binding { return getCode() + " - " + getMessage(); } } + + class ListElementBinding extends PropertyBinding { + + private int index; + + 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() { + return getList().get(index); + } + + public Class getValueType() { + if (elementType != null) { + return elementType; + } else { + return getValue().getClass(); + } + } + + public TypeDescriptor getValueTypeDescriptor() { + return TypeDescriptor.valueOf(getValueType()); + } + + public void setValue(Object value) { + getList().set(index, value); + } + }; + } + + // 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/main/java/org/springframework/ui/binding/support/PropertyPath.java b/org.springframework.context/src/main/java/org/springframework/ui/binding/support/PropertyPath.java index 48af4193ce6a7a920cf616fc4adb588ca7de506b..d34f3fa1016506d4fbf79ac597fc41a5a72296d8 100644 --- a/org.springframework.context/src/main/java/org/springframework/ui/binding/support/PropertyPath.java +++ b/org.springframework.context/src/main/java/org/springframework/ui/binding/support/PropertyPath.java @@ -19,9 +19,11 @@ public class PropertyPath implements Iterable { props = new String[] { propertyPath }; } for (String prop : props) { - if (prop.startsWith("[")) { - int end = prop.indexOf(']'); - String index = prop.substring(0, end); + if (prop.contains("[")) { + int start = prop.indexOf('['); + int end = prop.indexOf(']', start); + String index = prop.substring(start + 1, end); + elements.add(new PropertyPathElement(prop.substring(0, start), true)); elements.add(new PropertyPathElement(index, true)); } else { elements.add(new PropertyPathElement(prop, false)); @@ -35,7 +37,7 @@ public class PropertyPath implements Iterable { public List getNestedElements() { if (elements.size() > 1) { - return elements.subList(1, elements.size() - 1); + return elements.subList(1, elements.size()); } else { return Collections.emptyList(); } 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 ee026ca0385a4a2e7bd7fe4f2aaa54fd08bd4e22..d9574395625b9e65a6cc473f0a562cffde17943d 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 @@ -7,6 +7,7 @@ import static org.junit.Assert.assertTrue; import java.math.BigDecimal; import java.text.ParseException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.LinkedHashMap; @@ -210,18 +211,19 @@ public class GenericBinderTests { assertEquals(FooEnum.BOOP, value.get(2)); } - /* @Test public void getBindingMultiValuedIndexAccess() { - binder.addBinding("foos"); bean.setFoos(Arrays.asList(new FooEnum[] { FooEnum.BAR })); Binding b = binder.getBinding("foos[0]"); - assertFalse(b.isIndexable()); - assertEquals("BAR", b.getValue()); - b.setValue("BAZ"); - assertEquals("BAZ", b.getValue()); + assertFalse(b.isList()); + assertEquals(FooEnum.BAR, b.getValue()); + assertEquals("BAR", b.getRenderValue()); + b.applySourceValue("BAZ"); + assertEquals("BAZ", b.getRenderValue()); + assertEquals(FooEnum.BAZ, b.getValue()); } + /* @Test public void getBindingMultiValuedTypeConversionFailure() { binder.addBinding("foos");