nested binding work; list binding still has kinks

This commit is contained in:
Keith Donald 2009-07-21 22:32:06 +00:00
parent f739c3fde1
commit 14dd30c5a5
10 changed files with 619 additions and 535 deletions

View File

@ -15,7 +15,6 @@
*/
package org.springframework.ui.binding;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.ui.alert.Alert;
import org.springframework.ui.alert.Severity;
@ -114,33 +113,6 @@ public interface Binding {
*/
void revert();
/**
* For accessing the raw bound model object.
* @author Keith Donald
*/
public interface Model {
/**
* The model value.
*/
Object getValue();
/**
* The model value type.
*/
Class<?> getValueType();
/**
* The model value type descriptor.
*/
TypeDescriptor<?> getValueTypeDescriptor();
/**
* Set the model value.
*/
void setValue(Object value);
}
/**
* Get a Binding to a nested property value.
* @param property the nested property name, such as "foo"; should not be a property path like "foo.bar"

View File

@ -0,0 +1,375 @@
package org.springframework.ui.binding.support;
import java.lang.reflect.Array;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.text.ParseException;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.core.GenericTypeResolver;
import org.springframework.core.convert.ConversionFailedException;
import org.springframework.core.convert.TypeConverter;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.style.StylerUtils;
import org.springframework.ui.alert.Alert;
import org.springframework.ui.alert.Severity;
import org.springframework.ui.binding.Binding;
import org.springframework.ui.binding.support.GenericBinder.BindingContext;
import org.springframework.ui.format.Formatter;
import org.springframework.ui.message.MessageBuilder;
import org.springframework.ui.message.ResolvableArgument;
public abstract class AbstractBinding implements Binding {
private BindingContext bindingContext;
private ValueBuffer buffer;
private BindingStatus status;
private Object sourceValue;
private Exception invalidSourceValueCause;
public AbstractBinding(BindingContext bindingContext) {
this.bindingContext = bindingContext;
buffer = new ValueBuffer(getValueModel());
status = BindingStatus.CLEAN;
}
// implementing Binding
public String getRenderValue() {
return format(getValue(), bindingContext.getFormatter());
}
public Object getValue() {
if (status == BindingStatus.DIRTY || status == BindingStatus.COMMIT_FAILURE) {
return buffer.getValue();
} else {
return getValueModel().getValue();
}
}
public Class<?> getValueType() {
return getValueModel().getValueType();
}
public boolean isEditable() {
return bindingContext.getEditableCondition().isTrue();
}
public boolean isEnabled() {
return bindingContext.getEnabledCondition().isTrue();
}
public boolean isVisible() {
return bindingContext.getVisibleCondition().isTrue();
}
public void applySourceValue(Object sourceValue) {
assertEditable();
assertEnabled();
if (sourceValue instanceof String) {
try {
Object parsed = bindingContext.getFormatter().parse((String) sourceValue, getLocale());
buffer.setValue(coerseToValueType(parsed));
sourceValue = null;
status = BindingStatus.DIRTY;
} catch (ParseException e) {
this.sourceValue = sourceValue;
invalidSourceValueCause = e;
status = BindingStatus.INVALID_SOURCE_VALUE;
} catch (ConversionFailedException e) {
this.sourceValue = sourceValue;
invalidSourceValueCause = e;
status = BindingStatus.INVALID_SOURCE_VALUE;
}
} else if (sourceValue instanceof String[]) {
String[] sourceValues = (String[]) sourceValue;
Class<?> parsedType = getFormattedObjectType(bindingContext.getElementFormatter().getClass());
if (parsedType == null) {
parsedType = String.class;
}
Object parsed = Array.newInstance(parsedType, sourceValues.length);
for (int i = 0; i < sourceValues.length; i++) {
Object parsedValue;
try {
parsedValue = bindingContext.getElementFormatter().parse(sourceValues[i], getLocale());
Array.set(parsed, i, parsedValue);
} catch (ParseException e) {
this.sourceValue = sourceValue;
invalidSourceValueCause = e;
status = BindingStatus.INVALID_SOURCE_VALUE;
break;
}
}
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;
}
}
}
public Object getInvalidSourceValue() {
if (status != BindingStatus.INVALID_SOURCE_VALUE) {
throw new IllegalStateException("No invalid source value");
}
return sourceValue;
}
public BindingStatus getStatus() {
return status;
}
public Alert getStatusAlert() {
if (status == BindingStatus.INVALID_SOURCE_VALUE) {
return new AbstractAlert() {
public String getCode() {
return "typeMismatch";
}
public String getMessage() {
MessageBuilder builder = new MessageBuilder(bindingContext.getMessageSource());
builder.code(getCode());
if (invalidSourceValueCause instanceof ParseException) {
ParseException e = (ParseException) invalidSourceValueCause;
builder.arg("label", bindingContext.getLabel());
builder.arg("value", sourceValue);
builder.arg("errorOffset", e.getErrorOffset());
builder.defaultMessage("Failed to bind '" + bindingContext.getLabel() + "'; the source value "
+ StylerUtils.style(sourceValue) + " has an invalid format and could no be parsed");
} else {
ConversionFailedException e = (ConversionFailedException) invalidSourceValueCause;
builder.arg("label", new ResolvableArgument(bindingContext.getLabel()));
builder.arg("value", sourceValue);
builder.defaultMessage("Failed to bind '" + bindingContext.getLabel() + "'; the source value "
+ StylerUtils.style(sourceValue) + " has could not be converted to "
+ e.getTargetType().getName());
}
return builder.build();
}
public Severity getSeverity() {
return Severity.ERROR;
}
};
} else if (status == BindingStatus.COMMIT_FAILURE) {
return new AbstractAlert() {
public String getCode() {
return "internalError";
}
public String getMessage() {
buffer.getFlushException().printStackTrace();
return "Internal error occurred; message = [" + buffer.getFlushException().getMessage() + "]";
}
public Severity getSeverity() {
return Severity.FATAL;
}
};
} else if (status == BindingStatus.COMMITTED) {
return new AbstractAlert() {
public String getCode() {
return "bindSuccess";
}
public String getMessage() {
return "Binding successful";
}
public Severity getSeverity() {
return Severity.INFO;
}
};
} else {
return null;
}
}
public void commit() {
assertEditable();
assertEnabled();
if (status == BindingStatus.DIRTY) {
buffer.flush();
if (buffer.flushFailed()) {
status = BindingStatus.COMMIT_FAILURE;
} else {
status = BindingStatus.COMMITTED;
}
} else {
throw new IllegalStateException("Binding is not dirty; nothing to commit");
}
}
public void revert() {
if (status == BindingStatus.INVALID_SOURCE_VALUE) {
sourceValue = null;
invalidSourceValueCause = null;
status = BindingStatus.CLEAN;
} else if (status == BindingStatus.DIRTY || status == BindingStatus.COMMIT_FAILURE) {
buffer.clear();
status = BindingStatus.CLEAN;
} else {
throw new IllegalStateException("Nothing to revert");
}
}
public Binding getBinding(String property) {
return bindingContext.getBinding(property);
}
public boolean isList() {
return List.class.isAssignableFrom(getValueType());
}
public Binding getListElementBinding(int index) {
return bindingContext.getListElementBinding(index);
}
public boolean isMap() {
return Map.class.isAssignableFrom(getValueType());
}
public Binding getMapValueBinding(Object key) {
return bindingContext.getMapValueBinding(key);
}
@SuppressWarnings("unchecked")
public String formatValue(Object value) {
Formatter formatter;
if (isList() || isMap()) {
formatter = getBindingContext().getElementFormatter();
} else {
formatter = getBindingContext().getFormatter();
}
return format(value, formatter);
}
// subclassing hooks
protected BindingContext getBindingContext() {
return bindingContext;
}
protected abstract ValueModel getValueModel();
protected Locale getLocale() {
return LocaleContextHolder.getLocale();
}
protected String format(Object value, Formatter formatter) {
Class<?> formattedType = getFormattedObjectType(formatter.getClass());
value = bindingContext.getTypeConverter().convert(value, formattedType);
return formatter.format(value, getLocale());
}
private Class getFormattedObjectType(Class formatterClass) {
Class classToIntrospect = formatterClass;
while (classToIntrospect != null) {
Type[] ifcs = classToIntrospect.getGenericInterfaces();
for (Type ifc : ifcs) {
if (ifc instanceof ParameterizedType) {
ParameterizedType paramIfc = (ParameterizedType) ifc;
Type rawType = paramIfc.getRawType();
if (Formatter.class.equals(rawType)) {
Type arg = paramIfc.getActualTypeArguments()[0];
if (arg instanceof TypeVariable) {
arg = GenericTypeResolver.resolveTypeVariable((TypeVariable) arg, formatterClass);
}
if (arg instanceof Class) {
return (Class) arg;
}
} else if (Formatter.class.isAssignableFrom((Class) rawType)) {
return getFormattedObjectType((Class) rawType);
}
} else if (Formatter.class.isAssignableFrom((Class) ifc)) {
return getFormattedObjectType((Class) ifc);
}
}
classToIntrospect = classToIntrospect.getSuperclass();
}
return null;
}
private Object coerseToValueType(Object parsed) {
TypeDescriptor targetType = getValueModel().getValueTypeDescriptor();
TypeConverter converter = bindingContext.getTypeConverter();
if (parsed != null && converter.canConvert(parsed.getClass(), targetType)) {
return converter.convert(parsed, targetType);
} else {
return parsed;
}
}
private void assertEditable() {
if (!isEditable()) {
throw new IllegalStateException("Binding is not editable");
}
}
private void assertEnabled() {
if (!isEditable()) {
throw new IllegalStateException("Binding is not enabled");
}
}
// internal helpers
static abstract class AbstractAlert implements Alert {
public String toString() {
return getCode() + " - " + getMessage();
}
}
/**
* For accessing the raw bound model object.
* @author Keith Donald
*/
public interface ValueModel {
/**
* The model value.
*/
Object getValue();
/**
* The model value type.
*/
Class<?> getValueType();
/**
* The model value type descriptor.
*/
TypeDescriptor<?> getValueTypeDescriptor();
/**
* Set the model value.
*/
void setValue(Object value);
}
}

