From 8998228facd1eb3580d6aeda0f1fcf05b0752f96 Mon Sep 17 00:00:00 2001 From: Keith Donald Date: Thu, 11 Jun 2009 19:03:43 +0000 Subject: [PATCH] formatter registry --- .../ui/binding/FormatterRegistry.java | 40 ++++++ .../ui/binding/support/GenericBinder.java | 128 ++++++------------ .../support/GenericFormatterRegistry.java | 126 +++++++++++++++++ 3 files changed, 204 insertions(+), 90 deletions(-) create mode 100644 org.springframework.context/src/main/java/org/springframework/ui/binding/FormatterRegistry.java create mode 100644 org.springframework.context/src/main/java/org/springframework/ui/binding/support/GenericFormatterRegistry.java diff --git a/org.springframework.context/src/main/java/org/springframework/ui/binding/FormatterRegistry.java b/org.springframework.context/src/main/java/org/springframework/ui/binding/FormatterRegistry.java new file mode 100644 index 0000000000..64ae12c647 --- /dev/null +++ b/org.springframework.context/src/main/java/org/springframework/ui/binding/FormatterRegistry.java @@ -0,0 +1,40 @@ +package org.springframework.ui.binding; + +import org.springframework.core.convert.TypeDescriptor; +import org.springframework.ui.format.AnnotationFormatterFactory; +import org.springframework.ui.format.Formatter; + +/** + * A centralized registry of Formatters indexed by property types. + * + * @author Keith Donald + */ +public interface FormatterRegistry { + + /** + * Get the Formatter for the property type. + * @param propertyType the property type descriptor, which provides additional property metadata. + * @return the Formatter, or null if none is registered + */ + Formatter getFormatter(TypeDescriptor propertyType); + + /** + * Adds a Formatter that will format the values of properties of the provided type. + * The type should generally be a concrete class for a scalar value, such as BigDecimal, and not a collection value. + * The type may be an annotation type, which will have the Formatter format values of properties annotated with that annotation. + * Use {@link #add(AnnotationFormatterFactory)} when the format annotation defines configurable annotation instance values. + *

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