Skip to content

Commit 8f01770

Browse files
committed
backported "formatters" property to FormattingConversionServiceFactoryBean
1 parent c51b9a7 commit 8f01770

File tree

3 files changed

+220
-23
lines changed

3 files changed

+220
-23
lines changed

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

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import org.springframework.format.FormatterRegistry;
3737
import org.springframework.format.Parser;
3838
import org.springframework.format.Printer;
39+
import org.springframework.util.StringUtils;
3940
import org.springframework.util.StringValueResolver;
4041

4142
/**
@@ -63,6 +64,15 @@ public void setEmbeddedValueResolver(StringValueResolver resolver) {
6364
}
6465

6566

67+
public void addFormatter(Formatter<?> formatter) {
68+
Class<?> fieldType = GenericTypeResolver.resolveTypeArgument(formatter.getClass(), Formatter.class);
69+
if (fieldType == null) {
70+
throw new IllegalArgumentException("Unable to extract parameterized field type argument from Formatter [" +
71+
formatter.getClass().getName() + "]; does the formatter parameterize the <T> generic type?");
72+
}
73+
addFormatterForFieldType(fieldType, formatter);
74+
}
75+
6676
public void addFormatterForFieldType(Class<?> fieldType, Formatter<?> formatter) {
6777
addConverter(new PrinterConverter(fieldType, formatter, this));
6878
addConverter(new ParserConverter(fieldType, formatter, this));
@@ -228,7 +238,7 @@ public Set<ConvertiblePair> getConvertibleTypes() {
228238

229239
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
230240
String text = (String) source;
231-
if (text == null || text.length() == 0) {
241+
if (!StringUtils.hasText(text)) {
232242
return null;
233243
}
234244
Object result;

org.springframework.context/src/main/java/org/springframework/format/support/FormattingConversionServiceFactoryBean.java

Lines changed: 48 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2010 the original author or authors.
2+
* Copyright 2002-2011 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.
@@ -27,6 +27,7 @@
2727
import org.springframework.context.EmbeddedValueResolverAware;
2828
import org.springframework.core.convert.support.ConversionServiceFactory;
2929
import org.springframework.format.AnnotationFormatterFactory;
30+
import org.springframework.format.Formatter;
3031
import org.springframework.format.FormatterRegistry;
3132
import org.springframework.format.Parser;
3233
import org.springframework.format.Printer;
@@ -55,66 +56,91 @@ public class FormattingConversionServiceFactoryBean
5556

5657
private Set<?> converters;
5758

59+
private Set<?> formatters;
60+
5861
private StringValueResolver embeddedValueResolver;
5962

6063
private FormattingConversionService conversionService;
6164

6265

6366
/**
64-
* Configure the set of custom converter objects that should be added:
65-
* implementing {@link org.springframework.core.convert.converter.Converter},
67+
* Configure the set of custom converter objects that should be added.
68+
* @param converters instances of any of the following:
69+
* {@link org.springframework.core.convert.converter.Converter},
6670
* {@link org.springframework.core.convert.converter.ConverterFactory},
67-
* or {@link org.springframework.core.convert.converter.GenericConverter}.
71+
* {@link org.springframework.core.convert.converter.GenericConverter}
6872
*/
6973
public void setConverters(Set<?> converters) {
7074
this.converters = converters;
7175
}
7276

77+
/**
78+
* Configure the set of custom formatter objects that should be added.
79+
* @param formatters instances of {@link Formatter} or {@link AnnotationFormatterFactory}
80+
*/
81+
public void setFormatters(Set<?> formatters) {
82+
this.formatters = formatters;
83+
}
84+
7385
public void setEmbeddedValueResolver(StringValueResolver embeddedValueResolver) {
7486
this.embeddedValueResolver = embeddedValueResolver;
7587
}
7688

