Skip to content

Commit 02da2e8

Browse files
committed
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
1 parent 49c600b commit 02da2e8

File tree

5 files changed

+427
-41
lines changed

5 files changed

+427
-41
lines changed

spring-beans/src/main/java/org/springframework/beans/TypeConverterDelegate.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2014 the original author or authors.
2+
* Copyright 2002-2015 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -34,6 +34,7 @@
3434
import org.springframework.core.convert.ConversionService;
3535
import org.springframework.core.convert.TypeDescriptor;
3636
import org.springframework.util.ClassUtils;
37+
import org.springframework.util.NumberUtils;
3738
import org.springframework.util.StringUtils;
3839

3940
/**
@@ -204,7 +205,7 @@ public <T> T convertIfNecessary(String propertyName, Object oldValue, Object new
204205
if (Object.class.equals(requiredType)) {
205206
return (T) convertedValue;
206207
}
207-
if (requiredType.isArray()) {
208+
else if (requiredType.isArray()) {
208209
// Array required -> apply appropriate conversion of elements.
209210
if (convertedValue instanceof String && Enum.class.isAssignableFrom(requiredType.getComponentType())) {
210211
convertedValue = StringUtils.commaDelimitedListToStringArray((String) convertedValue);
@@ -257,6 +258,11 @@ else if (convertedValue instanceof String && !requiredType.isInstance(convertedV
257258
convertedValue = attemptToConvertStringToEnum(requiredType, trimmedValue, convertedValue);
258259
standardConversion = true;
259260
}
261+
else if (convertedValue instanceof Number && Number.class.isAssignableFrom(requiredType)) {
262+
convertedValue = NumberUtils.convertNumberToTargetClass(
263+
(Number) convertedValue, (Class<Number>) requiredType);
264+
standardConversion = true;
265+
}
260266
}
261267
else {
262268
// convertedValue == null
@@ -339,7 +345,6 @@ private Object attemptToConvertStringToEnum(Class<?> requiredType, String trimme
339345
catch (Throwable ex) {
340346
if (logger.isTraceEnabled()) {
341347
logger.trace("Field [" + convertedValue + "] isn't an enum value", ex);
342-
343348
}
344349
}
345350
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
* Copyright 2002-2015 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.format.support;
18+
19+
import java.beans.PropertyEditor;
20+
import java.beans.PropertyEditorSupport;
21+
import java.text.ParseException;
22+
23+
import org.springframework.context.i18n.LocaleContextHolder;
24+
import org.springframework.format.Formatter;
25+
import org.springframework.util.Assert;
26+
27+
/**
28+
* Adapter that bridges between {@link Formatter} and {@link PropertyEditor}.
29+
*
30+
* @author Juergen Hoeller
31+
* @since 4.2
32+
*/
33+
public class FormatterPropertyEditorAdapter extends PropertyEditorSupport {
34+
35+
private final Formatter<Object> formatter;
36+
37+
38+
/**
39+
* Create a new {@code FormatterPropertyEditorAdapter} for the given {@link Formatter}.
40+
* @param formatter the {@link Formatter} to wrap
41+
*/
42+
@SuppressWarnings("unchecked")
43+
public FormatterPropertyEditorAdapter(Formatter<?> formatter) {
44+
Assert.notNull(formatter, "Formatter must not be null");
45+
this.formatter = (Formatter<Object>) formatter;
46+
}
47+
48+
49+
/**
50+
* Determine the {@link Formatter}-declared field type.
51+
* @return the field type declared in the wrapped {@link Formatter} implementation
52+
* (never {@code null})
53+
* @throws IllegalArgumentException if the {@link Formatter}-declared field type
54+
* cannot be inferred
55+
*/
56+
public Class<?> getFieldType() {
57+
return FormattingConversionService.getFieldType(this.formatter);
58+
}
59+
60+
61+
@Override
62+
public void setAsText(String text) throws IllegalArgumentException {
63+
try {
64+
setValue(this.formatter.parse(text, LocaleContextHolder.getLocale()));
65+
}
66+
catch (ParseException ex) {
67+
throw new IllegalArgumentException("Parse attempt failed for value [" + text + "]", ex);
68+
}
69+
}
70+
71+
@Override
72+
public String getAsText() {
73+
return this.formatter.print(getValue(), LocaleContextHolder.getLocale());
74+
}
75+
76+
}

spring-context/src/main/java/org/springframework/format/support/FormattingConversionService.java

