nested binding work; list binding still has kinks
This commit is contained in:
parent
f739c3fde1
commit
14dd30c5a5
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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()) {
|
||||
|
|
@ -237,10 +238,11 @@ public class GenericBinder implements Binder {
|
|||
|
||||
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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -47,4 +47,8 @@ public class PropertyPath implements Iterable<PropertyPathElement> {
|
|||
return elements.iterator();
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return elements.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -25,4 +25,8 @@ public class PropertyPathElement {
|
|||
public int getIntValue() {
|
||||
return Integer.parseInt(value);
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return value + ";index=" + index;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Reference in New Issue