89+
7790
public void afterPropertiesSet() {
7891
this.conversionService = new FormattingConversionService();
7992
this.conversionService.setEmbeddedValueResolver(this.embeddedValueResolver);
8093
ConversionServiceFactory.addDefaultConverters(this.conversionService);
8194
ConversionServiceFactory.registerConverters(this.converters, this.conversionService);
82-
installFormatters(this.conversionService);
83-
}
84-
85-
86-
// implementing FactoryBean
87-
88-
public FormattingConversionService getObject() {
89-
return this.conversionService;
95+
registerFormatters();
9096
}
9197

92-
public Class<? extends FormattingConversionService> getObjectType() {
93-
return FormattingConversionService.class;
94-
}
95-
96-
public boolean isSingleton() {
97-
return true;
98+
private void registerFormatters() {
99+
if (this.formatters != null) {
100+
for (Object formatter : this.formatters) {
101+
if (formatter instanceof Formatter<?>) {
102+
this.conversionService.addFormatter((Formatter<?>) formatter);
103+
}
104+
else if (formatter instanceof AnnotationFormatterFactory<?>) {
105+
this.conversionService.addFormatterForFieldAnnotation((AnnotationFormatterFactory<?>) formatter);
106+
}
107+
else {
108+
throw new IllegalArgumentException(
109+
"Custom formatters must be implementations of Formatter or AnnotationFormatterFactory");
110+
}
111+
}
112+
}
113+
installFormatters(this.conversionService);
98114
}
99115

100-
101-
// subclassing hooks
102-
103116
/**
104117
* Install Formatters and Converters into the new FormattingConversionService using the FormatterRegistry SPI.
105118
* Subclasses may override to customize the set of formatters and/or converters that are installed.
106119
*/
107120
protected void installFormatters(FormatterRegistry registry) {
108121
registry.addFormatterForFieldAnnotation(new NumberFormatAnnotationFormatterFactory());
109122
if (jodaTimePresent) {
110-
new JodaTimeFormattingConfigurer().installJodaTimeFormatting(registry);
123+
new JodaTimeFormattingConfigurer().installJodaTimeFormatting(registry);
111124
}
112125
else {
113126
registry.addFormatterForFieldAnnotation(new NoJodaDateTimeFormatAnnotationFormatterFactory());
114127
}
115128
}
116129

117130

131+
public FormattingConversionService getObject() {
132+
return this.conversionService;
133+
}
134+
135+
public Class<? extends FormattingConversionService> getObjectType() {
136+
return FormattingConversionService.class;
137+
}
138+
139+
public boolean isSingleton() {
140+
return true;
141+
}
142+
143+
118144
/**
119145
* Dummy AnnotationFormatterFactory that simply fails if @DateTimeFormat is being used
120146
* without the JodaTime library being present.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
/*
2+
* Copyright 2002-2011 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.lang.annotation.ElementType;
20+
import java.lang.annotation.Retention;
21+
import java.lang.annotation.RetentionPolicy;
22+
import java.lang.annotation.Target;
23+
import java.text.ParseException;
24+
import java.util.HashSet;
25+
import java.util.Locale;
26+
import java.util.Set;
27+
28+
import org.junit.Test;
29+
30+
import org.springframework.core.convert.TypeDescriptor;
31+
import org.springframework.format.AnnotationFormatterFactory;
32+
import org.springframework.format.Formatter;
33+
import org.springframework.format.Parser;
34+
import org.springframework.format.Printer;
35+
import org.springframework.format.annotation.NumberFormat;
36+
import org.springframework.format.annotation.NumberFormat.Style;
37+
38+
import static org.junit.Assert.*;
39+
40+
/**
41+
* @author Rossen Stoyanchev
42+
*/
43+
public class FormattingConversionServiceFactoryBeanTests {
44+
45+
@Test
46+
public void testDefaultFormatters() throws Exception {
47+
FormattingConversionServiceFactoryBean factory = new FormattingConversionServiceFactoryBean();
48+
factory.afterPropertiesSet();
49+
FormattingConversionService fcs = factory.getObject();
50+
TypeDescriptor descriptor = new TypeDescriptor(TestBean.class.getDeclaredField("percent"));
51+
Object value = fcs.convert("5%", TypeDescriptor.valueOf(String.class), descriptor);
52+
assertEquals(.05, value);
53+
value = fcs.convert(.05, descriptor, TypeDescriptor.valueOf(String.class));
54+
assertEquals("5%", value);
55+
}
56+
57+
@Test
58+
public void testCustomFormatter() throws Exception {
59+
FormattingConversionServiceFactoryBean factory = new FormattingConversionServiceFactoryBean();
60+
Set<Object> formatters = new HashSet<Object>();
61+
formatters.add(new TestBeanFormatter());
62+
formatters.add(new SpecialIntAnnotationFormatterFactory());
63+
factory.setFormatters(formatters);
64+
factory.afterPropertiesSet();
65+
FormattingConversionService fcs = factory.getObject();
66+
67+
TestBean testBean = fcs.convert("5", TestBean.class);
68+
assertEquals(5, testBean.getSpecialInt());
69+
assertEquals("5", fcs.convert(testBean, String.class));
70+
71+
TypeDescriptor descriptor = new TypeDescriptor(TestBean.class.getDeclaredField("specialInt"));
72+
Object value = fcs.convert(":5", TypeDescriptor.valueOf(String.class), descriptor);
73+
assertEquals(5, value);
74+
value = fcs.convert(5, descriptor, TypeDescriptor.valueOf(String.class));
75+
assertEquals(":5", value);
76+
}
77+
78+
@Test
79+
public void testInvalidFormatter() throws Exception {
80+
FormattingConversionServiceFactoryBean factory = new FormattingConversionServiceFactoryBean();
81+
Set<Object> formatters = new HashSet<Object>();
82+
formatters.add(new Object());
83+
factory.setFormatters(formatters);
84+
try {
85+
factory.afterPropertiesSet();
86+
fail("Expected formatter to be rejected");
87+
}
88+
catch (IllegalArgumentException ex) {
89+
// expected
90+
}
91+
}
92+
93+
94+
@Target({ ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER })
95+
@Retention(RetentionPolicy.RUNTIME)
96+
private @interface SpecialInt {
97+
}
98+
99+
private static class TestBean {
100+
101+
@SuppressWarnings("unused")
102+
@NumberFormat(style = Style.PERCENT)
103+
private double percent;
104+
105+
@SpecialInt
106+
private int specialInt;
107+
108+
public int getSpecialInt() {
109+
return specialInt;
110+
}
111+
112+
public void setSpecialInt(int field) {
113+
this.specialInt = field;
114+
}
115+
116+
}
117+
118+
private static class TestBeanFormatter implements Formatter<TestBean> {
119+
120+
public String print(TestBean object, Locale locale) {
121+
return String.valueOf(object.getSpecialInt());
122+
}
123+
124+
public TestBean parse(String text, Locale locale) throws ParseException {
125+
TestBean object = new TestBean();
126+
object.setSpecialInt(Integer.parseInt(text));
127+
return object;
128+
}
129+
130+
}
131+
132+
private static class SpecialIntAnnotationFormatterFactory implements AnnotationFormatterFactory<SpecialInt> {
133+
134+
private final Set<Class<?>> fieldTypes = new HashSet<Class<?>>(1);
135+
136+
public SpecialIntAnnotationFormatterFactory() {
137+
fieldTypes.add(Integer.class);
138+
}
139+
140+
public Set<Class<?>> getFieldTypes() {
141+
return fieldTypes;
142+
}
143+
144+
public Printer<?> getPrinter(SpecialInt annotation, Class<?> fieldType) {
145+
return new Printer<Integer>() {
146+
public String print(Integer object, Locale locale) {
147+
return ":" + object.toString();
148+
}
149+
};
150+
}
151+
152+
public Parser<?> getParser(SpecialInt annotation, Class<?> fieldType) {
153+
return new Parser<Integer>() {
154+
public Integer parse(String text, Locale locale) throws ParseException {
155+
return Integer.parseInt(text.substring(1));
156+
}
157+
};
158+
}
159+
}
160+
161+
}

0 commit comments

Comments
 (0)