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;
|
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;
|
||||||
|
|
||||||
|
|
@ -114,33 +113,6 @@ public interface Binding {
|
||||||
*/
|
*/
|
||||||
void revert();
|
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.
|
* 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"
|
* @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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -52,15 +52,15 @@ public class GenericBinder implements Binder {
|
||||||
private Object model;
|
private Object model;
|
||||||
|
|
||||||
private Map<String, GenericBindingRule> bindingRules;
|
private Map<String, GenericBindingRule> bindingRules;
|
||||||
|
|
||||||
private FormatterRegistry formatterRegistry;
|
private FormatterRegistry formatterRegistry;
|
||||||
|
|
||||||
private TypeConverter typeConverter;
|
private TypeConverter typeConverter;
|
||||||
|
|
||||||
private MessageSource messageSource;
|
private MessageSource messageSource;
|
||||||
|
|
||||||
private String[] requiredProperties = new String[0];
|
private String[] requiredProperties = new String[0];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new binder for the model object.
|
* Creates a new binder for the model object.
|
||||||
* @param model the model object containing properties this binder will bind to
|
* @param model the model object containing properties this binder will bind to
|
||||||
|
|
@ -82,7 +82,7 @@ public class GenericBinder implements Binder {
|
||||||
Assert.notNull(formatterRegistry, "The FormatterRegistry is required");
|
Assert.notNull(formatterRegistry, "The FormatterRegistry is required");
|
||||||
this.formatterRegistry = formatterRegistry;
|
this.formatterRegistry = formatterRegistry;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configure the MessageSource that resolves localized {@link BindingResult} alert messages.
|
* Configure the MessageSource that resolves localized {@link BindingResult} alert messages.
|
||||||
* @param messageSource the message source
|
* @param messageSource the message source
|
||||||
|
|
@ -116,7 +116,7 @@ public class GenericBinder implements Binder {
|
||||||
}
|
}
|
||||||
return rule;
|
return rule;
|
||||||
}
|
}
|
||||||
|
|
||||||
// implementing Binder
|
// implementing Binder
|
||||||
|
|
||||||
public Object getModel() {
|
public Object getModel() {
|
||||||
|
|
@ -126,6 +126,7 @@ public class GenericBinder implements Binder {
|
||||||
public Binding getBinding(String property) {
|
public Binding getBinding(String property) {
|
||||||
PropertyPath path = new PropertyPath(property);
|
PropertyPath path = new PropertyPath(property);
|
||||||
Binding binding = getBindingRule(path.getFirstElement().getValue()).getBinding(model);
|
Binding binding = getBindingRule(path.getFirstElement().getValue()).getBinding(model);
|
||||||
|
System.out.println(path);
|
||||||
for (PropertyPathElement element : path.getNestedElements()) {
|
for (PropertyPathElement element : path.getNestedElements()) {
|
||||||
if (element.isIndex()) {
|
if (element.isIndex()) {
|
||||||
if (binding.isMap()) {
|
if (binding.isMap()) {
|
||||||
|
|
@ -158,7 +159,7 @@ public class GenericBinder implements Binder {
|
||||||
}
|
}
|
||||||
|
|
||||||
// subclassing hooks
|
// subclassing hooks
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hook subclasses may use to filter the source values to bind.
|
* Hook subclasses may use to filter the source values to bind.
|
||||||
* This hook allows the binder to pre-process the source values before binding occurs.
|
* This hook allows the binder to pre-process the source values before binding occurs.
|
||||||
|
|
@ -190,7 +191,7 @@ public class GenericBinder implements Binder {
|
||||||
throw new MissingSourceValuesException(missingRequired, sourceValues);
|
throw new MissingSourceValuesException(missingRequired, sourceValues);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private GenericBindingRule getBindingRule(String property) {
|
private GenericBindingRule getBindingRule(String property) {
|
||||||
GenericBindingRule rule = bindingRules.get(property);
|
GenericBindingRule rule = bindingRules.get(property);
|
||||||
if (rule == null) {
|
if (rule == null) {
|
||||||
|
|
@ -199,7 +200,7 @@ public class GenericBinder implements Binder {
|
||||||
}
|
}
|
||||||
return rule;
|
return rule;
|
||||||
}
|
}
|
||||||
|
|
||||||
private BindingResult bind(Map.Entry<String, ? extends Object> sourceValue, Binding binding) {
|
private BindingResult bind(Map.Entry<String, ? extends Object> sourceValue, Binding binding) {
|
||||||
String property = sourceValue.getKey();
|
String property = sourceValue.getKey();
|
||||||
Object value = sourceValue.getValue();
|
Object value = sourceValue.getValue();
|
||||||
|
|
@ -218,41 +219,42 @@ public class GenericBinder implements Binder {
|
||||||
class GenericBindingRule implements BindingRuleConfiguration, BindingContext {
|
class GenericBindingRule implements BindingRuleConfiguration, BindingContext {
|
||||||
|
|
||||||
private Class<?> modelClass;
|
private Class<?> modelClass;
|
||||||
|
|
||||||
private PropertyDescriptor property;
|
private PropertyDescriptor property;
|
||||||
|
|
||||||
private Formatter formatter;
|
private Formatter formatter;
|
||||||
|
|
||||||
private Formatter elementFormatter;
|
private Formatter elementFormatter;
|
||||||
|
|
||||||
private Formatter keyFormatter;
|
private Formatter keyFormatter;
|
||||||
|
|
||||||
private Condition editableCondition = Condition.ALWAYS_TRUE;
|
private Condition editableCondition = Condition.ALWAYS_TRUE;
|
||||||
|
|
||||||
private Condition enabledCondition = Condition.ALWAYS_TRUE;
|
private Condition enabledCondition = Condition.ALWAYS_TRUE;
|
||||||
|
|
||||||
private Condition visibleCondition = Condition.ALWAYS_TRUE;
|
private Condition visibleCondition = Condition.ALWAYS_TRUE;
|
||||||
|
|
||||||
private Map<String, GenericBindingRule> nestedBindingRules;
|
private Map<String, GenericBindingRule> nestedBindingRules;
|
||||||
|
|
||||||
private Binding binding;
|
private Binding binding;
|
||||||
|
|
||||||
|
private Map<Integer, Binding> listElementBindings;
|
||||||
|
|
||||||
public GenericBindingRule(String property, Class modelClass) {
|
public GenericBindingRule(String property, Class modelClass) {
|
||||||
this.modelClass = modelClass;
|
this.modelClass = modelClass;
|
||||||
this.property = findPropertyDescriptor(property);
|
this.property = findPropertyDescriptor(property);
|
||||||
nestedBindingRules = new HashMap<String, GenericBindingRule>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// implementing BindingContext
|
// implementing BindingContext
|
||||||
|
|
||||||
public MessageSource getMessageSource() {
|
public MessageSource getMessageSource() {
|
||||||
return messageSource;
|
return messageSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TypeConverter getTypeConverter() {
|
public TypeConverter getTypeConverter() {
|
||||||
return typeConverter;
|
return typeConverter;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Formatter<?> getFormatter() {
|
public Formatter<?> getFormatter() {
|
||||||
if (formatter != null) {
|
if (formatter != null) {
|
||||||
return formatter;
|
return formatter;
|
||||||
|
|
@ -288,9 +290,82 @@ public class GenericBinder implements Binder {
|
||||||
public Condition getVisibleCondition() {
|
public Condition getVisibleCondition() {
|
||||||
return visibleCondition;
|
return visibleCondition;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Binding getBinding(String property, Object model) {
|
public Binding getBinding(String property) {
|
||||||
return getBindingRule(property, model.getClass()).getBinding(model);
|
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
|
// implementing BindingRuleConfiguration
|
||||||
|
|
@ -302,7 +377,8 @@ public class GenericBinder implements Binder {
|
||||||
|
|
||||||
public BindingRuleConfiguration formatElementsWith(Formatter<?> formatter) {
|
public BindingRuleConfiguration formatElementsWith(Formatter<?> formatter) {
|
||||||
if (!List.class.isAssignableFrom(modelClass) || modelClass.isArray()) {
|
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;
|
elementFormatter = formatter;
|
||||||
return this;
|
return this;
|
||||||
|
|
@ -330,7 +406,7 @@ public class GenericBinder implements Binder {
|
||||||
visibleCondition = condition;
|
visibleCondition = condition;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
// internal helpers
|
// internal helpers
|
||||||
|
|
||||||
private Class<?> getElementType() {
|
private Class<?> getElementType() {
|
||||||
|
|
@ -344,8 +420,11 @@ public class GenericBinder implements Binder {
|
||||||
GenericBindingRule getBindingRule(String property) {
|
GenericBindingRule getBindingRule(String property) {
|
||||||
return getBindingRule(property, this.property.getPropertyType());
|
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);
|
GenericBindingRule rule = nestedBindingRules.get(property);
|
||||||
if (rule == null) {
|
if (rule == null) {
|
||||||
rule = new GenericBindingRule(property, modelClass);
|
rule = new GenericBindingRule(property, modelClass);
|
||||||
|
|
@ -353,12 +432,14 @@ public class GenericBinder implements Binder {
|
||||||
}
|
}
|
||||||
return rule;
|
return rule;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// internal helpers
|
||||||
|
|
||||||
Binding getBinding(Object model) {
|
Binding getBinding(Object model) {
|
||||||
if (binding == null) {
|
if (binding == null) {
|
||||||
binding = new PropertyBinding(property, model, this);
|
binding = new PropertyBinding(property, model, this);
|
||||||
}
|
}
|
||||||
return binding;
|
return binding;
|
||||||
}
|
}
|
||||||
|
|
||||||
private PropertyDescriptor findPropertyDescriptor(String property) {
|
private PropertyDescriptor findPropertyDescriptor(String property) {
|
||||||
|
|
@ -378,12 +459,55 @@ public class GenericBinder implements Binder {
|
||||||
throw new IllegalStateException("Unable to introspect model type " + clazz);
|
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 {
|
public interface BindingContext {
|
||||||
|
|
||||||
MessageSource getMessageSource();
|
MessageSource getMessageSource();
|
||||||
|
|
||||||
TypeConverter getTypeConverter();
|
TypeConverter getTypeConverter();
|
||||||
|
|
||||||
Condition getEditableCondition();
|
Condition getEditableCondition();
|
||||||
|
|
@ -392,17 +516,20 @@ public class GenericBinder implements Binder {
|
||||||
|
|
||||||
Condition getVisibleCondition();
|
Condition getVisibleCondition();
|
||||||
|
|
||||||
Binding getBinding(String property, Object model);
|
Binding getBinding(String property);
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
Formatter getFormatter();
|
Formatter getFormatter();
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
Formatter getElementFormatter();
|
Formatter getElementFormatter();
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
Formatter getKeyFormatter();
|
Formatter getKeyFormatter();
|
||||||
|
|
||||||
|
Binding getListElementBinding(int index);
|
||||||
|
|
||||||
|
Binding getMapValueBinding(Object key);
|
||||||
|
|
||||||
|
String getLabel();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setRequired(String[] propertyPaths) {
|
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;
|
package org.springframework.ui.binding.support;
|
||||||
|
|
||||||
import java.beans.PropertyDescriptor;
|
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.MethodParameter;
|
||||||
import org.springframework.core.convert.ConversionFailedException;
|
|
||||||
import org.springframework.core.convert.TypeConverter;
|
|
||||||
import org.springframework.core.convert.TypeDescriptor;
|
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.Binding;
|
||||||
import org.springframework.ui.binding.support.GenericBinder.BindingContext;
|
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;
|
import org.springframework.util.ReflectionUtils;
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
public class PropertyBinding extends AbstractBinding implements Binding {
|
||||||
public class PropertyBinding implements Binding {
|
|
||||||
|
|
||||||
private PropertyDescriptor property;
|
private PropertyDescriptor property;
|
||||||
|
|
||||||
private Object object;
|
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) {
|
public PropertyBinding(PropertyDescriptor property, Object object, BindingContext bindingContext) {
|
||||||
|
super(bindingContext);
|
||||||
this.property = property;
|
this.property = property;
|
||||||
this.object = object;
|
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() {
|
public boolean isEditable() {
|
||||||
return isWriteableProperty() && bindingContext.getEditableCondition().isTrue();
|
return isWriteable() && super.isEditable();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isEnabled() {
|
// implementing
|
||||||
return bindingContext.getEnabledCondition().isTrue();
|
|
||||||
}
|
protected ValueModel getValueModel() {
|
||||||
|
return new ValueModel() {
|
||||||
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() {
|
|
||||||
public Object getValue() {
|
public Object getValue() {
|
||||||
return ReflectionUtils.invokeMethod(property.getReadMethod(), object);
|
return ReflectionUtils.invokeMethod(property.getReadMethod(), object);
|
||||||
}
|
}
|
||||||
|
|
@ -266,6 +39,7 @@ public class PropertyBinding implements Binding {
|
||||||
return property.getPropertyType();
|
return property.getPropertyType();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
public TypeDescriptor<?> getValueTypeDescriptor() {
|
public TypeDescriptor<?> getValueTypeDescriptor() {
|
||||||
return new TypeDescriptor(new MethodParameter(property.getReadMethod(), -1));
|
return new TypeDescriptor(new MethodParameter(property.getReadMethod(), -1));
|
||||||
}
|
}
|
||||||
|
|
@ -275,232 +49,10 @@ 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
|
// internal helpers
|
||||||
|
|
||||||
private String format(Object value, Formatter formatter) {
|
private boolean isWriteable() {
|
||||||
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() {
|
|
||||||
return property.getWriteMethod() != null;
|
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -46,5 +46,9 @@ public class PropertyPath implements Iterable<PropertyPathElement> {
|
||||||
public Iterator<PropertyPathElement> iterator() {
|
public Iterator<PropertyPathElement> iterator() {
|
||||||
return elements.iterator();
|
return elements.iterator();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
return elements.toString();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -25,4 +25,8 @@ public class PropertyPathElement {
|
||||||
public int getIntValue() {
|
public int getIntValue() {
|
||||||
return Integer.parseInt(value);
|
return Integer.parseInt(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
return value + ";index=" + index;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
*/
|
*/
|
||||||
package org.springframework.ui.binding.support;
|
package org.springframework.ui.binding.support;
|
||||||
|
|
||||||
import org.springframework.ui.binding.Binding.Model;
|
import org.springframework.ui.binding.support.AbstractBinding.ValueModel;
|
||||||
|
|
||||||
class ValueBuffer {
|
class ValueBuffer {
|
||||||
|
|
||||||
|
|
@ -11,13 +11,13 @@ class ValueBuffer {
|
||||||
|
|
||||||
private boolean hasValue;
|
private boolean hasValue;
|
||||||
|
|
||||||
private Model model;
|
private ValueModel model;
|
||||||
|
|
||||||
private boolean flushFailed;
|
private boolean flushFailed;
|
||||||
|
|
||||||
private Exception flushException;
|
private Exception flushException;
|
||||||
|
|
||||||
public ValueBuffer(Model model) {
|
public ValueBuffer(ValueModel model) {
|
||||||
this.model = model;
|
this.model = model;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,7 @@ public class WebBinder extends GenericBinder {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Object getEmptyValue(PropertyBinding binding) {
|
protected Object getEmptyValue(PropertyBinding binding) {
|
||||||
Class<?> type = binding.getModel().getValueType();
|
Class<?> type = binding.getValueModel().getValueType();
|
||||||
if (boolean.class.equals(type) || Boolean.class.equals(type)) {
|
if (boolean.class.equals(type) || Boolean.class.equals(type)) {
|
||||||
return Boolean.FALSE;
|
return Boolean.FALSE;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,6 @@ public class GenericBinderTests {
|
||||||
values.put("integer", "3");
|
values.put("integer", "3");
|
||||||
values.put("foo", "BAR");
|
values.put("foo", "BAR");
|
||||||
BindingResults results = binder.bind(values);
|
BindingResults results = binder.bind(values);
|
||||||
System.out.println(results);
|
|
||||||
assertEquals(3, results.size());
|
assertEquals(3, results.size());
|
||||||
|
|
||||||
assertEquals("string", results.get(0).getProperty());
|
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[0]", "4655 Macy Lane:Melbourne:FL:35452");
|
||||||
values.put("addresses[1]", "1234 Rostock Circle:Palm Bay:FL:32901");
|
values.put("addresses[1]", "1234 Rostock Circle:Palm Bay:FL:32901");
|
||||||
values.put("addresses[5]", "1977 Bel Aire Estates:Coker:AL:12345");
|
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());
|
Assert.assertEquals(6, bean.addresses.size());
|
||||||
assertEquals("4655 Macy Lane", bean.addresses.get(0).street);
|
assertEquals("4655 Macy Lane", bean.addresses.get(0).street);
|
||||||
assertEquals("Melbourne", bean.addresses.get(0).city);
|
assertEquals("Melbourne", bean.addresses.get(0).city);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue