separated interface from impl

This commit is contained in:
Keith Donald 2009-06-09 19:27:13 +00:00
parent 58e60fb844
commit 71cbd982e3
5 changed files with 566 additions and 500 deletions

View File

@ -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;
}
}
} }

View File

@ -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 {

View File

@ -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;
}
}
}

View File

@ -0,0 +1,7 @@
<html>
<body>
<p>
Default implementation of the Binding API usable in any environment.
</p>
</body>
</html>

View File

@ -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")));