DataBinder allows for adding custom Formatters as alternative to PropertyEditors (including per-field formatters)

Includes a generic FormatterPropertyEditorAdapter plus Number conversion support in TypeConverterDelegate.

Issue: SPR-7773
Issue: SPR-6069
This commit is contained in:
Juergen Hoeller
2015-05-12 22:33:18 +02:00
parent 49c600b234
commit 02da2e85ee
5 changed files with 427 additions and 41 deletions

View File

@@ -0,0 +1,76 @@
/*
* Copyright 2002-2015 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.format.support;
import java.beans.PropertyEditor;
import java.beans.PropertyEditorSupport;
import java.text.ParseException;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.format.Formatter;
import org.springframework.util.Assert;
/**
* Adapter that bridges between {@link Formatter} and {@link PropertyEditor}.
*
* @author Juergen Hoeller
* @since 4.2
*/
public class FormatterPropertyEditorAdapter extends PropertyEditorSupport {
private final Formatter<Object> formatter;
/**
* Create a new {@code FormatterPropertyEditorAdapter} for the given {@link Formatter}.
* @param formatter the {@link Formatter} to wrap
*/
@SuppressWarnings("unchecked")
public FormatterPropertyEditorAdapter(Formatter<?> formatter) {
Assert.notNull(formatter, "Formatter must not be null");
this.formatter = (Formatter<Object>) formatter;
}
/**
* Determine the {@link Formatter}-declared field type.
* @return the field type declared in the wrapped {@link Formatter} implementation
* (never {@code null})
* @throws IllegalArgumentException if the {@link Formatter}-declared field type
* cannot be inferred
*/
public Class<?> getFieldType() {
return FormattingConversionService.getFieldType(this.formatter);
}
@Override
public void setAsText(String text) throws IllegalArgumentException {
try {
setValue(this.formatter.parse(text, LocaleContextHolder.getLocale()));
}
catch (ParseException ex) {
throw new IllegalArgumentException("Parse attempt failed for value [" + text + "]", ex);
}
}
@Override
public String getAsText() {
return this.formatter.print(getValue(), LocaleContextHolder.getLocale());
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 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.
@@ -67,12 +67,7 @@ public class FormattingConversionService extends GenericConversionService
@Override
public void addFormatter(Formatter<?> formatter) {
Class<?> fieldType = GenericTypeResolver.resolveTypeArgument(formatter.getClass(), Formatter.class);
if (fieldType == null) {
throw new IllegalArgumentException("Unable to extract parameterized field type argument from Formatter [" +
formatter.getClass().getName() + "]; does the formatter parameterize the <T> generic type?");
}
addFormatterForFieldType(fieldType, formatter);
addFormatterForFieldType(getFieldType(formatter), formatter);
}
@Override
@@ -88,14 +83,8 @@ public class FormattingConversionService extends GenericConversionService
}
@Override
@SuppressWarnings({ "unchecked", "rawtypes" })
public void addFormatterForFieldAnnotation(AnnotationFormatterFactory annotationFormatterFactory) {
Class<? extends Annotation> annotationType = (Class<? extends Annotation>)
GenericTypeResolver.resolveTypeArgument(annotationFormatterFactory.getClass(), AnnotationFormatterFactory.class);
if (annotationType == null) {
throw new IllegalArgumentException("Unable to extract parameterized Annotation type argument from AnnotationFormatterFactory [" +
annotationFormatterFactory.getClass().getName() + "]; does the factory parameterize the <A extends Annotation> generic type?");
}
public void addFormatterForFieldAnnotation(AnnotationFormatterFactory<? extends Annotation> annotationFormatterFactory) {
Class<? extends Annotation> annotationType = getAnnotationType(annotationFormatterFactory);
if (this.embeddedValueResolver != null && annotationFormatterFactory instanceof EmbeddedValueResolverAware) {
((EmbeddedValueResolverAware) annotationFormatterFactory).setEmbeddedValueResolver(this.embeddedValueResolver);
}
@@ -107,6 +96,28 @@ public class FormattingConversionService extends GenericConversionService
}
static Class<?> getFieldType(Formatter<?> formatter) {
Class<?> fieldType = GenericTypeResolver.resolveTypeArgument(formatter.getClass(), Formatter.class);
if (fieldType == null) {
throw new IllegalArgumentException("Unable to extract parameterized field type argument from Formatter [" +
formatter.getClass().getName() + "]; does the formatter parameterize the <T> generic type?");
}
return fieldType;
}
@SuppressWarnings("unchecked")
static Class<? extends Annotation> getAnnotationType(AnnotationFormatterFactory<? extends Annotation> factory) {
Class<? extends Annotation> annotationType = (Class<? extends Annotation>)
GenericTypeResolver.resolveTypeArgument(factory.getClass(), AnnotationFormatterFactory.class);
if (annotationType == null) {
throw new IllegalArgumentException("Unable to extract parameterized Annotation type argument from " +
"AnnotationFormatterFactory [" + factory.getClass().getName() +
"]; does the factory parameterize the <A extends Annotation> generic type?");
}
return annotationType;
}
private static class PrinterConverter implements GenericConverter {
private final Class<?> fieldType;
@@ -148,7 +159,7 @@ public class FormattingConversionService extends GenericConversionService
@Override
public String toString() {
return this.fieldType.getName() + " -> " + String.class.getName() + " : " + this.printer;
return (this.fieldType.getName() + " -> " + String.class.getName() + " : " + this.printer);
}
}
@@ -197,7 +208,7 @@ public class FormattingConversionService extends GenericConversionService
@Override
public String toString() {
return String.class.getName() + " -> " + this.fieldType.getName() + ": " + this.parser;
return (String.class.getName() + " -> " + this.fieldType.getName() + ": " + this.parser);
}
}
@@ -249,8 +260,8 @@ public class FormattingConversionService extends GenericConversionService
@Override
public String toString() {
return "@" + this.annotationType.getName() + " " + this.fieldType.getName() + " -> " +
String.class.getName() + ": " + this.annotationFormatterFactory;
return ("@" + this.annotationType.getName() + " " + this.fieldType.getName() + " -> " +
String.class.getName() + ": " + this.annotationFormatterFactory);
}
}
@@ -302,8 +313,8 @@ public class FormattingConversionService extends GenericConversionService
@Override
public String toString() {
return String.class.getName() + " -> @" + this.annotationType.getName() + " " +
this.fieldType.getName() + ": " + this.annotationFormatterFactory;
return (String.class.getName() + " -> @" + this.annotationType.getName() + " " +
this.fieldType.getName() + ": " + this.annotationFormatterFactory);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 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.
@@ -42,6 +42,8 @@ import org.springframework.beans.TypeConverter;
import org.springframework.beans.TypeMismatchException;
import org.springframework.core.MethodParameter;
import org.springframework.core.convert.ConversionService;
import org.springframework.format.Formatter;
import org.springframework.format.support.FormatterPropertyEditorAdapter;
import org.springframework.lang.UsesJava8;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
@@ -553,6 +555,7 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
return Collections.unmodifiableList(this.validators);
}
//---------------------------------------------------------------------
// Implementation of PropertyEditorRegistry/TypeConverter interface
//---------------------------------------------------------------------
@@ -576,6 +579,64 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
return this.conversionService;
}
/**
* Add a custom formatter, applying it to all fields matching the
* {@link Formatter}-declared type.
* <p>Registers a corresponding {@link PropertyEditor} adapter underneath the covers.
* @param formatter the formatter to add, generically declared for a specific type
* @since 4.2
* @see #registerCustomEditor(Class, PropertyEditor)
*/
public void addCustomFormatter(Formatter<?> formatter) {
FormatterPropertyEditorAdapter adapter = new FormatterPropertyEditorAdapter(formatter);
getPropertyEditorRegistry().registerCustomEditor(adapter.getFieldType(), adapter);
}
/**
* Add a custom formatter for the field type specified in {@link Formatter} class,
* applying it to the specified fields only, if any, or otherwise to all fields.
* <p>Registers a corresponding {@link PropertyEditor} adapter underneath the covers.
* @param formatter the formatter to add, generically declared for a specific type
* @param fields the fields to apply the formatter to, or none if to be applied to all
* @since 4.2
* @see #registerCustomEditor(Class, String, PropertyEditor)
*/
public void addCustomFormatter(Formatter<?> formatter, String... fields) {
FormatterPropertyEditorAdapter adapter = new FormatterPropertyEditorAdapter(formatter);
Class<?> fieldType = adapter.getFieldType();
if (ObjectUtils.isEmpty(fields)) {
getPropertyEditorRegistry().registerCustomEditor(fieldType, adapter);
}
else {
for (String field : fields) {
getPropertyEditorRegistry().registerCustomEditor(fieldType, field, adapter);
}
}
}
/**
* Add a custom formatter, applying it to the specified field types only, if any,
* or otherwise to all fields matching the {@link Formatter}-declared type.
* <p>Registers a corresponding {@link PropertyEditor} adapter underneath the covers.
* @param formatter the formatter to add (does not need to generically declare a
* field type if field types are explicitly specified as parameters)
* @param fieldTypes the field types to apply the formatter to, or none if to be
* derived from the given {@link Formatter} implementation class
* @since 4.2
* @see #registerCustomEditor(Class, PropertyEditor)
*/
public void addCustomFormatter(Formatter<?> formatter, Class<?>... fieldTypes) {
FormatterPropertyEditorAdapter adapter = new FormatterPropertyEditorAdapter(formatter);
if (ObjectUtils.isEmpty(fieldTypes)) {
getPropertyEditorRegistry().registerCustomEditor(adapter.getFieldType(), adapter);
}
else {
for (Class<?> fieldType : fieldTypes) {
getPropertyEditorRegistry().registerCustomEditor(fieldType, adapter);
}
}
}
@Override
public void registerCustomEditor(Class<?> requiredType, PropertyEditor propertyEditor) {
getPropertyEditorRegistry().registerCustomEditor(requiredType, propertyEditor);