separated interface from impl
This commit is contained in:
parent
58e60fb844
commit
71cbd982e3
|
|
@ -15,34 +15,9 @@
|
||||||
*/
|
*/
|
||||||
package org.springframework.ui.binding;
|
package org.springframework.ui.binding;
|
||||||
|
|
||||||
import java.lang.annotation.Annotation;
|
|
||||||
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.Collection;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.springframework.context.expression.MapAccessor;
|
|
||||||
import org.springframework.context.i18n.LocaleContextHolder;
|
|
||||||
import org.springframework.core.GenericTypeResolver;
|
|
||||||
import org.springframework.core.convert.TypeConverter;
|
|
||||||
import org.springframework.core.convert.TypeDescriptor;
|
|
||||||
import org.springframework.core.convert.support.DefaultTypeConverter;
|
|
||||||
import org.springframework.expression.EvaluationContext;
|
|
||||||
import org.springframework.expression.EvaluationException;
|
|
||||||
import org.springframework.expression.Expression;
|
|
||||||
import org.springframework.expression.ExpressionException;
|
|
||||||
import org.springframework.expression.ExpressionParser;
|
|
||||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
|
||||||
import org.springframework.expression.spel.standard.SpelExpressionParserConfiguration;
|
|
||||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
|
||||||
import org.springframework.expression.spel.support.StandardTypeConverter;
|
|
||||||
import org.springframework.ui.format.AnnotationFormatterFactory;
|
import org.springframework.ui.format.AnnotationFormatterFactory;
|
||||||
import org.springframework.ui.format.Formatter;
|
import org.springframework.ui.format.Formatter;
|
||||||
|
|
||||||
|
|
@ -50,59 +25,17 @@ import org.springframework.ui.format.Formatter;
|
||||||
* Binds user-entered values to properties of a model object.
|
* Binds user-entered values to properties of a model object.
|
||||||
* @author Keith Donald
|
* @author Keith Donald
|
||||||
*
|
*
|
||||||
* @param <T> The type of model object this binder binds to
|
* @param <M> The kind of model object this binder binds to
|
||||||
* @see #add(BindingConfiguration)
|
* @see #add(BindingConfiguration)
|
||||||
* @see #bind(Map)
|
* @see #bind(Map)
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
public interface Binder<M> {
|
||||||
public class Binder<T> {
|
|
||||||
|
|
||||||
private static final String[] EMPTY_STRING_ARRAY = new String[0];
|
|
||||||
|
|
||||||
private T model;
|
|
||||||
|
|
||||||
private Map<String, Binding> bindings;
|
|
||||||
|
|
||||||
private Map<Class, Formatter> typeFormatters = new HashMap<Class, Formatter>();
|
|
||||||
|
|
||||||
private Map<Class, AnnotationFormatterFactory> annotationFormatters = new HashMap<Class, AnnotationFormatterFactory>();
|
|
||||||
|
|
||||||
private ExpressionParser expressionParser;
|
|
||||||
|
|
||||||
private TypeConverter typeConverter;
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new binder for the model object.
|
* The model object this binder binds to.
|
||||||
* @param model the model object containing properties this binder will bind to
|
* @return the model object
|
||||||
*/
|
*/
|
||||||
public Binder(T model) {
|
M getModel();
|
||||||
this.model = model;
|
|
||||||
bindings = new HashMap<String, Binding>();
|
|
||||||
int parserConfig = SpelExpressionParserConfiguration.CreateListsOnAttemptToIndexIntoNull
|
|
||||||
| SpelExpressionParserConfiguration.GrowListsOnIndexBeyondSize;
|
|
||||||
expressionParser = new SpelExpressionParser(parserConfig);
|
|
||||||
typeConverter = new DefaultTypeConverter();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configures if this binder is <i>strict</i>; a strict binder requires all bindings to be registered explicitly using {@link #add(BindingConfiguration)}.
|
* Configures if this binder is <i>strict</i>; a strict binder requires all bindings to be registered explicitly using {@link #add(BindingConfiguration)}.
|
||||||
|
|
@ -110,438 +43,39 @@ public class Binder<T> {
|
||||||
* Default is optimistic.
|
* Default is optimistic.
|
||||||
* @param strict strict binder status
|
* @param strict strict binder status
|
||||||
*/
|
*/
|
||||||
public void setStrict(boolean strict) {
|
void setStrict(boolean strict);
|
||||||
this.strict = strict;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds new binding.
|
* Adds new binding.
|
||||||
* @param binding the binding configuration
|
* @param binding the binding configuration
|
||||||
* @return the new binding created from the configuration provided
|
* @return the new binding created from the configuration provided
|
||||||
*/
|
*/
|
||||||
public Binding add(BindingConfiguration binding) {
|
Binding add(BindingConfiguration binding);
|
||||||
Binding newBinding;
|
|
||||||
try {
|
|
||||||
newBinding = new BindingImpl(binding);
|
|
||||||
} catch (org.springframework.expression.ParseException e) {
|
|
||||||
throw new IllegalArgumentException(e);
|
|
||||||
}
|
|
||||||
bindings.put(binding.getProperty(), newBinding);
|
|
||||||
return newBinding;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a Formatter that will format property values of type <code>propertyType</coe>.
|
* Adds a Formatter that will format property values of type <code>propertyType</coe>.
|
||||||
* @param formatter the formatter
|
* @param formatter the formatter
|
||||||
* @param propertyType the property type
|
* @param propertyType the property type
|
||||||
*/
|
*/
|
||||||
public void add(Formatter<?> formatter, Class<?> propertyType) {
|
void add(Formatter<?> formatter, Class<?> propertyType);
|
||||||
if (propertyType.isAnnotation()) {
|
|
||||||
annotationFormatters.put(propertyType, new SimpleAnnotationFormatterFactory(formatter));
|
|
||||||
} else {
|
|
||||||
typeFormatters.put(propertyType, formatter);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a AnnotationFormatterFactory that will format values of properties annotated with a specific annotation.
|
* Adds a AnnotationFormatterFactory that will format values of properties annotated with a specific annotation.
|
||||||
* @param factory the annotation formatter factory
|
* @param factory the annotation formatter factory
|
||||||
*/
|
*/
|
||||||
public void add(AnnotationFormatterFactory<?, ?> factory) {
|
void add(AnnotationFormatterFactory<?, ?> factory);
|
||||||
annotationFormatters.put(getAnnotationType(factory), factory);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The model object this binder binds to.
|
|
||||||
* @return the model object
|
|
||||||
*/
|
|
||||||
public T getModel() {
|
|
||||||
return model;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the binding for the property.
|
* Returns the binding for the property.
|
||||||
* @param property the property path
|
* @param property the property path
|
||||||
* @return the binding
|
* @return the binding
|
||||||
*/
|
*/
|
||||||
public Binding getBinding(String property) {
|
Binding getBinding(String property);
|
||||||
Binding binding = bindings.get(property);
|
|
||||||
if (binding == null && !strict) {
|
|
||||||
return add(new BindingConfiguration(property, null));
|
|
||||||
} else {
|
|
||||||
return binding;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bind values in the map to the properties of the model object.
|
* Bind values in the map to the properties of the model object.
|
||||||
* TODO return BindingResults with getSuccesses()/getErrors()/etc?
|
* @param userValues user-entered values to bind
|
||||||
* @param propertyValues the property values map
|
|
||||||
*/
|
*/
|
||||||
public List<BindingResult> bind(List<UserValue> userValues) {
|
List<BindingResult> bind(List<UserValue> userValues);
|
||||||
List<BindingResult> results = new ArrayList<BindingResult>(userValues.size());
|
|
||||||
for (UserValue value : userValues) {
|
|
||||||
BindingImpl binding = (BindingImpl) getBinding(value.getProperty());
|
|
||||||
results.add(binding.setValue(value.getValue()));
|
|
||||||
}
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
class BindingImpl implements Binding {
|
|
||||||
|
|
||||||
private Expression property;
|
|
||||||
|
|
||||||
private Formatter formatter;
|
|
||||||
|
|
||||||
public BindingImpl(BindingConfiguration config) throws org.springframework.expression.ParseException {
|
|
||||||
property = expressionParser.parseExpression(config.getProperty());
|
|
||||||
formatter = config.getFormatter();
|
|
||||||
}
|
|
||||||
|
|
||||||
// implementing Binding
|
|
||||||
|
|
||||||
public String getValue() {
|
|
||||||
Object value;
|
|
||||||
try {
|
|
||||||
value = property.getValue(createEvaluationContext());
|
|
||||||
} catch (ExpressionException e) {
|
|
||||||
throw new IllegalStateException("Failed to get property expression value - this should not happen", e);
|
|
||||||
}
|
|
||||||
return format(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public BindingResult setValue(Object value) {
|
|
||||||
if (value instanceof String) {
|
|
||||||
return setStringValue((String) value);
|
|
||||||
} else if (value instanceof String[]) {
|
|
||||||
return setStringValues((String[]) value);
|
|
||||||
} else {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
Class<?> formattedType = getFormattedObjectType(formatter);
|
|
||||||
selectableValue = typeConverter.convert(selectableValue, formattedType);
|
|
||||||
return formatter.format(selectableValue, LocaleContextHolder.getLocale());
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isCollection() {
|
|
||||||
Class type;
|
|
||||||
try {
|
|
||||||
type = getValueType();
|
|
||||||
} catch (EvaluationException e) {
|
|
||||||
throw new IllegalArgumentException("Failed to get property expression value type - this should not happen", e);
|
|
||||||
}
|
|
||||||
TypeDescriptor<?> typeDesc = TypeDescriptor.valueOf(type);
|
|
||||||
return typeDesc.isCollection() || typeDesc.isArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String[] getCollectionValues() {
|
|
||||||
Object multiValue;
|
|
||||||
try {
|
|
||||||
multiValue = property.getValue(createEvaluationContext());
|
|
||||||
} catch (EvaluationException e) {
|
|
||||||
throw new IllegalStateException("Failed to get property expression value - this should not happen", e);
|
|
||||||
}
|
|
||||||
if (multiValue == null) {
|
|
||||||
return EMPTY_STRING_ARRAY;
|
|
||||||
}
|
|
||||||
TypeDescriptor<?> type = TypeDescriptor.valueOf(multiValue.getClass());
|
|
||||||
String[] formattedValues;
|
|
||||||
if (type.isCollection()) {
|
|
||||||
Collection<?> values = ((Collection<?>) multiValue);
|
|
||||||
formattedValues = (String[]) Array.newInstance(String.class, values.size());
|
|
||||||
copy(values, formattedValues);
|
|
||||||
} else if (type.isArray()) {
|
|
||||||
formattedValues = (String[]) Array.newInstance(String.class, Array.getLength(multiValue));
|
|
||||||
copy((Iterable<?>) multiValue, formattedValues);
|
|
||||||
} else {
|
|
||||||
throw new IllegalStateException();
|
|
||||||
}
|
|
||||||
return formattedValues;
|
|
||||||
}
|
|
||||||
|
|
||||||
// internal helpers
|
|
||||||
|
|
||||||
private BindingResult setStringValue(String formatted) {
|
|
||||||
Formatter formatter;
|
|
||||||
try {
|
|
||||||
formatter = getFormatter();
|
|
||||||
} catch (EvaluationException e) {
|
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
Object parsed;
|
|
||||||
try {
|
|
||||||
parsed = formatter.parse(formatted, LocaleContextHolder.getLocale());
|
|
||||||
} catch (ParseException e) {
|
|
||||||
return new InvalidFormatResult(property.getExpressionString(), formatted, e);
|
|
||||||
}
|
|
||||||
return setValue(parsed, formatted);
|
|
||||||
}
|
|
||||||
|
|
||||||
private BindingResult setStringValues(String[] formatted) {
|
|
||||||
Formatter formatter;
|
|
||||||
try {
|
|
||||||
formatter = getFormatter();
|
|
||||||
} catch (EvaluationException e) {
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
Object parsed = Array.newInstance(parsedType, formatted.length);
|
|
||||||
for (int i = 0; i < formatted.length; i++) {
|
|
||||||
Object parsedValue;
|
|
||||||
try {
|
|
||||||
parsedValue = formatter.parse(formatted[i], LocaleContextHolder.getLocale());
|
|
||||||
} catch (ParseException e) {
|
|
||||||
return new InvalidFormatResult(property.getExpressionString(), formatted, e);
|
|
||||||
}
|
|
||||||
Array.set(parsed, i, parsedValue);
|
|
||||||
}
|
|
||||||
return setValue(parsed, formatted);
|
|
||||||
}
|
|
||||||
|
|
||||||
private BindingResult setObjectValue(Object value) {
|
|
||||||
return setValue(value, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Formatter getFormatter() throws EvaluationException {
|
|
||||||
if (formatter != null) {
|
|
||||||
return formatter;
|
|
||||||
} else {
|
|
||||||
Class<?> type = getValueType();
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Class<?> getValueType() throws EvaluationException {
|
|
||||||
return property.getValueType(createEvaluationContext());
|
|
||||||
}
|
|
||||||
|
|
||||||
private Annotation[] getAnnotations() throws EvaluationException {
|
|
||||||
return property.getValueTypeDescriptor(createEvaluationContext()).getAnnotations();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void copy(Iterable<?> values, String[] formattedValues) {
|
|
||||||
int i = 0;
|
|
||||||
for (Object value : values) {
|
|
||||||
formattedValues[i] = format(value);
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private BindingResult setValue(Object parsedValue, Object userValue) {
|
|
||||||
try {
|
|
||||||
property.setValue(createEvaluationContext(), parsedValue);
|
|
||||||
return new SuccessResult(property.getExpressionString(), userValue);
|
|
||||||
} catch (EvaluationException e) {
|
|
||||||
return new ExpressionEvaluationErrorResult(property.getExpressionString(), userValue, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private EvaluationContext createEvaluationContext() {
|
|
||||||
StandardEvaluationContext context = new StandardEvaluationContext();
|
|
||||||
context.setRootObject(model);
|
|
||||||
context.addPropertyAccessor(new MapAccessor());
|
|
||||||
context.setTypeConverter(new StandardTypeConverter(typeConverter));
|
|
||||||
return context;
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
|
||||||
while (classToIntrospect != null) {
|
|
||||||
Type[] genericInterfaces = classToIntrospect.getGenericInterfaces();
|
|
||||||
for (Type genericInterface : genericInterfaces) {
|
|
||||||
if (genericInterface instanceof ParameterizedType) {
|
|
||||||
ParameterizedType pInterface = (ParameterizedType) genericInterface;
|
|
||||||
if (Formatter.class.isAssignableFrom((Class) pInterface.getRawType())) {
|
|
||||||
return getParameterClass(pInterface.getActualTypeArguments()[0], formatter.getClass());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
classToIntrospect = classToIntrospect.getSuperclass();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
static class InvalidFormatResult implements BindingResult {
|
|
||||||
|
|
||||||
private String property;
|
|
||||||
|
|
||||||
private Object formatted;
|
|
||||||
|
|
||||||
private ParseException e;
|
|
||||||
|
|
||||||
public InvalidFormatResult(String property, Object formatted, ParseException e) {
|
|
||||||
this.property = property;
|
|
||||||
this.formatted = formatted;
|
|
||||||
this.e = e;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getProperty() {
|
|
||||||
return property;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isError() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getErrorCode() {
|
|
||||||
return "invalidFormat";
|
|
||||||
}
|
|
||||||
|
|
||||||
public Throwable getErrorCause() {
|
|
||||||
return e;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Object getUserValue() {
|
|
||||||
return formatted;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static class ExpressionEvaluationErrorResult implements BindingResult {
|
|
||||||
|
|
||||||
private String property;
|
|
||||||
|
|
||||||
private Object formatted;
|
|
||||||
|
|
||||||
private EvaluationException e;
|
|
||||||
|
|
||||||
public ExpressionEvaluationErrorResult(String property, Object formatted, EvaluationException e) {
|
|
||||||
this.property = property;
|
|
||||||
this.formatted = formatted;
|
|
||||||
this.e = e;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getProperty() {
|
|
||||||
return property;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isError() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getErrorCode() {
|
|
||||||
if (e.getMessage().startsWith("EL1034E")) {
|
|
||||||
return "typeConversionFailure";
|
|
||||||
} else if (e.getMessage().startsWith("EL1008E")) {
|
|
||||||
return "propertyNotFound";
|
|
||||||
} else {
|
|
||||||
// TODO return more specific code based on underlying EvaluationException error code
|
|
||||||
return "couldNotSetValue";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Throwable getErrorCause() {
|
|
||||||
return e;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Object getUserValue() {
|
|
||||||
return formatted;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static class SuccessResult implements BindingResult {
|
|
||||||
|
|
||||||
private String property;
|
|
||||||
|
|
||||||
private Object formatted;
|
|
||||||
|
|
||||||
public SuccessResult(String property, Object formatted) {
|
|
||||||
this.property = property;
|
|
||||||
this.formatted = formatted;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getProperty() {
|
|
||||||
return property;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isError() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getErrorCode() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Throwable getErrorCause() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Object getUserValue() {
|
|
||||||
return formatted;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -15,12 +15,13 @@
|
||||||
*/
|
*/
|
||||||
package org.springframework.ui.binding;
|
package org.springframework.ui.binding;
|
||||||
|
|
||||||
|
import org.springframework.ui.binding.support.GenericBinder;
|
||||||
import org.springframework.ui.format.Formatter;
|
import org.springframework.ui.format.Formatter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configuration used to create a new {@link Binding} registered with a {@link Binder}.
|
* Configuration used to create a new {@link Binding} registered with a {@link GenericBinder}.
|
||||||
* @author Keith Donald
|
* @author Keith Donald
|
||||||
* @see Binder#add(BindingConfiguration)
|
* @see GenericBinder#add(BindingConfiguration)
|
||||||
*/
|
*/
|
||||||
public class BindingConfiguration {
|
public class BindingConfiguration {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,518 @@
|
||||||
|
/*
|
||||||
|
* 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.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.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.springframework.context.expression.MapAccessor;
|
||||||
|
import org.springframework.context.i18n.LocaleContextHolder;
|
||||||
|
import org.springframework.core.GenericTypeResolver;
|
||||||
|
import org.springframework.core.convert.TypeConverter;
|
||||||
|
import org.springframework.core.convert.TypeDescriptor;
|
||||||
|
import org.springframework.core.convert.support.DefaultTypeConverter;
|
||||||
|
import org.springframework.expression.EvaluationContext;
|
||||||
|
import org.springframework.expression.EvaluationException;
|
||||||
|
import org.springframework.expression.Expression;
|
||||||
|
import org.springframework.expression.ExpressionException;
|
||||||
|
import org.springframework.expression.ExpressionParser;
|
||||||
|
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||||
|
import org.springframework.expression.spel.standard.SpelExpressionParserConfiguration;
|
||||||
|
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||||
|
import org.springframework.expression.spel.support.StandardTypeConverter;
|
||||||
|
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.UserValue;
|
||||||
|
import org.springframework.ui.format.AnnotationFormatterFactory;
|
||||||
|
import org.springframework.ui.format.Formatter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binds user-entered values to properties of a model object.
|
||||||
|
* @author Keith Donald
|
||||||
|
*
|
||||||
|
* @param <T> The type of model object this binder binds to
|
||||||
|
* @see #add(BindingConfiguration)
|
||||||
|
* @see #bind(Map)
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public class GenericBinder<M> implements Binder<M> {
|
||||||
|
|
||||||
|
private static final String[] EMPTY_STRING_ARRAY = new String[0];
|
||||||
|
|
||||||
|
private M model;
|
||||||
|
|
||||||
|
private Map<String, Binding> bindings;
|
||||||
|
|
||||||
|
private Map<Class, Formatter> typeFormatters = new HashMap<Class, Formatter>();
|
||||||
|
|
||||||
|
private Map<Class, AnnotationFormatterFactory> annotationFormatters = new HashMap<Class, AnnotationFormatterFactory>();
|
||||||
|
|
||||||
|
private ExpressionParser expressionParser;
|
||||||
|
|
||||||
|
private TypeConverter typeConverter;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new binder for the model object.
|
||||||
|
* @param model the model object containing properties this binder will bind to
|
||||||
|
*/
|
||||||
|
public GenericBinder(M model) {
|
||||||
|
this.model = model;
|
||||||
|
bindings = new HashMap<String, Binding>();
|
||||||
|
int parserConfig = SpelExpressionParserConfiguration.CreateListsOnAttemptToIndexIntoNull
|
||||||
|
| SpelExpressionParserConfiguration.GrowListsOnIndexBeyondSize;
|
||||||
|
expressionParser = new SpelExpressionParser(parserConfig);
|
||||||
|
typeConverter = new DefaultTypeConverter();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStrict(boolean strict) {
|
||||||
|
this.strict = strict;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Binding add(BindingConfiguration binding) {
|
||||||
|
Binding newBinding;
|
||||||
|
try {
|
||||||
|
newBinding = new BindingImpl(binding);
|
||||||
|
} catch (org.springframework.expression.ParseException e) {
|
||||||
|
throw new IllegalArgumentException(e);
|
||||||
|
}
|
||||||
|
bindings.put(binding.getProperty(), newBinding);
|
||||||
|
return newBinding;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
public M getModel() {
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Binding getBinding(String property) {
|
||||||
|
Binding binding = bindings.get(property);
|
||||||
|
if (binding == null && !strict) {
|
||||||
|
return add(new BindingConfiguration(property, null));
|
||||||
|
} else {
|
||||||
|
return binding;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<BindingResult> bind(List<UserValue> userValues) {
|
||||||
|
List<BindingResult> results = new ArrayList<BindingResult>(userValues.size());
|
||||||
|
for (UserValue value : userValues) {
|
||||||
|
BindingImpl binding = (BindingImpl) getBinding(value.getProperty());
|
||||||
|
results.add(binding.setValue(value.getValue()));
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
class BindingImpl implements Binding {
|
||||||
|
|
||||||
|
private Expression property;
|
||||||
|
|
||||||
|
private Formatter formatter;
|
||||||
|
|
||||||
|
public BindingImpl(BindingConfiguration config) throws org.springframework.expression.ParseException {
|
||||||
|
property = expressionParser.parseExpression(config.getProperty());
|
||||||
|
formatter = config.getFormatter();
|
||||||
|
}
|
||||||
|
|
||||||
|
// implementing Binding
|
||||||
|
|
||||||
|
public String getValue() {
|
||||||
|
Object value;
|
||||||
|
try {
|
||||||
|
value = property.getValue(createEvaluationContext());
|
||||||
|
} catch (ExpressionException e) {
|
||||||
|
throw new IllegalStateException("Failed to get property expression value - this should not happen", e);
|
||||||
|
}
|
||||||
|
return format(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BindingResult setValue(Object value) {
|
||||||
|
if (value instanceof String) {
|
||||||
|
return setStringValue((String) value);
|
||||||
|
} else if (value instanceof String[]) {
|
||||||
|
return setStringValues((String[]) value);
|
||||||
|
} else {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
Class<?> formattedType = getFormattedObjectType(formatter);
|
||||||
|
selectableValue = typeConverter.convert(selectableValue, formattedType);
|
||||||
|
return formatter.format(selectableValue, LocaleContextHolder.getLocale());
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isCollection() {
|
||||||
|
Class type;
|
||||||
|
try {
|
||||||
|
type = getValueType();
|
||||||
|
} catch (EvaluationException e) {
|
||||||
|
throw new IllegalArgumentException("Failed to get property expression value type - this should not happen", e);
|
||||||
|
}
|
||||||
|
TypeDescriptor<?> typeDesc = TypeDescriptor.valueOf(type);
|
||||||
|
return typeDesc.isCollection() || typeDesc.isArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String[] getCollectionValues() {
|
||||||
|
Object multiValue;
|
||||||
|
try {
|
||||||
|
multiValue = property.getValue(createEvaluationContext());
|
||||||
|
} catch (EvaluationException e) {
|
||||||
|
throw new IllegalStateException("Failed to get property expression value - this should not happen", e);
|
||||||
|
}
|
||||||
|
if (multiValue == null) {
|
||||||
|
return EMPTY_STRING_ARRAY;
|
||||||
|
}
|
||||||
|
TypeDescriptor<?> type = TypeDescriptor.valueOf(multiValue.getClass());
|
||||||
|
String[] formattedValues;
|
||||||
|
if (type.isCollection()) {
|
||||||
|
Collection<?> values = ((Collection<?>) multiValue);
|
||||||
|
formattedValues = (String[]) Array.newInstance(String.class, values.size());
|
||||||
|
copy(values, formattedValues);
|
||||||
|
} else if (type.isArray()) {
|
||||||
|
formattedValues = (String[]) Array.newInstance(String.class, Array.getLength(multiValue));
|
||||||
|
copy((Iterable<?>) multiValue, formattedValues);
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
return formattedValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
// internal helpers
|
||||||
|
|
||||||
|
private BindingResult setStringValue(String formatted) {
|
||||||
|
Formatter formatter;
|
||||||
|
try {
|
||||||
|
formatter = getFormatter();
|
||||||
|
} catch (EvaluationException e) {
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
Object parsed;
|
||||||
|
try {
|
||||||
|
parsed = formatter.parse(formatted, LocaleContextHolder.getLocale());
|
||||||
|
} catch (ParseException e) {
|
||||||
|
return new InvalidFormatResult(property.getExpressionString(), formatted, e);
|
||||||
|
}
|
||||||
|
return setValue(parsed, formatted);
|
||||||
|
}
|
||||||
|
|
||||||
|
private BindingResult setStringValues(String[] formatted) {
|
||||||
|
Formatter formatter;
|
||||||
|
try {
|
||||||
|
formatter = getFormatter();
|
||||||
|
} catch (EvaluationException e) {
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
Object parsed = Array.newInstance(parsedType, formatted.length);
|
||||||
|
for (int i = 0; i < formatted.length; i++) {
|
||||||
|
Object parsedValue;
|
||||||
|
try {
|
||||||
|
parsedValue = formatter.parse(formatted[i], LocaleContextHolder.getLocale());
|
||||||
|
} catch (ParseException e) {
|
||||||
|
return new InvalidFormatResult(property.getExpressionString(), formatted, e);
|
||||||
|
}
|
||||||
|
Array.set(parsed, i, parsedValue);
|
||||||
|
}
|
||||||
|
return setValue(parsed, formatted);
|
||||||
|
}
|
||||||
|
|
||||||
|
private BindingResult setObjectValue(Object value) {
|
||||||
|
return setValue(value, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Formatter getFormatter() throws EvaluationException {
|
||||||
|
if (formatter != null) {
|
||||||
|
return formatter;
|
||||||
|
} else {
|
||||||
|
Class<?> type = getValueType();
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Class<?> getValueType() throws EvaluationException {
|
||||||
|
return property.getValueType(createEvaluationContext());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Annotation[] getAnnotations() throws EvaluationException {
|
||||||
|
return property.getValueTypeDescriptor(createEvaluationContext()).getAnnotations();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void copy(Iterable<?> values, String[] formattedValues) {
|
||||||
|
int i = 0;
|
||||||
|
for (Object value : values) {
|
||||||
|
formattedValues[i] = format(value);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private BindingResult setValue(Object parsedValue, Object userValue) {
|
||||||
|
try {
|
||||||
|
property.setValue(createEvaluationContext(), parsedValue);
|
||||||
|
return new SuccessResult(property.getExpressionString(), userValue);
|
||||||
|
} catch (EvaluationException e) {
|
||||||
|
return new ExpressionEvaluationErrorResult(property.getExpressionString(), userValue, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private EvaluationContext createEvaluationContext() {
|
||||||
|
StandardEvaluationContext context = new StandardEvaluationContext();
|
||||||
|
context.setRootObject(model);
|
||||||
|
context.addPropertyAccessor(new MapAccessor());
|
||||||
|
context.setTypeConverter(new StandardTypeConverter(typeConverter));
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
while (classToIntrospect != null) {
|
||||||
|
Type[] genericInterfaces = classToIntrospect.getGenericInterfaces();
|
||||||
|
for (Type genericInterface : genericInterfaces) {
|
||||||
|
if (genericInterface instanceof ParameterizedType) {
|
||||||
|
ParameterizedType pInterface = (ParameterizedType) genericInterface;
|
||||||
|
if (Formatter.class.isAssignableFrom((Class) pInterface.getRawType())) {
|
||||||
|
return getParameterClass(pInterface.getActualTypeArguments()[0], formatter.getClass());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
classToIntrospect = classToIntrospect.getSuperclass();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static class InvalidFormatResult implements BindingResult {
|
||||||
|
|
||||||
|
private String property;
|
||||||
|
|
||||||
|
private Object formatted;
|
||||||
|
|
||||||
|
private ParseException e;
|
||||||
|
|
||||||
|
public InvalidFormatResult(String property, Object formatted, ParseException e) {
|
||||||
|
this.property = property;
|
||||||
|
this.formatted = formatted;
|
||||||
|
this.e = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getProperty() {
|
||||||
|
return property;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isError() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getErrorCode() {
|
||||||
|
return "invalidFormat";
|
||||||
|
}
|
||||||
|
|
||||||
|
public Throwable getErrorCause() {
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object getUserValue() {
|
||||||
|
return formatted;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class ExpressionEvaluationErrorResult implements BindingResult {
|
||||||
|
|
||||||
|
private String property;
|
||||||
|
|
||||||
|
private Object formatted;
|
||||||
|
|
||||||
|
private EvaluationException e;
|
||||||
|
|
||||||
|
public ExpressionEvaluationErrorResult(String property, Object formatted, EvaluationException e) {
|
||||||
|
this.property = property;
|
||||||
|
this.formatted = formatted;
|
||||||
|
this.e = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getProperty() {
|
||||||
|
return property;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isError() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getErrorCode() {
|
||||||
|
if (e.getMessage().startsWith("EL1034E")) {
|
||||||
|
return "typeConversionFailure";
|
||||||
|
} else if (e.getMessage().startsWith("EL1008E")) {
|
||||||
|
return "propertyNotFound";
|
||||||
|
} else {
|
||||||
|
// TODO return more specific code based on underlying EvaluationException error code
|
||||||
|
return "couldNotSetValue";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Throwable getErrorCause() {
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object getUserValue() {
|
||||||
|
return formatted;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class SuccessResult implements BindingResult {
|
||||||
|
|
||||||
|
private String property;
|
||||||
|
|
||||||
|
private Object formatted;
|
||||||
|
|
||||||
|
public SuccessResult(String property, Object formatted) {
|
||||||
|
this.property = property;
|
||||||
|
this.formatted = formatted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getProperty() {
|
||||||
|
return property;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isError() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getErrorCode() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Throwable getErrorCause() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object getUserValue() {
|
||||||
|
return formatted;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<p>
|
||||||
|
Default implementation of the Binding API usable in any environment.
|
||||||
|
</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package org.springframework.ui.binding;
|
package org.springframework.ui.binding.support;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
|
|
@ -19,13 +19,19 @@ import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.springframework.context.i18n.LocaleContextHolder;
|
import org.springframework.context.i18n.LocaleContextHolder;
|
||||||
import org.springframework.expression.EvaluationException;
|
import org.springframework.expression.EvaluationException;
|
||||||
|
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.UserValue;
|
||||||
|
import org.springframework.ui.binding.support.GenericBinder;
|
||||||
import org.springframework.ui.format.date.DateFormatter;
|
import org.springframework.ui.format.date.DateFormatter;
|
||||||
import org.springframework.ui.format.number.CurrencyAnnotationFormatterFactory;
|
import org.springframework.ui.format.number.CurrencyAnnotationFormatterFactory;
|
||||||
import org.springframework.ui.format.number.CurrencyFormat;
|
import org.springframework.ui.format.number.CurrencyFormat;
|
||||||
import org.springframework.ui.format.number.CurrencyFormatter;
|
import org.springframework.ui.format.number.CurrencyFormatter;
|
||||||
import org.springframework.ui.format.number.IntegerFormatter;
|
import org.springframework.ui.format.number.IntegerFormatter;
|
||||||
|
|
||||||
public class BinderTests {
|
public class GenericBinderTests {
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
|
|
@ -39,7 +45,7 @@ public class BinderTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void bindSingleValuesWithDefaultTypeConverterConversion() {
|
public void bindSingleValuesWithDefaultTypeConverterConversion() {
|
||||||
Binder<TestBean> binder = new Binder<TestBean>(new TestBean());
|
Binder<TestBean> binder = new GenericBinder<TestBean>(new TestBean());
|
||||||
List<UserValue> values = new ArrayList<UserValue>();
|
List<UserValue> values = new ArrayList<UserValue>();
|
||||||
values.add(new UserValue("string", "test"));
|
values.add(new UserValue("string", "test"));
|
||||||
values.add(new UserValue("integer", "3"));
|
values.add(new UserValue("integer", "3"));
|
||||||
|
|
@ -69,7 +75,7 @@ public class BinderTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void bindSingleValuesWithDefaultTypeCoversionFailure() {
|
public void bindSingleValuesWithDefaultTypeCoversionFailure() {
|
||||||
Binder<TestBean> binder = new Binder<TestBean>(new TestBean());
|
Binder<TestBean> binder = new GenericBinder<TestBean>(new TestBean());
|
||||||
List<UserValue> values = new ArrayList<UserValue>();
|
List<UserValue> values = new ArrayList<UserValue>();
|
||||||
values.add(new UserValue("string", "test"));
|
values.add(new UserValue("string", "test"));
|
||||||
// bad value
|
// bad value
|
||||||
|
|
@ -83,7 +89,7 @@ public class BinderTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void bindSingleValuePropertyFormatter() throws ParseException {
|
public void bindSingleValuePropertyFormatter() throws ParseException {
|
||||||
Binder<TestBean> binder = new Binder<TestBean>(new TestBean());
|
Binder<TestBean> binder = new GenericBinder<TestBean>(new TestBean());
|
||||||
binder.add(new BindingConfiguration("date", new DateFormatter()));
|
binder.add(new BindingConfiguration("date", new DateFormatter()));
|
||||||
binder.bind(UserValue.single("date", "2009-06-01"));
|
binder.bind(UserValue.single("date", "2009-06-01"));
|
||||||
assertEquals(new DateFormatter().parse("2009-06-01", Locale.US), binder.getModel().getDate());
|
assertEquals(new DateFormatter().parse("2009-06-01", Locale.US), binder.getModel().getDate());
|
||||||
|
|
@ -91,14 +97,14 @@ public class BinderTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void bindSingleValuePropertyFormatterParseException() {
|
public void bindSingleValuePropertyFormatterParseException() {
|
||||||
Binder<TestBean> binder = new Binder<TestBean>(new TestBean());
|
Binder<TestBean> binder = new GenericBinder<TestBean>(new TestBean());
|
||||||
binder.add(new BindingConfiguration("date", new DateFormatter()));
|
binder.add(new BindingConfiguration("date", new DateFormatter()));
|
||||||
binder.bind(UserValue.single("date", "bogus"));
|
binder.bind(UserValue.single("date", "bogus"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void bindSingleValueWithFormatterRegistedByType() throws ParseException {
|
public void bindSingleValueWithFormatterRegistedByType() throws ParseException {
|
||||||
Binder<TestBean> binder = new Binder<TestBean>(new TestBean());
|
Binder<TestBean> binder = new GenericBinder<TestBean>(new TestBean());
|
||||||
binder.add(new DateFormatter(), Date.class);
|
binder.add(new DateFormatter(), Date.class);
|
||||||
binder.bind(UserValue.single("date", "2009-06-01"));
|
binder.bind(UserValue.single("date", "2009-06-01"));
|
||||||
assertEquals(new DateFormatter().parse("2009-06-01", Locale.US), binder.getModel().getDate());
|
assertEquals(new DateFormatter().parse("2009-06-01", Locale.US), binder.getModel().getDate());
|
||||||
|
|
@ -106,7 +112,7 @@ public class BinderTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void bindSingleValueWithFormatterRegisteredByAnnotation() throws ParseException {
|
public void bindSingleValueWithFormatterRegisteredByAnnotation() throws ParseException {
|
||||||
Binder<TestBean> binder = new Binder<TestBean>(new TestBean());
|
Binder<TestBean> binder = new GenericBinder<TestBean>(new TestBean());
|
||||||
binder.add(new CurrencyFormatter(), CurrencyFormat.class);
|
binder.add(new CurrencyFormatter(), CurrencyFormat.class);
|
||||||
binder.bind(UserValue.single("currency", "$23.56"));
|
binder.bind(UserValue.single("currency", "$23.56"));
|
||||||
assertEquals(new BigDecimal("23.56"), binder.getModel().getCurrency());
|
assertEquals(new BigDecimal("23.56"), binder.getModel().getCurrency());
|
||||||
|
|
@ -114,7 +120,7 @@ public class BinderTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void bindSingleValueWithnAnnotationFormatterFactoryRegistered() throws ParseException {
|
public void bindSingleValueWithnAnnotationFormatterFactoryRegistered() throws ParseException {
|
||||||
Binder<TestBean> binder = new Binder<TestBean>(new TestBean());
|
Binder<TestBean> binder = new GenericBinder<TestBean>(new TestBean());
|
||||||
binder.add(new CurrencyAnnotationFormatterFactory());
|
binder.add(new CurrencyAnnotationFormatterFactory());
|
||||||
binder.bind(UserValue.single("currency", "$23.56"));
|
binder.bind(UserValue.single("currency", "$23.56"));
|
||||||
assertEquals(new BigDecimal("23.56"), binder.getModel().getCurrency());
|
assertEquals(new BigDecimal("23.56"), binder.getModel().getCurrency());
|
||||||
|
|
@ -122,7 +128,7 @@ public class BinderTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void bindSingleValuePropertyNotFound() throws ParseException {
|
public void bindSingleValuePropertyNotFound() throws ParseException {
|
||||||
Binder<TestBean> binder = new Binder<TestBean>(new TestBean());
|
Binder<TestBean> binder = new GenericBinder<TestBean>(new TestBean());
|
||||||
List<BindingResult> results = binder.bind(UserValue.single("bogus", "2009-06-01"));
|
List<BindingResult> results = binder.bind(UserValue.single("bogus", "2009-06-01"));
|
||||||
assertEquals(1, results.size());
|
assertEquals(1, results.size());
|
||||||
assertTrue(results.get(0).isError());
|
assertTrue(results.get(0).isError());
|
||||||
|
|
@ -131,7 +137,7 @@ public class BinderTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getBindingOptimistic() {
|
public void getBindingOptimistic() {
|
||||||
Binder<TestBean> binder = new Binder<TestBean>(new TestBean());
|
Binder<TestBean> binder = new GenericBinder<TestBean>(new TestBean());
|
||||||
Binding b = binder.getBinding("integer");
|
Binding b = binder.getBinding("integer");
|
||||||
assertFalse(b.isCollection());
|
assertFalse(b.isCollection());
|
||||||
assertEquals("0", b.getValue());
|
assertEquals("0", b.getValue());
|
||||||
|
|
@ -142,7 +148,7 @@ public class BinderTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getBindingStrict() {
|
public void getBindingStrict() {
|
||||||
Binder<TestBean> binder = new Binder<TestBean>(new TestBean());
|
Binder<TestBean> binder = new GenericBinder<TestBean>(new TestBean());
|
||||||
binder.setStrict(true);
|
binder.setStrict(true);
|
||||||
Binding b = binder.getBinding("integer");
|
Binding b = binder.getBinding("integer");
|
||||||
assertNull(b);
|
assertNull(b);
|
||||||
|
|
@ -157,7 +163,7 @@ public class BinderTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getBindingCustomFormatter() {
|
public void getBindingCustomFormatter() {
|
||||||
Binder<TestBean> binder = new Binder<TestBean>(new TestBean());
|
Binder<TestBean> binder = new GenericBinder<TestBean>(new TestBean());
|
||||||
binder.add(new BindingConfiguration("currency", new CurrencyFormatter()));
|
binder.add(new BindingConfiguration("currency", new CurrencyFormatter()));
|
||||||
Binding b = binder.getBinding("currency");
|
Binding b = binder.getBinding("currency");
|
||||||
assertFalse(b.isCollection());
|
assertFalse(b.isCollection());
|
||||||
|
|
@ -168,7 +174,7 @@ public class BinderTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getBindingCustomFormatterRequiringTypeCoersion() {
|
public void getBindingCustomFormatterRequiringTypeCoersion() {
|
||||||
Binder<TestBean> binder = new Binder<TestBean>(new TestBean());
|
Binder<TestBean> binder = new GenericBinder<TestBean>(new TestBean());
|
||||||
// IntegerFormatter formats Longs, so conversion from Integer -> Long is performed
|
// IntegerFormatter formats Longs, so conversion from Integer -> Long is performed
|
||||||
binder.add(new BindingConfiguration("integer", new IntegerFormatter()));
|
binder.add(new BindingConfiguration("integer", new IntegerFormatter()));
|
||||||
Binding b = binder.getBinding("integer");
|
Binding b = binder.getBinding("integer");
|
||||||
|
|
@ -178,7 +184,7 @@ public class BinderTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getBindingMultiValued() {
|
public void getBindingMultiValued() {
|
||||||
Binder<TestBean> binder = new Binder<TestBean>(new TestBean());
|
Binder<TestBean> binder = new GenericBinder<TestBean>(new TestBean());
|
||||||
Binding b = binder.getBinding("foos");
|
Binding b = binder.getBinding("foos");
|
||||||
assertTrue(b.isCollection());
|
assertTrue(b.isCollection());
|
||||||
assertEquals(0, b.getCollectionValues().length);
|
assertEquals(0, b.getCollectionValues().length);
|
||||||
|
|
@ -195,7 +201,7 @@ public class BinderTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getBindingMultiValuedTypeConversionFailure() {
|
public void getBindingMultiValuedTypeConversionFailure() {
|
||||||
Binder<TestBean> binder = new Binder<TestBean>(new TestBean());
|
Binder<TestBean> binder = new GenericBinder<TestBean>(new TestBean());
|
||||||
Binding b = binder.getBinding("foos");
|
Binding b = binder.getBinding("foos");
|
||||||
assertTrue(b.isCollection());
|
assertTrue(b.isCollection());
|
||||||
assertEquals(0, b.getCollectionValues().length);
|
assertEquals(0, b.getCollectionValues().length);
|
||||||
|
|
@ -208,7 +214,7 @@ public class BinderTests {
|
||||||
@Test
|
@Test
|
||||||
public void bindHandleNullValueInNestedPath() {
|
public void bindHandleNullValueInNestedPath() {
|
||||||
TestBean testbean = new TestBean();
|
TestBean testbean = new TestBean();
|
||||||
Binder<TestBean> binder = new Binder<TestBean>(testbean);
|
Binder<TestBean> binder = new GenericBinder<TestBean>(testbean);
|
||||||
List<UserValue> values = new ArrayList<UserValue>();
|
List<UserValue> values = new ArrayList<UserValue>();
|
||||||
|
|
||||||
// EL configured with some options from SpelExpressionParserConfiguration:
|
// EL configured with some options from SpelExpressionParserConfiguration:
|
||||||
|
|
@ -244,7 +250,7 @@ public class BinderTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void formatPossibleValue() {
|
public void formatPossibleValue() {
|
||||||
Binder<TestBean> binder = new Binder<TestBean>(new TestBean());
|
Binder<TestBean> binder = new GenericBinder<TestBean>(new TestBean());
|
||||||
binder.add(new BindingConfiguration("currency", new CurrencyFormatter()));
|
binder.add(new BindingConfiguration("currency", new CurrencyFormatter()));
|
||||||
Binding b = binder.getBinding("currency");
|
Binding b = binder.getBinding("currency");
|
||||||
assertEquals("$5.00", b.format(new BigDecimal("5")));
|
assertEquals("$5.00", b.format(new BigDecimal("5")));
|
||||||
Loading…
Reference in New Issue