diff --git a/org.springframework.context/src/main/java/org/springframework/ui/binding/FormatterRegistry.java b/org.springframework.context/src/main/java/org/springframework/ui/binding/FormatterRegistry.java
new file mode 100644
index 0000000000..64ae12c647
--- /dev/null
+++ b/org.springframework.context/src/main/java/org/springframework/ui/binding/FormatterRegistry.java
@@ -0,0 +1,40 @@
+package org.springframework.ui.binding;
+
+import org.springframework.core.convert.TypeDescriptor;
+import org.springframework.ui.format.AnnotationFormatterFactory;
+import org.springframework.ui.format.Formatter;
+
+/**
+ * A centralized registry of Formatters indexed by property types.
+ *
+ * @author Keith Donald
+ */
+public interface FormatterRegistry {
+
+ /**
+ * Get the Formatter for the property type.
+ * @param propertyType the property type descriptor, which provides additional property metadata.
+ * @return the Formatter, or null if none is registered
+ */
+ Formatter> getFormatter(TypeDescriptor> propertyType);
+
+ /**
+ * 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.
+ * The type may be an annotation type, which will have the Formatter format values of properties annotated with that annotation.
+ * Use {@link #add(AnnotationFormatterFactory)} when the format annotation defines configurable annotation instance values.
+ *
+ * Note the Formatter's formatted object type does not have to equal the associated property type.
+ * When the property type differs from the formatted object type, the caller of the Formatter is expected to coerse a property value to the type expected by the Formatter.
+ * @param formatter the formatter
+ * @param propertyType the type
+ */
+ void add(Formatter> formatter, Class> propertyType);
+
+ /**
+ * Adds a AnnotationFormatterFactory that will format values of properties annotated with a specific annotation.
+ * @param factory the annotation formatter factory
+ */
+ void add(AnnotationFormatterFactory, ?> factory);
+
+}
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 c708d0c046..8b46ed9571 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,7 +15,6 @@
*/
package org.springframework.ui.binding.support;
-import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
@@ -49,13 +48,14 @@ import org.springframework.ui.binding.Binder;
import org.springframework.ui.binding.Binding;
import org.springframework.ui.binding.BindingConfiguration;
import org.springframework.ui.binding.BindingResult;
+import org.springframework.ui.binding.FormatterRegistry;
import org.springframework.ui.binding.UserValue;
import org.springframework.ui.binding.UserValues;
import org.springframework.ui.format.AnnotationFormatterFactory;
import org.springframework.ui.format.Formatter;
/**
- * Binds user-entered values to properties of a model object.
+ * A generic {@link Binder binder} suitable for use in most binding environments.
* @author Keith Donald
* @see #add(BindingConfiguration)
* @see #bind(UserValues)
@@ -69,9 +69,7 @@ public class GenericBinder implements Binder {
private Map bindings;
- private Map typeFormatters = new HashMap();
-
- private Map annotationFormatters = new HashMap();
+ private FormatterRegistry formatterRegistry = new GenericFormatterRegistry();
private ExpressionParser expressionParser;
@@ -79,23 +77,7 @@ public class GenericBinder implements Binder {
private boolean strict = false;
- private static Formatter defaultFormatter = new Formatter() {
- public String format(Object object, Locale locale) {
- if (object == null) {
- return "";
- } else {
- return object.toString();
- }
- }
-
- public Object parse(String formatted, Locale locale) throws ParseException {
- if (formatted == "") {
- return null;
- } else {
- return formatted;
- }
- }
- };
+ private static Formatter defaultFormatter = new DefaultFormatter();
/**
* Creates a new binder for the model object.
@@ -130,15 +112,11 @@ public class GenericBinder implements Binder {
}
public void add(Formatter> formatter, Class> propertyType) {
- if (propertyType.isAnnotation()) {
- annotationFormatters.put(propertyType, new SimpleAnnotationFormatterFactory(formatter));
- } else {
- typeFormatters.put(propertyType, formatter);
- }
+ formatterRegistry.add(formatter, propertyType);
}
public void add(AnnotationFormatterFactory, ?> factory) {
- annotationFormatters.put(getAnnotationType(factory), factory);
+ formatterRegistry.add(factory);
}
public Binding getBinding(String property) {
@@ -158,7 +136,7 @@ public class GenericBinder implements Binder {
}
return results;
}
-
+
public UserValues createUserValues(Map userMap) {
UserValues values = new UserValues(userMap.size());
for (Map.Entry entry : userMap.entrySet()) {
@@ -181,7 +159,7 @@ public class GenericBinder implements Binder {
}
// implementing Binding
-
+
public String getValue() {
Object value;
try {
@@ -201,13 +179,14 @@ public class GenericBinder implements Binder {
return setObjectValue(value);
}
}
-
+
public String format(Object selectableValue) {
Formatter formatter;
try {
formatter = getFormatter();
} catch (EvaluationException e) {
- throw new IllegalStateException("Failed to get property expression value type - this should not happen", e);
+ throw new IllegalStateException(
+ "Failed to get property expression value type - this should not happen", e);
}
Class> formattedType = getFormattedObjectType(formatter);
selectableValue = typeConverter.convert(selectableValue, formattedType);
@@ -244,21 +223,22 @@ public class GenericBinder implements Binder {
}
return formattedValues;
}
-
+
// public impl only
-
+
public Class getValueType() {
Class type;
- try {
+ try {
type = property.getValueType(createEvaluationContext());
} catch (EvaluationException e) {
- throw new IllegalArgumentException("Failed to get property expression value type - this should not happen", e);
+ throw new IllegalArgumentException(
+ "Failed to get property expression value type - this should not happen", e);
}
return type;
}
-
+
// internal helpers
-
+
private BindingResult setStringValue(String formatted) {
Formatter formatter;
try {
@@ -276,7 +256,7 @@ public class GenericBinder implements Binder {
}
return setValue(parsed, formatted);
}
-
+
private BindingResult setStringValues(String[] formatted) {
Formatter formatter;
try {
@@ -285,7 +265,7 @@ public class GenericBinder implements Binder {
// could occur the property was not found or is not readable
// TODO probably should not handle all EL failures, only type conversion & property not found?
return new ExpressionEvaluationErrorResult(property.getExpressionString(), formatted, e);
- }
+ }
Class parsedType = getFormattedObjectType(formatter);
if (parsedType == null) {
parsedType = String.class;
@@ -302,7 +282,7 @@ public class GenericBinder implements Binder {
}
return setValue(parsed, formatted);
}
-
+
private BindingResult setObjectValue(Object value) {
return setValue(value, value);
}
@@ -311,28 +291,12 @@ public class GenericBinder implements Binder {
if (formatter != null) {
return formatter;
} else {
- Class> type = property.getValueType(createEvaluationContext());
- Formatter> formatter = typeFormatters.get(type);
- if (formatter != null) {
- return formatter;
- } else {
- Annotation[] annotations = getAnnotations();
- for (Annotation a : annotations) {
- AnnotationFormatterFactory factory = annotationFormatters.get(a.annotationType());
- if (factory != null) {
- return factory.getFormatter(a);
- }
- }
- return defaultFormatter;
- }
+ Formatter> formatter = formatterRegistry.getFormatter(property
+ .getValueTypeDescriptor(createEvaluationContext()));
+ return formatter != null ? formatter : defaultFormatter;
}
}
-
- private Annotation[] getAnnotations() throws EvaluationException {
- return property.getValueTypeDescriptor(createEvaluationContext()).getAnnotations();
- }
-
private void copy(Iterable> values, String[] formattedValues) {
int i = 0;
for (Object value : values) {
@@ -349,7 +313,6 @@ public class GenericBinder implements Binder {
return new ExpressionEvaluationErrorResult(property.getExpressionString(), userValue, e);
}
}
-
private EvaluationContext createEvaluationContext() {
StandardEvaluationContext context = new StandardEvaluationContext();
@@ -361,25 +324,6 @@ public class GenericBinder implements Binder {
}
- private Class getAnnotationType(AnnotationFormatterFactory factory) {
- Class classToIntrospect = factory.getClass();
- while (classToIntrospect != null) {
- Type[] genericInterfaces = classToIntrospect.getGenericInterfaces();
- for (Type genericInterface : genericInterfaces) {
- if (genericInterface instanceof ParameterizedType) {
- ParameterizedType pInterface = (ParameterizedType) genericInterface;
- if (AnnotationFormatterFactory.class.isAssignableFrom((Class) pInterface.getRawType())) {
- return getParameterClass(pInterface.getActualTypeArguments()[0], factory.getClass());
- }
- }
- }
- classToIntrospect = classToIntrospect.getSuperclass();
- }
- throw new IllegalArgumentException(
- "Unable to extract Annotation type A argument from AnnotationFormatterFactory ["
- + factory.getClass().getName() + "]; does the factory parameterize the generic type?");
- }
-
private Class getFormattedObjectType(Formatter formatter) {
// TODO consider caching this info
Class classToIntrospect = formatter.getClass();
@@ -409,18 +353,22 @@ public class GenericBinder implements Binder {
+ "] on Formatter [" + converterClass.getName() + "]");
}
- static class SimpleAnnotationFormatterFactory implements AnnotationFormatterFactory {
-
- private Formatter formatter;
-
- public SimpleAnnotationFormatterFactory(Formatter formatter) {
- this.formatter = formatter;
+ static class DefaultFormatter implements Formatter {
+ public String format(Object object, Locale locale) {
+ if (object == null) {
+ return "";
+ } else {
+ return object.toString();
+ }
}
- public Formatter getFormatter(Annotation annotation) {
- return formatter;
+ public Object parse(String formatted, Locale locale) throws ParseException {
+ if (formatted == "") {
+ return null;
+ } else {
+ return formatted;
+ }
}
-
}
static class InvalidFormatResult implements BindingResult {
@@ -481,7 +429,7 @@ public class GenericBinder implements Binder {
}
public String getErrorCode() {
- SpelMessage spelCode = ((SpelEvaluationException)e).getMessageCode();
+ SpelMessage spelCode = ((SpelEvaluationException) e).getMessageCode();
if (spelCode==SpelMessage.EXCEPTION_DURING_PROPERTY_WRITE) {
return "typeConversionFailure";
} else if (spelCode==SpelMessage.PROPERTY_OR_FIELD_NOT_READABLE) {
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
new file mode 100644
index 0000000000..bc65602d5f
--- /dev/null
+++ b/org.springframework.context/src/main/java/org/springframework/ui/binding/support/GenericFormatterRegistry.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2004-2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.ui.binding.support;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.springframework.core.GenericTypeResolver;
+import org.springframework.core.convert.TypeDescriptor;
+import org.springframework.ui.binding.FormatterRegistry;
+import org.springframework.ui.format.AnnotationFormatterFactory;
+import org.springframework.ui.format.Formatter;
+
+/**
+ * A generic implementation of {@link FormatterRegistry} suitable for use in most binding environments.
+ * @author Keith Donald
+ * @see #add(Formatter, Class)
+ * @see #add(AnnotationFormatterFactory)
+ */
+@SuppressWarnings("unchecked")
+public class GenericFormatterRegistry implements FormatterRegistry {
+
+ private Map typeFormatters = new HashMap();
+
+ private Map annotationFormatters = new HashMap();
+
+ public Formatter> getFormatter(Class> propertyType) {
+ if (propertyType.isAnnotation()) {
+ return annotationFormatters.get(propertyType).getFormatter(null);
+ } else {
+ return typeFormatters.get(propertyType);
+ }
+ }
+
+ public Formatter> getFormatter(TypeDescriptor> propertyType) {
+ Formatter> formatter = typeFormatters.get(propertyType.getType());
+ if (formatter != null) {
+ return formatter;
+ } else {
+ Annotation[] annotations = propertyType.getAnnotations();
+ for (Annotation a : annotations) {
+ AnnotationFormatterFactory factory = annotationFormatters.get(a.annotationType());
+ if (factory != null) {
+ return factory.getFormatter(a);
+ }
+ }
+ return null;
+ }
+ }
+
+ public void add(Formatter> formatter, Class> propertyType) {
+ if (propertyType.isAnnotation()) {
+ annotationFormatters.put(propertyType, new SimpleAnnotationFormatterFactory(formatter));
+ } else {
+ typeFormatters.put(propertyType, formatter);
+ }
+ }
+
+ public void add(AnnotationFormatterFactory, ?> factory) {
+ annotationFormatters.put(getAnnotationType(factory), factory);
+ }
+
+ // internal helpers
+
+ private Class getAnnotationType(AnnotationFormatterFactory factory) {
+ Class classToIntrospect = factory.getClass();
+ while (classToIntrospect != null) {
+ Type[] genericInterfaces = classToIntrospect.getGenericInterfaces();
+ for (Type genericInterface : genericInterfaces) {
+ if (genericInterface instanceof ParameterizedType) {
+ ParameterizedType pInterface = (ParameterizedType) genericInterface;
+ if (AnnotationFormatterFactory.class.isAssignableFrom((Class) pInterface.getRawType())) {
+ return getParameterClass(pInterface.getActualTypeArguments()[0], factory.getClass());
+ }
+ }
+ }
+ classToIntrospect = classToIntrospect.getSuperclass();
+ }
+ throw new IllegalArgumentException(
+ "Unable to extract Annotation type A argument from AnnotationFormatterFactory ["
+ + factory.getClass().getName() + "]; does the factory parameterize the generic type?");
+ }
+
+ private Class getParameterClass(Type parameterType, Class converterClass) {
+ if (parameterType instanceof TypeVariable) {
+ parameterType = GenericTypeResolver.resolveTypeVariable((TypeVariable) parameterType, converterClass);
+ }
+ if (parameterType instanceof Class) {
+ return (Class) parameterType;
+ }
+ throw new IllegalArgumentException("Unable to obtain the java.lang.Class for parameterType [" + parameterType
+ + "] on Formatter [" + converterClass.getName() + "]");
+ }
+
+ static class SimpleAnnotationFormatterFactory implements AnnotationFormatterFactory {
+
+ private Formatter formatter;
+
+ public SimpleAnnotationFormatterFactory(Formatter formatter) {
+ this.formatter = formatter;
+ }
+
+ public Formatter getFormatter(Annotation annotation) {
+ return formatter;
+ }
+
+ }
+
+}