Lines changed: 32 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2014 the original author or authors.
2+
* Copyright 2002-2015 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -67,12 +67,7 @@ public void setEmbeddedValueResolver(StringValueResolver resolver) {
6767

6868
@Override
6969
public void addFormatter(Formatter<?> formatter) {
70-
Class<?> fieldType = GenericTypeResolver.resolveTypeArgument(formatter.getClass(), Formatter.class);
71-
if (fieldType == null) {
72-
throw new IllegalArgumentException("Unable to extract parameterized field type argument from Formatter [" +
73-
formatter.getClass().getName() + "]; does the formatter parameterize the <T> generic type?");
74-
}
75-
addFormatterForFieldType(fieldType, formatter);
70+
addFormatterForFieldType(getFieldType(formatter), formatter);
7671
}
7772

7873
@Override
@@ -88,14 +83,8 @@ public void addFormatterForFieldType(Class<?> fieldType, Printer<?> printer, Par
8883
}
8984

9085
@Override
91-
@SuppressWarnings({ "unchecked", "rawtypes" })
92-
public void addFormatterForFieldAnnotation(AnnotationFormatterFactory annotationFormatterFactory) {
93-
Class<? extends Annotation> annotationType = (Class<? extends Annotation>)
94-
GenericTypeResolver.resolveTypeArgument(annotationFormatterFactory.getClass(), AnnotationFormatterFactory.class);
95-
if (annotationType == null) {
96-
throw new IllegalArgumentException("Unable to extract parameterized Annotation type argument from AnnotationFormatterFactory [" +
97-
annotationFormatterFactory.getClass().getName() + "]; does the factory parameterize the <A extends Annotation> generic type?");
98-
}
86+
public void addFormatterForFieldAnnotation(AnnotationFormatterFactory<? extends Annotation> annotationFormatterFactory) {
87+
Class<? extends Annotation> annotationType = getAnnotationType(annotationFormatterFactory);
9988
if (this.embeddedValueResolver != null && annotationFormatterFactory instanceof EmbeddedValueResolverAware) {
10089
((EmbeddedValueResolverAware) annotationFormatterFactory).setEmbeddedValueResolver(this.embeddedValueResolver);
10190
}
@@ -107,6 +96,28 @@ public void addFormatterForFieldAnnotation(AnnotationFormatterFactory annotation
10796
}
10897

10998

99+
static Class<?> getFieldType(Formatter<?> formatter) {
100+
Class<?> fieldType = GenericTypeResolver.resolveTypeArgument(formatter.getClass(), Formatter.class);
101+
if (fieldType == null) {
102+
throw new IllegalArgumentException("Unable to extract parameterized field type argument from Formatter [" +
103+
formatter.getClass().getName() + "]; does the formatter parameterize the <T> generic type?");
104+
}
105+
return fieldType;
106+
}
107+
108+
@SuppressWarnings("unchecked")
109+
static Class<? extends Annotation> getAnnotationType(AnnotationFormatterFactory<? extends Annotation> factory) {
110+
Class<? extends Annotation> annotationType = (Class<? extends Annotation>)
111+
GenericTypeResolver.resolveTypeArgument(factory.getClass(), AnnotationFormatterFactory.class);
112+
if (annotationType == null) {
113+
throw new IllegalArgumentException("Unable to extract parameterized Annotation type argument from " +
114+
"AnnotationFormatterFactory [" + factory.getClass().getName() +
115+
"]; does the factory parameterize the <A extends Annotation> generic type?");
116+
}
117+
return annotationType;
118+
}
119+
120+
110121
private static class PrinterConverter implements GenericConverter {
111122

112123
private final Class<?> fieldType;
@@ -148,7 +159,7 @@ private Class<?> resolvePrinterObjectType(Printer<?> printer) {
148159

149160
@Override
150161
public String toString() {
151-
return this.fieldType.getName() + " -> " + String.class.getName() + " : " + this.printer;
162+
return (this.fieldType.getName() + " -> " + String.class.getName() + " : " + this.printer);
152163
}
153164
}
154165

@@ -197,7 +208,7 @@ public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor t
197208

198209
@Override
199210
public String toString() {
200-
return String.class.getName() + " -> " + this.fieldType.getName() + ": " + this.parser;
211+
return (String.class.getName() + " -> " + this.fieldType.getName() + ": " + this.parser);
201212
}
202213
}
203214

@@ -249,8 +260,8 @@ public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor t
249260

250261
@Override
251262
public String toString() {
252-
return "@" + this.annotationType.getName() + " " + this.fieldType.getName() + " -> " +
253-
String.class.getName() + ": " + this.annotationFormatterFactory;
263+
return ("@" + this.annotationType.getName() + " " + this.fieldType.getName() + " -> " +
264+
String.class.getName() + ": " + this.annotationFormatterFactory);
254265
}
255266
}
256267

@@ -302,8 +313,8 @@ public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor t
302313

303314
@Override
304315
public String toString() {
305-
return String.class.getName() + " -> @" + this.annotationType.getName() + " " +
306-
this.fieldType.getName() + ": " + this.annotationFormatterFactory;
316+
return (String.class.getName() + " -> @" + this.annotationType.getName() + " " +
317+
this.fieldType.getName() + ": " + this.annotationFormatterFactory);
307318
}
308319
}
309320

spring-context/src/main/java/org/springframework/validation/DataBinder.java

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2014 the original author or authors.
2+
* Copyright 2002-2015 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -42,6 +42,8 @@
4242
import org.springframework.beans.TypeMismatchException;
4343
import org.springframework.core.MethodParameter;
4444
import org.springframework.core.convert.ConversionService;
45+
import org.springframework.format.Formatter;
46+
import org.springframework.format.support.FormatterPropertyEditorAdapter;
4547
import org.springframework.lang.UsesJava8;
4648
import org.springframework.util.Assert;
4749
import org.springframework.util.ClassUtils;
@@ -553,6 +555,7 @@ public List<Validator> getValidators() {
553555
return Collections.unmodifiableList(this.validators);
554556
}
555557

558+
556559
//---------------------------------------------------------------------
557560
// Implementation of PropertyEditorRegistry/TypeConverter interface
558561
//---------------------------------------------------------------------
@@ -576,6 +579,64 @@ public ConversionService getConversionService() {
576579
return this.conversionService;
577580
}
578581

582+
/**
583+
* Add a custom formatter, applying it to all fields matching the
584+
* {@link Formatter}-declared type.
585+
* <p>Registers a corresponding {@link PropertyEditor} adapter underneath the covers.
586+
* @param formatter the formatter to add, generically declared for a specific type
587+
* @since 4.2
588+
* @see #registerCustomEditor(Class, PropertyEditor)
589+
*/
590+
public void addCustomFormatter(Formatter<?> formatter) {
591+
FormatterPropertyEditorAdapter adapter = new FormatterPropertyEditorAdapter(formatter);
592+
getPropertyEditorRegistry().registerCustomEditor(adapter.getFieldType(), adapter);
593+
}
594+
595+
/**
596+
* Add a custom formatter for the field type specified in {@link Formatter} class,
597+
* applying it to the specified fields only, if any, or otherwise to all fields.
598+
* <p>Registers a corresponding {@link PropertyEditor} adapter underneath the covers.
599+
* @param formatter the formatter to add, generically declared for a specific type
600+
* @param fields the fields to apply the formatter to, or none if to be applied to all
601+
* @since 4.2
602+
* @see #registerCustomEditor(Class, String, PropertyEditor)
603+
*/
604+
public void addCustomFormatter(Formatter<?> formatter, String... fields) {
605+
FormatterPropertyEditorAdapter adapter = new FormatterPropertyEditorAdapter(formatter);
606+
Class<?> fieldType = adapter.getFieldType();
607+
if (ObjectUtils.isEmpty(fields)) {
608+
getPropertyEditorRegistry().registerCustomEditor(fieldType, adapter);
609+
}
610+
else {
611+
for (String field : fields) {
612+
getPropertyEditorRegistry().registerCustomEditor(fieldType, field, adapter);
613+
}
614+
}
615+
}
616+
617+
/**
618+
* Add a custom formatter, applying it to the specified field types only, if any,
619+
* or otherwise to all fields matching the {@link Formatter}-declared type.
620+
* <p>Registers a corresponding {@link PropertyEditor} adapter underneath the covers.
621+
* @param formatter the formatter to add (does not need to generically declare a
622+
* field type if field types are explicitly specified as parameters)
623+
* @param fieldTypes the field types to apply the formatter to, or none if to be
624+
* derived from the given {@link Formatter} implementation class
625+
* @since 4.2
626+
* @see #registerCustomEditor(Class, PropertyEditor)
627+
*/
628+
public void addCustomFormatter(Formatter<?> formatter, Class<?>... fieldTypes) {
629+
FormatterPropertyEditorAdapter adapter = new FormatterPropertyEditorAdapter(formatter);
630+
if (ObjectUtils.isEmpty(fieldTypes)) {
631+
getPropertyEditorRegistry().registerCustomEditor(adapter.getFieldType(), adapter);
632+
}
633+
else {
634+
for (Class<?> fieldType : fieldTypes) {
635+
getPropertyEditorRegistry().registerCustomEditor(fieldType, adapter);
636+
}
637+
}
638+
}
639+
579640
@Override
580641
public void registerCustomEditor(Class<?> requiredType, PropertyEditor propertyEditor) {
581642
getPropertyEditorRegistry().registerCustomEditor(requiredType, propertyEditor);

0 commit comments

Comments
 (0)