提交 3cd3cddb 编写于 作者: K Keith Donald

type formatters

上级 b2c723a7
package org.springframework.ui.binding.support;
import org.springframework.ui.binding.Binding;
import org.springframework.ui.binding.config.Condition;
import org.springframework.ui.format.Formatter;
......@@ -17,8 +16,5 @@ public interface BindingRule {
Condition getEnabledCondition();
Condition getVisibleCondition();
// TODO - does this belong here?
Binding getBinding(String property, Object model);
}
\ No newline at end of file
......@@ -37,4 +37,8 @@ class BindingStatusResult implements BindingResult {
return bindingStatusAlert;
}
public String toString() {
return getAlert().toString();
}
}
\ No newline at end of file
......@@ -15,7 +15,8 @@
*/
package org.springframework.ui.binding.support;
import org.springframework.core.convert.TypeDescriptor;
import java.beans.PropertyDescriptor;
import org.springframework.ui.format.AnnotationFormatterFactory;
import org.springframework.ui.format.Formatter;
......@@ -28,13 +29,14 @@ import org.springframework.ui.format.Formatter;
*/
public interface FormatterRegistry {
Formatter<?> getFormatter(PropertyDescriptor property);
/**
* Get the Formatter for the property type.
* @param propertyType the property type descriptor, which provides additional property metadata.
* Get the Formatter for the type.
* @return the Formatter, or <code>null</code> if none is registered
*/
Formatter<?> getFormatter(TypeDescriptor<?> propertyType);
Formatter<?> getFormatter(Class<?> type);
/**
* Adds a Formatter that will format the values of properties of the provided type.
* The type should generally be a concrete class for a scalar value, such as BigDecimal, and not a collection value.
......
......@@ -15,11 +15,17 @@
*/
package org.springframework.ui.binding.support;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.util.HashMap;
import java.util.Map;
import org.springframework.context.MessageSource;
import org.springframework.core.GenericCollectionTypeResolver;
import org.springframework.core.convert.TypeConverter;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.support.DefaultTypeConverter;
import org.springframework.ui.binding.Binder;
import org.springframework.ui.binding.Binding;
......@@ -45,6 +51,8 @@ public class GenericBinder implements Binder {
private Map<String, GenericBindingRule> bindingRules;
private FormatterRegistry formatterRegistry;
private TypeConverter typeConverter;
private MessageSource messageSource;
......@@ -57,9 +65,20 @@ public class GenericBinder implements Binder {
Assert.notNull(model, "The model to bind to is required");
this.model = model;
bindingRules = new HashMap<String, GenericBindingRule>();
formatterRegistry = new GenericFormatterRegistry();
typeConverter = new DefaultTypeConverter();
}
/**
* Configures the registry of Formatters to query when no explicit Formatter has been registered for a Binding.
* Allows Formatters to be applied by property type and by property annotation.
* @param registry the formatter registry
*/
public void setFormatterRegistry(FormatterRegistry formatterRegistry) {
Assert.notNull(formatterRegistry, "The FormatterRegistry is required");
this.formatterRegistry = formatterRegistry;
}
/**
* Configure the MessageSource that resolves localized {@link BindingResult} alert messages.
* @param messageSource the message source
......@@ -148,7 +167,7 @@ public class GenericBinder implements Binder {
private GenericBindingRule getBindingRule(String property) {
GenericBindingRule rule = bindingRules.get(property);
if (rule == null) {
rule = new GenericBindingRule(property);
rule = new GenericBindingRule(property, model.getClass());
bindingRules.put(property, rule);
}
return rule;
......@@ -169,15 +188,17 @@ public class GenericBinder implements Binder {
}
@SuppressWarnings("unchecked")
class GenericBindingRule implements BindingRule, BindingRuleConfiguration {
class GenericBindingRule implements BindingRuleConfiguration, BindingContext {
private String property;
private Class<?> modelClass;
private PropertyDescriptor property;
private Formatter formatter = DefaultFormatter.INSTANCE;
private Formatter formatter;
private Formatter elementFormatter = DefaultFormatter.INSTANCE;
private Formatter elementFormatter;
private Formatter keyFormatter = DefaultFormatter.INSTANCE;
private Formatter keyFormatter;
private Condition editableCondition = Condition.ALWAYS_TRUE;
......@@ -189,26 +210,39 @@ public class GenericBinder implements Binder {
private Binding binding;
public GenericBindingRule(String property) {
this.property = property;
public GenericBindingRule(String property, Class modelClass) {
this.modelClass = modelClass;
this.property = findPropertyDescriptor(property);
}
// implementing BindingRule
public Binding getBinding(String property, Object model) {
return getBindingRule(property).getBinding(model);
}
// implementing BindingContext
public TypeConverter getTypeConverter() {
return typeConverter;
}
public Formatter<?> getFormatter() {
return formatter;
if (formatter != null) {
return formatter;
} else {
return formatterRegistry.getFormatter(property);
}
}
public Formatter<?> getElementFormatter() {
return elementFormatter;
if (elementFormatter != null) {
return formatter;
} else {
return formatterRegistry.getFormatter(getElementType());
}
}
public Formatter<?> getKeyFormatter() {
return keyFormatter;
if (keyFormatter != null) {
return keyFormatter;
} else {
return formatterRegistry.getFormatter(getKeyType());
}
}
public Condition getEnabledCondition() {
......@@ -223,8 +257,12 @@ public class GenericBinder implements Binder {
return visibleCondition;
}
public Binding getBinding(String property, Object model) {
return getBindingRule(property).getBinding(model);
}
// implementing BindingRuleConfiguration
public BindingRuleConfiguration formatWith(Formatter<?> formatter) {
this.formatter = formatter;
return this;
......@@ -255,10 +293,20 @@ public class GenericBinder implements Binder {
return this;
}
// internal helpers
private Class<?> getElementType() {
return GenericCollectionTypeResolver.getCollectionReturnType(property.getReadMethod());
}
private Class<?> getKeyType() {
return GenericCollectionTypeResolver.getMapKeyReturnType(property.getReadMethod());
}
GenericBindingRule getBindingRule(String property) {
GenericBindingRule rule = nestedBindingRules.get(property);
if (rule == null) {
rule = new GenericBindingRule(property);
rule = new GenericBindingRule(property, this.property.getPropertyType());
nestedBindingRules.put(property, rule);
}
return rule;
......@@ -266,11 +314,52 @@ public class GenericBinder implements Binder {
Binding getBinding(Object model) {
if (binding == null) {
binding = new PropertyBinding(property, model, typeConverter, this);
binding = new PropertyBinding(property, model, this);
}
return binding;
}
private PropertyDescriptor findPropertyDescriptor(String property) {
PropertyDescriptor[] propDescs = getBeanInfo(modelClass).getPropertyDescriptors();
for (PropertyDescriptor propDesc : propDescs) {
if (propDesc.getName().equals(property)) {
return propDesc;
}
}
throw new IllegalArgumentException("No property '" + property + "' found on model ["
+ modelClass.getName() + "]");
}
private BeanInfo getBeanInfo(Class<?> clazz) {
try {
return Introspector.getBeanInfo(clazz);
} catch (IntrospectionException e) {
throw new IllegalStateException("Unable to introspect model type " + clazz);
}
}
}
public interface BindingContext {
TypeConverter getTypeConverter();
Condition getEditableCondition();
Condition getEnabledCondition();
Condition getVisibleCondition();
Binding getBinding(String property, Object model);
@SuppressWarnings("unchecked")
Formatter getFormatter();
@SuppressWarnings("unchecked")
Formatter getElementFormatter();
@SuppressWarnings("unchecked")
Formatter getKeyFormatter();
}
}
......@@ -15,6 +15,7 @@
*/
package org.springframework.ui.binding.support;
import java.beans.PropertyDescriptor;
import java.lang.annotation.Annotation;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
......@@ -24,6 +25,7 @@ import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.core.GenericTypeResolver;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.ui.format.AnnotationFormatterFactory;
......@@ -46,7 +48,8 @@ public class GenericFormatterRegistry implements FormatterRegistry {
private Map<Class, AnnotationFormatterFactory> annotationFormatters = new HashMap<Class, AnnotationFormatterFactory>();
public Formatter<?> getFormatter(TypeDescriptor<?> propertyType) {
public Formatter<?> getFormatter(PropertyDescriptor property) {
TypeDescriptor<?> propertyType = new TypeDescriptor(new MethodParameter(property.getReadMethod(), -1));
Annotation[] annotations = propertyType.getAnnotations();
for (Annotation a : annotations) {
AnnotationFormatterFactory factory = annotationFormatters.get(a.annotationType());
......@@ -66,7 +69,11 @@ public class GenericFormatterRegistry implements FormatterRegistry {
} else {
type = propertyType.getType();
}
formatter = typeFormatters.get(type);
return getFormatter(type);
}
public Formatter<?> getFormatter(Class<?> type) {
Formatter formatter = typeFormatters.get(type);
if (formatter != null) {
return formatter;
} else {
......@@ -84,11 +91,12 @@ public class GenericFormatterRegistry implements FormatterRegistry {
typeFormatters.put(type, formatter);
return formatter;
} else {
return null;
return DefaultFormatter.INSTANCE;
}
}
}
}
public void add(Class<?> propertyType, Formatter<?> formatter) {
if (propertyType.isAnnotation()) {
annotationFormatters.put(propertyType, new SimpleAnnotationFormatterFactory(formatter));
......
......@@ -3,9 +3,6 @@
*/
package org.springframework.ui.binding.support;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Array;
import java.lang.reflect.ParameterizedType;
......@@ -26,36 +23,32 @@ import org.springframework.core.convert.TypeDescriptor;
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.util.ReflectionUtils;
@SuppressWarnings("unchecked")
public class PropertyBinding implements Binding {
private String property;
private PropertyDescriptor property;
private Object model;
private Object object;
private TypeConverter typeConverter;
private BindingRule bindingRule;
private PropertyDescriptor propertyDescriptor;
private BindingContext bindingContext;
private Object sourceValue;
@SuppressWarnings("unused")
private ParseException sourceValueParseException;
private ValueBuffer buffer;
private BindingStatus status;
public PropertyBinding(String property, Object model, TypeConverter typeConverter, BindingRule bindingRule) {
initProperty(property, model);
this.model = model;
this.typeConverter = typeConverter;
this.bindingRule = bindingRule;
public PropertyBinding(PropertyDescriptor property, Object object, BindingContext bindingContext) {
this.property = property;
this.object = object;
this.bindingContext = bindingContext;
this.buffer = new ValueBuffer(getModel());
status = BindingStatus.CLEAN;
}
......@@ -71,23 +64,23 @@ public class PropertyBinding implements Binding {
}
public boolean isEditable() {
return isWriteableProperty() && bindingRule.getEditableCondition().isTrue();
return isWriteableProperty() && bindingContext.getEditableCondition().isTrue();
}
public boolean isEnabled() {
return bindingRule.getEnabledCondition().isTrue();
return bindingContext.getEnabledCondition().isTrue();
}
public boolean isVisible() {
return bindingRule.getVisibleCondition().isTrue();
return bindingContext.getVisibleCondition().isTrue();
}
public void applySourceValue(Object sourceValue) {
assertEditable();
assertEnabled();
if (sourceValue instanceof String) {
try {
buffer.setValue(bindingRule.getFormatter().parse((String) sourceValue, getLocale()));
try {
buffer.setValue(bindingContext.getFormatter().parse((String) sourceValue, getLocale()));
sourceValue = null;
status = BindingStatus.DIRTY;
} catch (ParseException e) {
......@@ -97,7 +90,7 @@ public class PropertyBinding implements Binding {
}
} else if (sourceValue instanceof String[]) {
String[] sourceValues = (String[]) sourceValue;
Class<?> parsedType = getFormattedObjectType(bindingRule.getElementFormatter().getClass());
Class<?> parsedType = getFormattedObjectType(bindingContext.getElementFormatter().getClass());
if (parsedType == null) {
parsedType = String.class;
}
......@@ -105,7 +98,8 @@ public class PropertyBinding implements Binding {
for (int i = 0; i < sourceValues.length; i++) {
Object parsedValue;
try {
parsedValue = bindingRule.getElementFormatter().parse(sourceValues[i], LocaleContextHolder.getLocale());
parsedValue = bindingContext.getElementFormatter().parse(sourceValues[i],
LocaleContextHolder.getLocale());
Array.set(parsed, i, parsedValue);
} catch (ParseException e) {
this.sourceValue = sourceValue;
......@@ -121,14 +115,14 @@ public class PropertyBinding implements Binding {
}
}
}
public BindingStatus getStatus() {
return status;
}
public Alert getStatusAlert() {
if (status == BindingStatus.INVALID_SOURCE_VALUE) {
return new Alert() {
return new AbstractAlert() {
public String getCode() {
return "typeMismatch";
}
......@@ -139,11 +133,11 @@ public class PropertyBinding implements Binding {
public Severity getSeverity() {
return Severity.ERROR;
}
}
};
} else if (status == BindingStatus.COMMIT_FAILURE) {
if (buffer.getFlushException() instanceof ConversionFailedException) {
return new Alert() {
return new AbstractAlert() {
public String getCode() {
return "typeMismatch";
}
......@@ -154,25 +148,25 @@ public class PropertyBinding implements Binding {
public Severity getSeverity() {
return Severity.ERROR;
}
};
}
};
} else {
return new Alert() {
return new AbstractAlert() {
public String getCode() {
return "internalError";
}
public String getMessage() {
return "Internal error occurred";
return "Internal error occurred " + buffer.getFlushException();
}
public Severity getSeverity() {
return Severity.FATAL;
}
};
}
};
}
} else if (status == BindingStatus.COMMITTED) {
return new Alert() {
return new AbstractAlert() {
public String getCode() {
return "bindSucces";
}
......@@ -183,8 +177,8 @@ public class PropertyBinding implements Binding {
public Severity getSeverity() {
return Severity.INFO;
}
};
}
};
} else {
return null;
}
......@@ -204,7 +198,7 @@ public class PropertyBinding implements Binding {
throw new IllegalStateException("Binding is not dirty; nothing to commit");
}
}
public void revert() {
if (status == BindingStatus.INVALID_SOURCE_VALUE) {
sourceValue = null;
......@@ -212,28 +206,29 @@ public class PropertyBinding implements Binding {
status = BindingStatus.CLEAN;
} else if (status == BindingStatus.DIRTY || status == BindingStatus.COMMIT_FAILURE) {
buffer.clear();
status = BindingStatus.CLEAN;
status = BindingStatus.CLEAN;
} else {
throw new IllegalStateException("Nothing to revert");
}
}
public Model getModel() {
return new Model() {
return new Model() {
public Object getValue() {
return ReflectionUtils.invokeMethod(propertyDescriptor.getReadMethod(), model);
return ReflectionUtils.invokeMethod(property.getReadMethod(), object);
}
public Class<?> getValueType() {
return propertyDescriptor.getPropertyType();
return property.getPropertyType();
}
public void setValue(Object value) {
TypeDescriptor targetType = new TypeDescriptor(new MethodParameter(propertyDescriptor.getWriteMethod(), 0));
if (value != null && typeConverter.canConvert(value.getClass(), targetType)) {
value = typeConverter.convert(value, targetType);
TypeDescriptor targetType = new TypeDescriptor(new MethodParameter(property.getWriteMethod(), 0));
TypeConverter converter = bindingContext.getTypeConverter();
if (value != null && converter.canConvert(value.getClass(), targetType)) {
value = converter.convert(value, targetType);
}
ReflectionUtils.invokeMethod(propertyDescriptor.getWriteMethod(), model, value);
ReflectionUtils.invokeMethod(property.getWriteMethod(), object, value);
}
};
}
......@@ -243,7 +238,7 @@ public class PropertyBinding implements Binding {
if (getValue() == null) {
createValue();
}
return bindingRule.getBinding(property, getValue());
return bindingContext.getBinding(property, getValue());
}
public boolean isList() {
......@@ -264,7 +259,7 @@ public class PropertyBinding implements Binding {
assertMapProperty();
if (key instanceof String) {
try {
key = bindingRule.getKeyFormatter().parse((String) key, getLocale());
key = bindingContext.getKeyFormatter().parse((String) key, getLocale());
} catch (ParseException e) {
throw new IllegalArgumentException("Invald key", e);
}
......@@ -276,41 +271,17 @@ public class PropertyBinding implements Binding {
public String formatValue(Object value) {
Formatter formatter;
if (isList() || isMap()) {
formatter = bindingRule.getElementFormatter();
formatter = bindingContext.getElementFormatter();
} else {
formatter = bindingRule.getFormatter();
formatter = bindingContext.getFormatter();
}
Class<?> formattedType = getFormattedObjectType(formatter.getClass());
value = typeConverter.convert(value, formattedType);
value = bindingContext.getTypeConverter().convert(value, formattedType);
return formatter.format(value, getLocale());
}
// internal helpers
private void initProperty(String property, Object model) {
this.propertyDescriptor = findPropertyDescriptor(property, model);
this.property = property;
}
private PropertyDescriptor findPropertyDescriptor(String property, Object model) {
PropertyDescriptor[] propDescs = getBeanInfo(model.getClass()).getPropertyDescriptors();
for (PropertyDescriptor propDesc : propDescs) {
if (propDesc.getName().equals(property)) {
return propDesc;
}
}
throw new IllegalArgumentException("No property '" + property + "' found on model ["
+ model.getClass().getName() + "]");
}
private BeanInfo getBeanInfo(Class<?> clazz) {
try {
return Introspector.getBeanInfo(clazz);
} catch (IntrospectionException e) {
throw new IllegalStateException("Unable to introspect model type " + clazz);
}
}
private Locale getLocale() {
return LocaleContextHolder.getLocale();
}
......@@ -320,11 +291,11 @@ public class PropertyBinding implements Binding {
Object value = getModel().getValueType().newInstance();
getModel().setValue(value);
} catch (InstantiationException e) {
throw new IllegalStateException("Could not lazily instantiate model of type [" + getModel().getValueType().getName()
+ "] to access property" + property, e);
throw new IllegalStateException("Could not lazily instantiate object of type ["
+ getModel().getValueType().getName() + "] to access property" + property, e);
} catch (IllegalAccessException e) {
throw new IllegalStateException("Could not lazily instantiate model of type [" + getModel().getValueType().getName()
+ "] to access property" + property, e);
throw new IllegalStateException("Could not lazily instantiate object of type ["
+ getModel().getValueType().getName() + "] to access property" + property, e);
}
}
......@@ -358,8 +329,7 @@ public class PropertyBinding implements Binding {
@SuppressWarnings("unused")
private CollectionTypeDescriptor getCollectionTypeDescriptor() {
Class<?> elementType = GenericCollectionTypeResolver.getCollectionReturnType(propertyDescriptor
.getReadMethod());
Class<?> elementType = GenericCollectionTypeResolver.getCollectionReturnType(property.getReadMethod());
return new CollectionTypeDescriptor(getModel().getValueType(), elementType);
}
......@@ -383,7 +353,7 @@ public class PropertyBinding implements Binding {
throw new IllegalStateException("Not a Map property binding");
}
}
private void assertEditable() {
if (!isEditable()) {
throw new IllegalStateException("Binding is not editable");
......@@ -397,6 +367,13 @@ public class PropertyBinding implements Binding {
}
private boolean isWriteableProperty() {
return propertyDescriptor.getWriteMethod() != null;
return property.getWriteMethod() != null;
}
static abstract class AbstractAlert implements Alert {
public String toString() {
return getCode() + " - " + getMessage();
}
}
}
\ No newline at end of file
......@@ -97,32 +97,29 @@ public class GenericBinderTests {
assertEquals(new DateFormatter().parse("2009-06-01", Locale.US), bean.getDate());
}
/*
@Test
public void bindSingleValuePropertyFormatterParseException() {
BindingRulesBuilder builder = new BindingRulesBuilder(TestBean.class);
builder.bind("date").formatWith(new DateFormatter());
GenericBinder binder = new GenericBinder(bean, builder.getBindingRules());
GenericBinder binder = new GenericBinder(bean);
binder.bindingRule("date").formatWith(new DateFormatter());
BindingResults results = binder.bind(Collections.singletonMap("date", "bogus"));
assertEquals(1, results.size());
assertTrue(results.get(0).isFailure());
assertEquals("invalidFormat", results.get(0).getAlert().getCode());
assertEquals("typeMismatch", results.get(0).getAlert().getCode());
}
@Test
public void bindSingleValueWithFormatterRegistedByType() throws ParseException {
BindingRulesBuilder builder = new BindingRulesBuilder(TestBean.class);
builder.bind("date").formatWith(new DateFormatter());
GenericBinder binder = new GenericBinder(bean, builder.getBindingRules());
GenericBinder binder = new GenericBinder(bean);
GenericFormatterRegistry formatterRegistry = new GenericFormatterRegistry();
formatterRegistry.add(Date.class, new DateFormatter());
binder.setFormatterRegistry(formatterRegistry);
binder.bind(Collections.singletonMap("date", "2009-06-01"));
assertEquals(new DateFormatter().parse("2009-06-01", Locale.US), bean.getDate());
}
/*
@Test
public void bindSingleValueWithAnnotationFormatterFactoryRegistered() throws ParseException {
binder.addBinding("currency");
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册