View File

@ -126,6 +126,7 @@ public class GenericBinder implements Binder {
public Binding getBinding(String property) {
PropertyPath path = new PropertyPath(property);
Binding binding = getBindingRule(path.getFirstElement().getValue()).getBinding(model);
System.out.println(path);
for (PropertyPathElement element : path.getNestedElements()) {
if (element.isIndex()) {
if (binding.isMap()) {
@ -231,16 +232,17 @@ public class GenericBinder implements Binder {
private Condition enabledCondition = Condition.ALWAYS_TRUE;
private Condition visibleCondition = Condition.ALWAYS_TRUE;
private Condition visibleCondition = Condition.ALWAYS_TRUE;
private Map<String, GenericBindingRule> nestedBindingRules;
private Binding binding;
private Map<Integer, Binding> listElementBindings;
public GenericBindingRule(String property, Class modelClass) {
this.modelClass = modelClass;
this.property = findPropertyDescriptor(property);
nestedBindingRules = new HashMap<String, GenericBindingRule>();
}
// implementing BindingContext
@ -289,8 +291,81 @@ public class GenericBinder implements Binder {
return visibleCondition;
}
public Binding getBinding(String property, Object model) {
return getBindingRule(property, model.getClass()).getBinding(model);
public Binding getBinding(String property) {
createValueIfNecessary();
return getBindingRule(property, binding.getValueType()).getBinding(binding.getValue());
}
public Binding getListElementBinding(final int index) {
if (listElementBindings == null) {
listElementBindings = new HashMap<Integer, Binding>();
}
growListIfNecessary(index);
Binding binding = listElementBindings.get(index);
if (binding == null) {
BindingContext listContext = new BindingContext() {
public MessageSource getMessageSource() {
return GenericBindingRule.this.getMessageSource();
}
public TypeConverter getTypeConverter() {
return GenericBindingRule.this.getTypeConverter();
}
public Binding getBinding(String property) {
Object model = ((List) GenericBindingRule.this.binding.getValue()).get(index);
return GenericBindingRule.this.getBindingRule(property, getElementType()).getBinding(model);
}
public Formatter getFormatter() {
return GenericBindingRule.this.getElementFormatter();
}
public Formatter getElementFormatter() {
return null;
}
public Formatter getKeyFormatter() {
return null;
}
public Condition getEditableCondition() {
return GenericBindingRule.this.getEditableCondition();
}
public Condition getEnabledCondition() {
return GenericBindingRule.this.getEnabledCondition();
}
public Condition getVisibleCondition() {
return GenericBindingRule.this.getVisibleCondition();
}
public Binding getListElementBinding(int index) {
throw new IllegalArgumentException("Not yet supported");
}
public Binding getMapValueBinding(Object key) {
throw new IllegalArgumentException("Not yet supported");
}
public String getLabel() {
return GenericBindingRule.this.getLabel() + "[" + index + "]";
}
};
binding = new ListElementBinding(index, getElementType(), (List) this.binding.getValue(), listContext);
listElementBindings.put(index, binding);
}
return binding;
}
public Binding getMapValueBinding(Object key) {
return null;
}
public String getLabel() {
return property.getName();
}
// implementing BindingRuleConfiguration
@ -302,7 +377,8 @@ public class GenericBinder implements Binder {
public BindingRuleConfiguration formatElementsWith(Formatter<?> formatter) {
if (!List.class.isAssignableFrom(modelClass) || modelClass.isArray()) {
throw new IllegalStateException("Bound property is not a List or an array; cannot set a element formatter");
throw new IllegalStateException(
"Bound property is not a List or an array; cannot set a element formatter");
}
elementFormatter = formatter;
return this;
@ -345,7 +421,10 @@ public class GenericBinder implements Binder {
return getBindingRule(property, this.property.getPropertyType());
}
GenericBindingRule getBindingRule(String property, Class modelClass) {
GenericBindingRule getBindingRule(String property, Class<?> modelClass) {
if (nestedBindingRules == null) {
nestedBindingRules = new HashMap<String, GenericBindingRule>();
}
GenericBindingRule rule = nestedBindingRules.get(property);
if (rule == null) {
rule = new GenericBindingRule(property, modelClass);
@ -354,6 +433,8 @@ public class GenericBinder implements Binder {
return rule;
}
// internal helpers
Binding getBinding(Object model) {
if (binding == null) {
binding = new PropertyBinding(property, model, this);
@ -378,6 +459,49 @@ public class GenericBinder implements Binder {
throw new IllegalStateException("Unable to introspect model type " + clazz);
}
}
private void createValueIfNecessary() {
Object value = binding.getValue();
if (value == null) {
value = newValue(binding.getValueType());
binding.applySourceValue(value);
binding.commit();
}
}
private void growListIfNecessary(int index) {
List list = (List) binding.getValue();
if (list == null) {
list = newListValue(binding.getValueType());
binding.applySourceValue(list);
binding.commit();
list = (List) binding.getValue();
}
if (index >= list.size()) {
for (int i = list.size(); i <= index; i++) {
list.add(newValue(getElementType()));
}
}
}
private List newListValue(Class<?> type) {
if (type.isInterface()) {
return (List) newValue(ArrayList.class);
} else {
return (List) 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);
}
}
}
public interface BindingContext {
@ -392,17 +516,20 @@ public class GenericBinder implements Binder {
Condition getVisibleCondition();
Binding getBinding(String property, Object model);
Binding getBinding(String property);
@SuppressWarnings("unchecked")
Formatter getFormatter();
@SuppressWarnings("unchecked")
Formatter getElementFormatter();
@SuppressWarnings("unchecked")
Formatter getKeyFormatter();
Binding getListElementBinding(int index);
Binding getMapValueBinding(Object key);
String getLabel();
}
public void setRequired(String[] propertyPaths) {

View File

@ -0,0 +1,51 @@
package org.springframework.ui.binding.support;
import java.util.List;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.ui.binding.support.GenericBinder.BindingContext;
public class ListElementBinding extends AbstractBinding {
private List list;
private int index;
private Class<?> elementType;
private BindingContext bindingContext;
public ListElementBinding(int index, Class<?> elementType, List list, BindingContext bindingContext) {
super(bindingContext);
this.index = index;
this.elementType = elementType;
this.list = list;
this.bindingContext = bindingContext;
}
@Override
protected ValueModel getValueModel() {
return new ValueModel() {
public Object getValue() {
return list.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) {
list.set(index, value);
}
};
}
}

View File

@ -1,263 +1,36 @@
package org.springframework.ui.binding.support;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Array;
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;
import java.util.Map;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.core.GenericCollectionTypeResolver;
import org.springframework.core.GenericTypeResolver;
import org.springframework.core.MethodParameter;
import org.springframework.core.convert.ConversionFailedException;
import org.springframework.core.convert.TypeConverter;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.style.StylerUtils;
import org.springframework.ui.alert.Alert;
import org.springframework.ui.alert.Severity;
import org.springframework.ui.binding.Binding;
import org.springframework.ui.binding.support.GenericBinder.BindingContext;
import org.springframework.ui.format.Formatter;
import org.springframework.ui.message.MessageBuilder;
import org.springframework.ui.message.ResolvableArgument;
import org.springframework.util.ReflectionUtils;
@SuppressWarnings("unchecked")
public class PropertyBinding implements Binding {
public class PropertyBinding extends AbstractBinding implements Binding {
private PropertyDescriptor property;
private Object object;
private BindingContext bindingContext;
private Object sourceValue;
private Exception invalidSourceValueCause;
private ValueBuffer buffer;
private BindingStatus status;
private Map<Integer, ListElementBinding> listElementBindings;
private Class<?> elementType;
public PropertyBinding(PropertyDescriptor property, Object object, BindingContext bindingContext) {
super(bindingContext);
this.property = property;
this.object = object;
this.bindingContext = bindingContext;
buffer = new ValueBuffer(getModel());
status = BindingStatus.CLEAN;
if (isList()) {
listElementBindings = new HashMap<Integer, ListElementBinding>();
elementType = GenericCollectionTypeResolver.getCollectionReturnType(property.getReadMethod());
}
}
public String getRenderValue() {
return format(getValue(), getFormatter());
}
public Object getValue() {
if (status == BindingStatus.DIRTY || status == BindingStatus.COMMIT_FAILURE) {
return buffer.getValue();
} else {
return getModel().getValue();
}
}
public Class<?> getValueType() {
return getModel().getValueType();
}
// Binding overrides
@Override
public boolean isEditable() {
return isWriteableProperty() && bindingContext.getEditableCondition().isTrue();
return isWriteable() && super.isEditable();
}
public boolean isEnabled() {
return bindingContext.getEnabledCondition().isTrue();
}
// implementing
public boolean isVisible() {
return bindingContext.getVisibleCondition().isTrue();
}
public void applySourceValue(Object sourceValue) {
assertEditable();
assertEnabled();
if (sourceValue instanceof String) {
try {
Object parsed = getFormatter().parse((String) sourceValue, getLocale());
buffer.setValue(coerseToValueType(parsed));
sourceValue = null;
status = BindingStatus.DIRTY;
} catch (ParseException e) {
this.sourceValue = sourceValue;
invalidSourceValueCause = e;
status = BindingStatus.INVALID_SOURCE_VALUE;
} catch (ConversionFailedException e) {
this.sourceValue = sourceValue;
invalidSourceValueCause = e;
status = BindingStatus.INVALID_SOURCE_VALUE;
}
} else if (sourceValue instanceof String[]) {
String[] sourceValues = (String[]) sourceValue;
Class<?> parsedType = getFormattedObjectType(bindingContext.getElementFormatter().getClass());
if (parsedType == null) {
parsedType = String.class;
}
Object parsed = Array.newInstance(parsedType, sourceValues.length);
for (int i = 0; i < sourceValues.length; i++) {
Object parsedValue;
try {
parsedValue = bindingContext.getElementFormatter().parse(sourceValues[i], getLocale());
Array.set(parsed, i, parsedValue);
} catch (ParseException e) {
this.sourceValue = sourceValue;
invalidSourceValueCause = e;
status = BindingStatus.INVALID_SOURCE_VALUE;
break;
}
}
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;
}
}
}
public Object getInvalidSourceValue() {
if (status != BindingStatus.INVALID_SOURCE_VALUE) {
throw new IllegalStateException("No invalid source value");
}
return sourceValue;
}
public BindingStatus getStatus() {
return status;
}
public Alert getStatusAlert() {
if (status == BindingStatus.INVALID_SOURCE_VALUE) {
return new AbstractAlert() {
public String getCode() {
return "typeMismatch";
}
public String getMessage() {
MessageBuilder builder = new MessageBuilder(bindingContext.getMessageSource());
builder.code(getCode());
if (invalidSourceValueCause instanceof ParseException) {
ParseException e = (ParseException) invalidSourceValueCause;
builder.arg("label", new ResolvableArgument(property.getName()));
builder.arg("value", sourceValue);
builder.arg("errorOffset", e.getErrorOffset());
builder.defaultMessage("Failed to bind to property '" + property + "'; the user value "
+ StylerUtils.style(sourceValue) + " has an invalid format and could no be parsed");
} else {
ConversionFailedException e = (ConversionFailedException) invalidSourceValueCause;
builder.arg("label", new ResolvableArgument(property.getName()));
builder.arg("value", sourceValue);
builder.defaultMessage("Failed to bind to property '" + property + "'; the user value "
+ StylerUtils.style(sourceValue) + " has could not be converted to "
+ e.getTargetType().getName());
}
return builder.build();
}
public Severity getSeverity() {
return Severity.ERROR;
}
};
} else if (status == BindingStatus.COMMIT_FAILURE) {
return new AbstractAlert() {
public String getCode() {
return "internalError";
}
public String getMessage() {
return "Internal error occurred; message = [" + buffer.getFlushException().getMessage() + "]";
}
public Severity getSeverity() {
return Severity.FATAL;
}
};
} else if (status == BindingStatus.COMMITTED) {
return new AbstractAlert() {
public String getCode() {
return "bindSucces";
}
public String getMessage() {
return "Binding successful";
}
public Severity getSeverity() {
return Severity.INFO;
}
};
} else {
return null;
}
}
public void commit() {
assertEditable();
assertEnabled();
if (status == BindingStatus.DIRTY) {
buffer.flush();
if (buffer.flushFailed()) {
status = BindingStatus.COMMIT_FAILURE;
} else {
status = BindingStatus.COMMITTED;
}
} else {
throw new IllegalStateException("Binding is not dirty; nothing to commit");
}
}
public void revert() {
if (status == BindingStatus.INVALID_SOURCE_VALUE) {
sourceValue = null;
invalidSourceValueCause = null;
status = BindingStatus.CLEAN;
} else if (status == BindingStatus.DIRTY || status == BindingStatus.COMMIT_FAILURE) {
buffer.clear();
status = BindingStatus.CLEAN;
} else {
throw new IllegalStateException("Nothing to revert");
}
}
public Model getModel() {
return new Model() {
protected ValueModel getValueModel() {
return new ValueModel() {
public Object getValue() {
return ReflectionUtils.invokeMethod(property.getReadMethod(), object);
}
@ -266,6 +39,7 @@ public class PropertyBinding implements Binding {
return property.getPropertyType();
}
@SuppressWarnings("unchecked")
public TypeDescriptor<?> getValueTypeDescriptor() {
return new TypeDescriptor(new MethodParameter(property.getReadMethod(), -1));
}
@ -276,231 +50,9 @@ public class PropertyBinding implements Binding {
};
}
public Binding getBinding(String property) {
assertScalarProperty();
if (getValue() == null) {
getModel().setValue(newValue(getValueType()));
}
return bindingContext.getBinding(property, getValue());
}
public boolean isList() {
return List.class.isAssignableFrom(getValueType());
}
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);
listElementBindings.put(index, binding);
}
return binding;
}
public boolean isMap() {
return Map.class.isAssignableFrom(getValueType());
}
public Binding getMapValueBinding(Object key) {
assertMapProperty();
if (key instanceof String) {
try {
key = bindingContext.getKeyFormatter().parse((String) key, getLocale());
} catch (ParseException e) {
throw new IllegalArgumentException("Invald key", e);
}
}
//TODO return new KeyedPropertyBinding(key, (Map) getValue(), getMapTypeDescriptor());
return null;
}
public String formatValue(Object value) {
Formatter formatter;
if (isList() || isMap()) {
formatter = bindingContext.getElementFormatter();
} else {
formatter = bindingContext.getFormatter();
}
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());
}
private Locale getLocale() {
return LocaleContextHolder.getLocale();
}
private Class getFormattedObjectType(Class formatterClass) {
Class classToIntrospect = formatterClass;
while (classToIntrospect != null) {
Type[] ifcs = classToIntrospect.getGenericInterfaces();
for (Type ifc : ifcs) {
if (ifc instanceof ParameterizedType) {
ParameterizedType paramIfc = (ParameterizedType) ifc;
Type rawType = paramIfc.getRawType();
if (Formatter.class.equals(rawType)) {
Type arg = paramIfc.getActualTypeArguments()[0];
if (arg instanceof TypeVariable) {
arg = GenericTypeResolver.resolveTypeVariable((TypeVariable) arg, formatterClass);
}
if (arg instanceof Class) {
return (Class) arg;
}
} else if (Formatter.class.isAssignableFrom((Class) rawType)) {
return getFormattedObjectType((Class) rawType);
}
} else if (Formatter.class.isAssignableFrom((Class) ifc)) {
return getFormattedObjectType((Class) ifc);
}
}
classToIntrospect = classToIntrospect.getSuperclass();
}
return null;
}
private Object coerseToValueType(Object parsed) {
TypeDescriptor targetType = getModel().getValueTypeDescriptor();
TypeConverter converter = bindingContext.getTypeConverter();
if (parsed != null && converter.canConvert(parsed.getClass(), targetType)) {
return converter.convert(parsed, targetType);
} else {
return parsed;
}
}
private void assertScalarProperty() {
if (isList()) {
throw new IllegalArgumentException("Is a Collection but should be a scalar");
}
if (isMap()) {
throw new IllegalArgumentException("Is a Map but should be a scalar");
}
}
private void assertListProperty() {
if (!isList()) {
throw new IllegalStateException("Not a List property binding");
}
}
private void assertMapProperty() {
if (!isList()) {
throw new IllegalStateException("Not a Map property binding");
}
}
private void assertEditable() {
if (!isEditable()) {
throw new IllegalStateException("Binding is not editable");
}
}
private void assertEnabled() {
if (!isEditable()) {
throw new IllegalStateException("Binding is not enabled");
}
}
private boolean isWriteableProperty() {
private boolean isWriteable() {
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;
public ListElementBinding(int index) {
super(property, object, bindingContext);
this.index = index;
}
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 List getList() {
return (List) PropertyBinding.this.getValue();
}
}
}

View File

@ -47,4 +47,8 @@ public class PropertyPath implements Iterable<PropertyPathElement> {
return elements.iterator();
}
public String toString() {
return elements.toString();
}
}

View File

@ -25,4 +25,8 @@ public class PropertyPathElement {
public int getIntValue() {
return Integer.parseInt(value);
}
public String toString() {
return value + ";index=" + index;
}
}

View File

@ -3,7 +3,7 @@
*/
package org.springframework.ui.binding.support;
import org.springframework.ui.binding.Binding.Model;
import org.springframework.ui.binding.support.AbstractBinding.ValueModel;
class ValueBuffer {
@ -11,13 +11,13 @@ class ValueBuffer {
private boolean hasValue;
private Model model;
private ValueModel model;
private boolean flushFailed;
private Exception flushException;
public ValueBuffer(Model model) {
public ValueBuffer(ValueModel model) {
this.model = model;
}

View File

@ -83,7 +83,7 @@ public class WebBinder extends GenericBinder {
}
protected Object getEmptyValue(PropertyBinding binding) {
Class<?> type = binding.getModel().getValueType();
Class<?> type = binding.getValueModel().getValueType();
if (boolean.class.equals(type) || Boolean.class.equals(type)) {
return Boolean.FALSE;
} else {

View File

@ -63,7 +63,6 @@ public class GenericBinderTests {
values.put("integer", "3");
values.put("foo", "BAR");
BindingResults results = binder.bind(values);
System.out.println(results);
assertEquals(3, results.size());
assertEquals("string", results.get(0).getProperty());
@ -251,7 +250,7 @@ public class GenericBinderTests {
values.put("addresses[0]", "4655 Macy Lane:Melbourne:FL:35452");
values.put("addresses[1]", "1234 Rostock Circle:Palm Bay:FL:32901");
values.put("addresses[5]", "1977 Bel Aire Estates:Coker:AL:12345");
binder.bind(values);
BindingResults results = binder.bind(values);
Assert.assertEquals(6, bean.addresses.size());
assertEquals("4655 Macy Lane", bean.addresses.get(0).street);
assertEquals("Melbourne", bean.addresses.get(0).city);