list element binding

This commit is contained in:
Keith Donald 2009-07-20 20:34:02 +00:00
parent 2da1bb8607
commit 603ffe80c8
4 changed files with 138 additions and 36 deletions

View File

@ -15,6 +15,7 @@
*/ */
package org.springframework.ui.binding; package org.springframework.ui.binding;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.ui.alert.Alert; import org.springframework.ui.alert.Alert;
import org.springframework.ui.alert.Severity; import org.springframework.ui.alert.Severity;
@ -26,7 +27,7 @@ import org.springframework.ui.alert.Severity;
public interface Binding { 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 model value if {@link BindingStatus#CLEAN} or {@link BindingStatus#COMMITTED}.
* Is the formatted buffered value if {@link BindingStatus#DIRTY} or {@link BindingStatus#COMMIT_FAILURE}. * Is the formatted buffered value if {@link BindingStatus#DIRTY} or {@link BindingStatus#COMMIT_FAILURE}.
*/ */
@ -129,6 +130,11 @@ public interface Binding {
*/ */
Class<?> getValueType(); Class<?> getValueType();
/**
* The model value type descriptor.
*/
TypeDescriptor<?> getValueTypeDescriptor();
/** /**
* Set the model value. * Set the model value.
*/ */

View File

@ -9,6 +9,7 @@ import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable; import java.lang.reflect.TypeVariable;
import java.text.ParseException; import java.text.ParseException;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
@ -47,16 +48,24 @@ public class PropertyBinding implements Binding {
private BindingStatus status; private BindingStatus status;
private Map<Integer, ListElementBinding> listElementBindings;
private Class<?> elementType;
public PropertyBinding(PropertyDescriptor property, Object object, BindingContext bindingContext) { public PropertyBinding(PropertyDescriptor property, Object object, BindingContext bindingContext) {
this.property = property; this.property = property;
this.object = object; this.object = object;
this.bindingContext = bindingContext; this.bindingContext = bindingContext;
buffer = new ValueBuffer(getModel()); buffer = new ValueBuffer(getModel());
status = BindingStatus.CLEAN; status = BindingStatus.CLEAN;
if (isList()) {
listElementBindings = new HashMap<Integer, ListElementBinding>();
elementType = GenericCollectionTypeResolver.getCollectionReturnType(property.getReadMethod());
}
} }
public String getRenderValue() { public String getRenderValue() {
return format(getValue(), bindingContext.getFormatter()); return format(getValue(), getFormatter());
} }
public Object getValue() { public Object getValue() {
@ -88,7 +97,7 @@ public class PropertyBinding implements Binding {
assertEnabled(); assertEnabled();
if (sourceValue instanceof String) { if (sourceValue instanceof String) {
try { try {
Object parsed = bindingContext.getFormatter().parse((String) sourceValue, getLocale()); Object parsed = getFormatter().parse((String) sourceValue, getLocale());
buffer.setValue(coerseToValueType(parsed)); buffer.setValue(coerseToValueType(parsed));
sourceValue = null; sourceValue = null;
status = BindingStatus.DIRTY; status = BindingStatus.DIRTY;
@ -123,13 +132,23 @@ public class PropertyBinding implements Binding {
if (status != BindingStatus.INVALID_SOURCE_VALUE) { if (status != BindingStatus.INVALID_SOURCE_VALUE) {
try { try {
buffer.setValue(coerseToValueType(parsed)); buffer.setValue(coerseToValueType(parsed));
sourceValue = null;
status = BindingStatus.DIRTY;
} catch (ConversionFailedException e) { } catch (ConversionFailedException e) {
this.sourceValue = sourceValue; this.sourceValue = sourceValue;
invalidSourceValueCause = e; invalidSourceValueCause = e;
status = BindingStatus.INVALID_SOURCE_VALUE; status = BindingStatus.INVALID_SOURCE_VALUE;
} }
}
} else {
try {
buffer.setValue(coerseToValueType(sourceValue));
sourceValue = null; sourceValue = null;
status = BindingStatus.DIRTY; 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) { } else if (status == BindingStatus.COMMIT_FAILURE) {
return new AbstractAlert() { return new AbstractAlert() {
public String getCode() { public String getCode() {
return "internalError"; return "internalError";
} }
public String getMessage() { public String getMessage() {
return "Internal error occurred; message = [" + buffer.getFlushException().getMessage() + "]"; return "Internal error occurred; message = [" + buffer.getFlushException().getMessage() + "]";
} }
public Severity getSeverity() { public Severity getSeverity() {
return Severity.FATAL; return Severity.FATAL;
} }
}; };
} else if (status == BindingStatus.COMMITTED) { } else if (status == BindingStatus.COMMITTED) {
return new AbstractAlert() { return new AbstractAlert() {
public String getCode() { public String getCode() {
@ -248,6 +267,10 @@ public class PropertyBinding implements Binding {
public Class<?> getValueType() { public Class<?> getValueType() {
return property.getPropertyType(); return property.getPropertyType();
} }
public TypeDescriptor<?> getValueTypeDescriptor() {
return new TypeDescriptor(new MethodParameter(property.getReadMethod(), -1));
}
public void setValue(Object value) { public void setValue(Object value) {
ReflectionUtils.invokeMethod(property.getWriteMethod(), object, value); ReflectionUtils.invokeMethod(property.getWriteMethod(), object, value);
@ -264,17 +287,21 @@ public class PropertyBinding implements Binding {
} }
public boolean isList() { public boolean isList() {
return getModel().getValueType().isArray() || List.class.isAssignableFrom(getModel().getValueType()); return List.class.isAssignableFrom(getValueType());
} }
public Binding getListElementBinding(int index) { public Binding getListElementBinding(int index) {
assertListProperty(); assertListProperty();
//return new IndexedBinding(index, (List) getValue(), getCollectionTypeDescriptor(), typeConverter); ListElementBinding binding = listElementBindings.get(index);
return null; if (binding == null) {
binding = new ListElementBinding(index);
listElementBindings.put(index, binding);
}
return binding;
} }
public boolean isMap() { public boolean isMap() {
return Map.class.isAssignableFrom(getModel().getValueType()); return Map.class.isAssignableFrom(getValueType());
} }
public Binding getMapValueBinding(Object key) { public Binding getMapValueBinding(Object key) {
@ -299,7 +326,7 @@ public class PropertyBinding implements Binding {
} }
return format(value, formatter); return format(value, formatter);
} }
private String format(Object value, Formatter formatter) { private String format(Object value, Formatter formatter) {
Class<?> formattedType = getFormattedObjectType(formatter.getClass()); Class<?> formattedType = getFormattedObjectType(formatter.getClass());
value = bindingContext.getTypeConverter().convert(value, formattedType); value = bindingContext.getTypeConverter().convert(value, formattedType);
@ -308,6 +335,10 @@ public class PropertyBinding implements Binding {
// internal helpers // internal helpers
protected Formatter getFormatter() {
return bindingContext.getFormatter();
}
private Locale getLocale() { private Locale getLocale() {
return LocaleContextHolder.getLocale(); return LocaleContextHolder.getLocale();
} }
@ -354,7 +385,7 @@ public class PropertyBinding implements Binding {
} }
private Object coerseToValueType(Object parsed) { private Object coerseToValueType(Object parsed) {
TypeDescriptor targetType = new TypeDescriptor(new MethodParameter(property.getWriteMethod(), 0)); TypeDescriptor targetType = getModel().getValueTypeDescriptor();
TypeConverter converter = bindingContext.getTypeConverter(); TypeConverter converter = bindingContext.getTypeConverter();
if (parsed != null && converter.canConvert(parsed.getClass(), targetType)) { if (parsed != null && converter.canConvert(parsed.getClass(), targetType)) {
return converter.convert(parsed, 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() { private void assertScalarProperty() {
if (isList()) { if (isList()) {
throw new IllegalArgumentException("Is a Collection but should be a scalar"); throw new IllegalArgumentException("Is a Collection but should be a scalar");
@ -411,5 +436,72 @@ public class PropertyBinding implements Binding {
return getCode() + " - " + getMessage(); 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);
}
}
}
} }

View File

@ -19,9 +19,11 @@ public class PropertyPath implements Iterable<PropertyPathElement> {
props = new String[] { propertyPath }; props = new String[] { propertyPath };
} }
for (String prop : props) { for (String prop : props) {
if (prop.startsWith("[")) { if (prop.contains("[")) {
int end = prop.indexOf(']'); int start = prop.indexOf('[');
String index = prop.substring(0, end); 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)); elements.add(new PropertyPathElement(index, true));
} else { } else {
elements.add(new PropertyPathElement(prop, false)); elements.add(new PropertyPathElement(prop, false));
@ -35,7 +37,7 @@ public class PropertyPath implements Iterable<PropertyPathElement> {
public List<PropertyPathElement> getNestedElements() { public List<PropertyPathElement> getNestedElements() {
if (elements.size() > 1) { if (elements.size() > 1) {
return elements.subList(1, elements.size() - 1); return elements.subList(1, elements.size());
} else { } else {
return Collections.emptyList(); return Collections.emptyList();
} }

View File

@ -7,6 +7,7 @@ import static org.junit.Assert.assertTrue;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.text.ParseException; import java.text.ParseException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
@ -210,18 +211,19 @@ public class GenericBinderTests {
assertEquals(FooEnum.BOOP, value.get(2)); assertEquals(FooEnum.BOOP, value.get(2));
} }
/*
@Test @Test
public void getBindingMultiValuedIndexAccess() { public void getBindingMultiValuedIndexAccess() {
binder.addBinding("foos");
bean.setFoos(Arrays.asList(new FooEnum[] { FooEnum.BAR })); bean.setFoos(Arrays.asList(new FooEnum[] { FooEnum.BAR }));
Binding b = binder.getBinding("foos[0]"); Binding b = binder.getBinding("foos[0]");
assertFalse(b.isIndexable()); assertFalse(b.isList());
assertEquals("BAR", b.getValue()); assertEquals(FooEnum.BAR, b.getValue());
b.setValue("BAZ"); assertEquals("BAR", b.getRenderValue());
assertEquals("BAZ", b.getValue()); b.applySourceValue("BAZ");
assertEquals("BAZ", b.getRenderValue());
assertEquals(FooEnum.BAZ, b.getValue());
} }
/*
@Test @Test
public void getBindingMultiValuedTypeConversionFailure() { public void getBindingMultiValuedTypeConversionFailure() {
binder.addBinding("foos"); binder.addBinding("foos");