formatter registry
This commit is contained in:
@@ -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 <code>null</code> 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.
|
||||
* <p>
|
||||
* 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);
|
||||
|
||||
}
|
||||
@@ -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<String, Binding> bindings;
|
||||
|
||||
private Map<Class, Formatter> typeFormatters = new HashMap<Class, Formatter>();
|
||||
|
||||
private Map<Class, AnnotationFormatterFactory> annotationFormatters = new HashMap<Class, AnnotationFormatterFactory>();
|
||||
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<String, ? extends Object> userMap) {
|
||||
UserValues values = new UserValues(userMap.size());
|
||||
for (Map.Entry<String, ? extends Object> 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 <A> 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) {
|
||||
|
||||
@@ -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<Class, Formatter> typeFormatters = new HashMap<Class, Formatter>();
|
||||
|
||||
private Map<Class, AnnotationFormatterFactory> annotationFormatters = new HashMap<Class, AnnotationFormatterFactory>();
|
||||
|
||||
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 <A> 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user