/*
* Copyright 2002-2016 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.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Properties;
import org.joda.time.DateTime;
import org.joda.time.LocalDate;
import org.joda.time.format.DateTimeFormat;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.beans.ConfigurablePropertyAccessor;
import org.springframework.beans.PropertyAccessorFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.convert.ConversionFailedException;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.converter.ConverterFactory;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.format.Formatter;
import org.springframework.format.Printer;
import org.springframework.format.annotation.NumberFormat;
import org.springframework.format.datetime.joda.DateTimeParser;
import org.springframework.format.datetime.joda.JodaDateTimeFormatAnnotationFormatterFactory;
import org.springframework.format.datetime.joda.ReadablePartialPrinter;
import org.springframework.format.number.NumberStyleFormatter;
import static org.junit.Assert.*;
/**
* @author Keith Donald
* @author Juergen Hoeller
* @author Kazuki Shimizu
* @author Sam Brannen
*/
public class FormattingConversionServiceTests {
private FormattingConversionService formattingService;
@Before
public void setUp() {
formattingService = new FormattingConversionService();
DefaultConversionService.addDefaultConverters(formattingService);
LocaleContextHolder.setLocale(Locale.US);
}
@After
public void tearDown() {
LocaleContextHolder.setLocale(null);
}
@Test
public void formatFieldForTypeWithFormatter() throws ParseException {
formattingService.addFormatterForFieldType(Number.class, new NumberStyleFormatter());
String formatted = formattingService.convert(3, String.class);
assertEquals("3", formatted);
Integer i = formattingService.convert("3", Integer.class);
assertEquals(new Integer(3), i);
}
@Test
public void formatFieldForTypeWithPrinterParserWithCoercion() throws ParseException {
formattingService.addConverter(new Converter<DateTime, LocalDate>() {
@Override
public LocalDate convert(DateTime source) {
return source.toLocalDate();
}
});
formattingService.addFormatterForFieldType(LocalDate.class, new ReadablePartialPrinter(DateTimeFormat
.shortDate()), new DateTimeParser(DateTimeFormat.shortDate()));
String formatted = formattingService.convert(new LocalDate(2009, 10, 31), String.class);
assertEquals("10/31/09", formatted);
LocalDate date = formattingService.convert("10/31/09", LocalDate.class);
assertEquals(new LocalDate(2009, 10, 31), date);
}
@Test
@SuppressWarnings("resource")
public void formatFieldForValueInjection() {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
ac.registerBeanDefinition("valueBean", new RootBeanDefinition(ValueBean.class));
ac.registerBeanDefinition("conversionService", new RootBeanDefinition(FormattingConversionServiceFactoryBean.class));
ac.refresh();
ValueBean valueBean = ac.getBean(ValueBean.class);
assertEquals(new LocalDate(2009, 10, 31), new LocalDate(valueBean.date));
}
@Test
@SuppressWarnings("resource")
public void formatFieldForValueInjectionUsingMetaAnnotations() {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
RootBeanDefinition bd = new RootBeanDefinition(MetaValueBean.class);
bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
ac.registerBeanDefinition("valueBean", bd);
ac.registerBeanDefinition("conversionService", new RootBeanDefinition(FormattingConversionServiceFactoryBean.class));
ac.registerBeanDefinition("ppc", new RootBeanDefinition(PropertyPlaceholderConfigurer.class));
ac.refresh();
System.setProperty("myDate", "10-31-09");
System.setProperty("myNumber", "99.99%");
try {
MetaValueBean valueBean = ac.getBean(MetaValueBean.class);
assertEquals(new LocalDate(2009, 10, 31), new LocalDate(valueBean.date));
assertEquals(Double.valueOf(0.9999), valueBean.number);
}
finally {
System.clearProperty("myDate");
System.clearProperty("myNumber");
}
}
@Test
public void formatFieldForAnnotation() throws Exception {
formattingService.addFormatterForFieldAnnotation(new JodaDateTimeFormatAnnotationFormatterFactory());
doTestFormatFieldForAnnotation(Model.class, false);
}
@Test
public void formatFieldForAnnotationWithDirectFieldAccess() throws Exception {
formattingService.addFormatterForFieldAnnotation(new JodaDateTimeFormatAnnotationFormatterFactory());
doTestFormatFieldForAnnotation(Model.class, true);
}
@Test
@SuppressWarnings("resource")
public void formatFieldForAnnotationWithPlaceholders() throws Exception {
GenericApplicationContext context = new GenericApplicationContext();
PropertyPlaceholderConfigurer ppc = new PropertyPlaceholderConfigurer();
Properties props = new Properties();
props.setProperty("dateStyle", "S-");
props.setProperty("datePattern", "M-d-yy");
ppc.setProperties(props);
context.getBeanFactory().registerSingleton("ppc", ppc);
context.refresh();
context.getBeanFactory().initializeBean(formattingService, "formattingService");
formattingService.addFormatterForFieldAnnotation(new JodaDateTimeFormatAnnotationFormatterFactory());
doTestFormatFieldForAnnotation(ModelWithPlaceholders.class, false);
}
@Test
@SuppressWarnings("resource")
public void formatFieldForAnnotationWithPlaceholdersAndFactoryBean() throws Exception {
GenericApplicationContext context = new GenericApplicationContext();
PropertyPlaceholderConfigurer ppc = new PropertyPlaceholderConfigurer();
Properties props = new Properties();
props.setProperty("dateStyle", "S-");
props.setProperty("datePattern", "M-d-yy");
ppc.setProperties(props);
context.registerBeanDefinition("formattingService", new RootBeanDefinition(FormattingConversionServiceFactoryBean.class));
context.getBeanFactory().registerSingleton("ppc", ppc);
context.refresh();
formattingService = context.getBean("formattingService", FormattingConversionService.class);
doTestFormatFieldForAnnotation(ModelWithPlaceholders.class, false);
}
@SuppressWarnings("unchecked")
private void doTestFormatFieldForAnnotation(Class<?> modelClass, boolean directFieldAccess) throws Exception {
formattingService.addConverter(new Converter<Date, Long>() {
@Override
public Long convert(Date source) {
return source.getTime();
}
});
formattingService.addConverter(new Converter<DateTime, Date>() {
@Override
public Date convert(DateTime source) {
return source.toDate();
}
});
String formatted = (String) formattingService.convert(new LocalDate(2009, 10, 31).toDateTimeAtCurrentTime()
.toDate(), new TypeDescriptor(modelClass.getField("date")), TypeDescriptor.valueOf(String.class));
assertEquals("10/31/09", formatted);
LocalDate date = new LocalDate(formattingService.convert("10/31/09", TypeDescriptor.valueOf(String.class),
new TypeDescriptor(modelClass.getField("date"))));
assertEquals(new LocalDate(2009, 10, 31), date);
List<Date> dates = new ArrayList<>();
dates.add(new LocalDate(2009, 10, 31).toDateTimeAtCurrentTime().toDate());
dates.add(new LocalDate(2009, 11, 1).toDateTimeAtCurrentTime().toDate());
dates.add(new LocalDate(2009, 11, 2).toDateTimeAtCurrentTime().toDate());
formatted = (String) formattingService.convert(dates,
new TypeDescriptor(modelClass.getField("dates")), TypeDescriptor.valueOf(String.class));
assertEquals("10-31-09,11-1-09,11-2-09", formatted);
dates = (List<Date>) formattingService.convert("10-31-09,11-1-09,11-2-09",
TypeDescriptor.valueOf(String.class), new TypeDescriptor(modelClass.getField("dates")));
assertEquals(new LocalDate(2009, 10, 31), new LocalDate(dates.get(0)));
assertEquals(new LocalDate(2009, 11, 1), new LocalDate(dates.get(1)));
assertEquals(new LocalDate(2009, 11, 2), new LocalDate(dates.get(2)));
Object model = modelClass.newInstance();
ConfigurablePropertyAccessor accessor = directFieldAccess ? PropertyAccessorFactory.forDirectFieldAccess(model) :
PropertyAccessorFactory.forBeanPropertyAccess(model);
accessor.setConversionService(formattingService);
accessor.setPropertyValue("dates", "10-31-09,11-1-09,11-2-09");
dates = (List<Date>) accessor.getPropertyValue("dates");
assertEquals(new LocalDate(2009, 10, 31), new LocalDate(dates.get(0)));
assertEquals(new LocalDate(2009, 11, 1), new LocalDate(dates.get(1)));
assertEquals(new LocalDate(2009, 11, 2), new LocalDate(dates.get(2)));
if (!directFieldAccess) {
accessor.setPropertyValue("dates[0]", "10-30-09");
accessor.setPropertyValue("dates[1]", "10-1-09");
accessor.setPropertyValue("dates[2]", "10-2-09");
dates = (List<Date>) accessor.getPropertyValue("dates");
assertEquals(new LocalDate(2009, 10, 30), new LocalDate(dates.get(0)));
assertEquals(new LocalDate(2009, 10, 1), new LocalDate(dates.get(1)));
assertEquals(new LocalDate(2009, 10, 2), new LocalDate(dates.get(2)));
}
}
@Test
public void printNull() throws ParseException {
formattingService.addFormatterForFieldType(Number.class, new NumberStyleFormatter());
assertEquals("", formattingService.convert(null, TypeDescriptor.valueOf(Integer.class), TypeDescriptor.valueOf(String.class)));
}
@Test
public void parseNull() throws ParseException {
formattingService.addFormatterForFieldType(Number.class, new NumberStyleFormatter());
assertNull(formattingService
.convert(null, TypeDescriptor.valueOf(String.class), TypeDescriptor.valueOf(Integer.class)));
}
@Test
public void parseEmptyString() throws ParseException {
formattingService.addFormatterForFieldType(Number.class, new NumberStyleFormatter());
assertNull(formattingService.convert("", TypeDescriptor.valueOf(String.class), TypeDescriptor.valueOf(Integer.class)));
}
@Test
public void parseBlankString() throws ParseException {
formattingService.addFormatterForFieldType(Number.class, new NumberStyleFormatter());
assertNull(formattingService.convert(" ", TypeDescriptor.valueOf(String.class), TypeDescriptor.valueOf(Integer.class)));
}
@Test(expected = ConversionFailedException.class)
public void parseParserReturnsNull() throws ParseException {
formattingService.addFormatterForFieldType(Integer.class, new NullReturningFormatter());
assertNull(formattingService.convert("1", TypeDescriptor.valueOf(String.class), TypeDescriptor.valueOf(Integer.class)));
}
@Test(expected = ConversionFailedException.class)
public void parseNullPrimitiveProperty() throws ParseException {
formattingService.addFormatterForFieldType(Integer.class, new NumberStyleFormatter());
assertNull(formattingService.convert(null, TypeDescriptor.valueOf(String.class), TypeDescriptor.valueOf(int.class)));
}
@Test
public void printNullDefault() throws ParseException {
assertEquals(null, formattingService
.convert(null, TypeDescriptor.valueOf(Integer.class), TypeDescriptor.valueOf(String.class)));
}
@Test
public void parseNullDefault() throws ParseException {
assertNull(formattingService
.convert(null, TypeDescriptor.valueOf(String.class), TypeDescriptor.valueOf(Integer.class)));
}
@Test
public void parseEmptyStringDefault() throws ParseException {
assertNull(formattingService.convert("", TypeDescriptor.valueOf(String.class), TypeDescriptor.valueOf(Integer.class)));
}
@Test
public void formatFieldForAnnotationWithSubclassAsFieldType() throws Exception {
formattingService.addFormatterForFieldAnnotation(new JodaDateTimeFormatAnnotationFormatterFactory() {
@Override
public Printer<?> getPrinter(org.springframework.format.annotation.DateTimeFormat annotation, Class<?> fieldType) {
assertEquals(MyDate.class, fieldType);
return super.getPrinter(annotation, fieldType);
}
});
formattingService.addConverter(new Converter<MyDate, Long>() {
@Override
public Long convert(MyDate source) {
return source.getTime();
}
});
formattingService.addConverter(new Converter<MyDate, Date>() {
@Override
public Date convert(MyDate source) {
return source;
}
});
formattingService.convert(new MyDate(), new TypeDescriptor(ModelWithSubclassField.class.getField("date")),
TypeDescriptor.valueOf(String.class));
}
@Test
public void registerDefaultValueViaFormatter() {
registerDefaultValue(Date.class, new Date());
}
private <T> void registerDefaultValue(Class<T> clazz, final T defaultValue) {
formattingService.addFormatterForFieldType(clazz, new Formatter<T>() {
@Override
public T parse(String text, Locale locale) throws ParseException {
return defaultValue;
}
@Override
public String print(T t, Locale locale) {
return defaultValue.toString();
}
@Override
public String toString() {
return defaultValue.toString();
}
});
}
@Test
public void introspectedFormatter() throws ParseException {
formattingService.addFormatter(new NumberStyleFormatter());
assertNull(formattingService.convert(null, TypeDescriptor.valueOf(String.class), TypeDescriptor.valueOf(Integer.class)));
}
@Test
public void proxiedFormatter() throws ParseException {
Formatter<?> formatter = new NumberStyleFormatter();
formattingService.addFormatter((Formatter<?>) new ProxyFactory(formatter).getProxy());
assertNull(formattingService.convert(null, TypeDescriptor.valueOf(String.class), TypeDescriptor.valueOf(Integer.class)));
}
@Test
public void introspectedConverter() {
formattingService.addConverter(new IntegerConverter());
assertEquals(Integer.valueOf(1), formattingService.convert("1", Integer.class));
}
@Test
public void proxiedConverter() {
Converter<?, ?> converter = new IntegerConverter();
formattingService.addConverter((Converter<?, ?>) new ProxyFactory(converter).getProxy());
assertEquals(Integer.valueOf(1), formattingService.convert("1", Integer.class));
}
@Test
public void introspectedConverterFactory() {
formattingService.addConverterFactory(new IntegerConverterFactory());
assertEquals(Integer.valueOf(1), formattingService.convert("1", Integer.class));
}
@Test
public void proxiedConverterFactory() {
ConverterFactory<?, ?> converterFactory = new IntegerConverterFactory();
formattingService.addConverterFactory((ConverterFactory<?, ?>) new ProxyFactory(converterFactory).getProxy());
assertEquals(Integer.valueOf(1), formattingService.convert("1", Integer.class));
}
public static class ValueBean {
@Value("10-31-09")
@org.springframework.format.annotation.DateTimeFormat(pattern="MM-d-yy")
public Date date;
}
public static class MetaValueBean {
@MyDateAnn
public Date date;
@MyNumberAnn
public Double number;
}
@Value("${myDate}")
@org.springframework.format.annotation.DateTimeFormat(pattern="MM-d-yy")
@Retention(RetentionPolicy.RUNTIME)
public @interface MyDateAnn {
}
@Value("${myNumber}")
@NumberFormat(style = NumberFormat.Style.PERCENT)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyNumberAnn {
}
public static class Model {
@org.springframework.format.annotation.DateTimeFormat(style="S-")
public Date date;
@org.springframework.format.annotation.DateTimeFormat(pattern="M-d-yy")
public List<Date> dates;
public List<Date> getDates() {
return dates;
}
public void setDates(List<Date> dates) {
this.dates = dates;
}
}
public static class ModelWithPlaceholders {
@org.springframework.format.annotation.DateTimeFormat(style="${dateStyle}")
public Date date;
@MyDatePattern
public List<Date> dates;
public List<Date> getDates() {
return dates;
}
public void setDates(List<Date> dates) {
this.dates = dates;
}
}
@org.springframework.format.annotation.DateTimeFormat(pattern="${datePattern}")
@Retention(RetentionPolicy.RUNTIME)
public @interface MyDatePattern {
}
public static class NullReturningFormatter implements Formatter<Integer> {
@Override
public String print(Integer object, Locale locale) {
return null;
}
@Override
public Integer parse(String text, Locale locale) throws ParseException {
return null;
}
}
@SuppressWarnings("serial")
public static class MyDate extends Date {
}
private static class ModelWithSubclassField {
@org.springframework.format.annotation.DateTimeFormat(style = "S-")
public MyDate date;
}
private static class IntegerConverter implements Converter<String, Integer> {
@Override
public Integer convert(String source) {
return Integer.parseInt(source);
}
}
private static class IntegerConverterFactory implements ConverterFactory<String, Number> {
@Override
@SuppressWarnings("unchecked")
public <T extends Number> Converter<String, T> getConverter(Class<T> targetType) {
if (Integer.class == targetType) {
return (Converter<String, T>) new IntegerConverter();
}
else {
throw new IllegalStateException();
}
}
}
}