From 3cd3cddbe0bce123959c2529ac326f8bccb950ea Mon Sep 17 00:00:00 2001 From: Keith Donald Date: Mon, 20 Jul 2009 14:26:29 +0000 Subject: [PATCH] type formatters --- .../ui/binding/support/BindingRule.java | 4 - .../binding/support/BindingStatusResult.java | 4 + .../ui/binding/support/FormatterRegistry.java | 12 +- .../ui/binding/support/GenericBinder.java | 127 +++++++++++--- .../support/GenericFormatterRegistry.java | 16 +- .../ui/binding/support/PropertyBinding.java | 157 ++++++++---------- .../binding/support/GenericBinderTests.java | 15 +- 7 files changed, 204 insertions(+), 131 deletions(-) diff --git a/org.springframework.context/src/main/java/org/springframework/ui/binding/support/BindingRule.java b/org.springframework.context/src/main/java/org/springframework/ui/binding/support/BindingRule.java index 3bfe4ec2b41..4e344ab715a 100644 --- a/org.springframework.context/src/main/java/org/springframework/ui/binding/support/BindingRule.java +++ b/org.springframework.context/src/main/java/org/springframework/ui/binding/support/BindingRule.java @@ -1,6 +1,5 @@ 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 diff --git a/org.springframework.context/src/main/java/org/springframework/ui/binding/support/BindingStatusResult.java b/org.springframework.context/src/main/java/org/springframework/ui/binding/support/BindingStatusResult.java index 5982181f434..0b4222ef5d8 100644 --- a/org.springframework.context/src/main/java/org/springframework/ui/binding/support/BindingStatusResult.java +++ b/org.springframework.context/src/main/java/org/springframework/ui/binding/support/BindingStatusResult.java @@ -37,4 +37,8 @@ class BindingStatusResult implements BindingResult { return bindingStatusAlert; } + public String toString() { + return getAlert().toString(); + } + } \ No newline at end of file diff --git a/org.springframework.context/src/main/java/org/springframework/ui/binding/support/FormatterRegistry.java b/org.springframework.context/src/main/java/org/springframework/ui/binding/support/FormatterRegistry.java index a6aba4a16ff..ad5440c9280 100644 --- a/org.springframework.context/src/main/java/org/springframework/ui/binding/support/FormatterRegistry.java +++ b/org.springframework.context/src/main/java/org/springframework/ui/binding/support/FormatterRegistry.java @@ -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 null 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. diff --git a/org.springframework.context/src/main/java/org/springframework/ui/binding/support/GenericBinder.java b/org.springframework.context/src/main/java/org/springframework/ui/binding/support/GenericBinder.java index dba6d604d93..ea7dfa02907 100644 --- a/org.springframework.context/src/main/java/org/springframework/ui/binding/support/GenericBinder.java +++ b/org.springframework.context/src/main/java/org/springframework/ui/binding/support/GenericBinder.java @@ -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 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(); + 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 Formatter formatter = DefaultFormatter.INSTANCE; + private PropertyDescriptor property; - private Formatter elementFormatter = DefaultFormatter.INSTANCE; + private Formatter formatter; - private Formatter keyFormatter = DefaultFormatter.INSTANCE; + private Formatter elementFormatter; + + 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(); + } } diff --git a/org.springframework.context/src/main/java/org/springframework/ui/binding/support/GenericFormatterRegistry.java b/org.springframework.context/src/main/java/org/springframework/ui/binding/support/GenericFormatterRegistry.java index 7ab1151375c..0074c6c49dc 100644 --- a/org.springframework.context/src/main/java/org/springframework/ui/binding/support/GenericFormatterRegistry.java +++ b/org.springframework.context/src/main/java/org/springframework/ui/binding/support/GenericFormatterRegistry.java @@ -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 annotationFormatters = new HashMap(); - 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)); diff --git a/org.springframework.context/src/main/java/org/springframework/ui/binding/support/PropertyBinding.java b/org.springframework.context/src/main/java/org/springframework/ui/binding/support/PropertyBinding.java index a7da51bd822..54070bec213 100644 --- a/org.springframework.context/src/main/java/org/springframework/ui/binding/support/PropertyBinding.java +++ b/org.springframework.context/src/main/java/org/springframework/ui/binding/support/PropertyBinding.java @@ -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 diff --git a/org.springframework.context/src/test/java/org/springframework/ui/binding/support/GenericBinderTests.java b/org.springframework.context/src/test/java/org/springframework/ui/binding/support/GenericBinderTests.java index 461bc9b59c9..2968c2d3432 100644 --- a/org.springframework.context/src/test/java/org/springframework/ui/binding/support/GenericBinderTests.java +++ b/org.springframework.context/src/test/java/org/springframework/ui/binding/support/GenericBinderTests.java @@ -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");