From 20f5f99e9a44bbafabe29a56f12e8fe7cb7c8925 Mon Sep 17 00:00:00 2001 From: Keith Donald Date: Mon, 17 Aug 2009 23:13:29 +0000 Subject: [PATCH] SPR-6013, SPR-6014, SPR-6015 tests --- .../ui/format/GenericFormatterRegistry.java | 190 +++++++++++++----- .../format/GenericFormatterRegistryTests.java | 122 ++++++++++- 2 files changed, 253 insertions(+), 59 deletions(-) diff --git a/org.springframework.context/src/main/java/org/springframework/ui/format/GenericFormatterRegistry.java b/org.springframework.context/src/main/java/org/springframework/ui/format/GenericFormatterRegistry.java index c2254113cf5..6b060e5be8c 100644 --- a/org.springframework.context/src/main/java/org/springframework/ui/format/GenericFormatterRegistry.java +++ b/org.springframework.context/src/main/java/org/springframework/ui/format/GenericFormatterRegistry.java @@ -19,19 +19,24 @@ import java.lang.annotation.Annotation; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; +import java.text.ParseException; import java.util.HashMap; +import java.util.Locale; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.springframework.core.GenericTypeResolver; import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.TypeDescriptor; +import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.util.Assert; /** * A generic implementation of {@link FormatterRegistry} suitable for use in most binding environments. * @author Keith Donald * @since 3.0 + * @see #add(Formatter) * @see #add(Class, Formatter) * @see #add(AnnotationFormatterFactory) */ @@ -42,10 +47,21 @@ public class GenericFormatterRegistry implements FormatterRegistry { private Map annotationFormatters = new HashMap(); + private ConversionService conversionService = new DefaultConversionService(); + + /** + * Sets the type conversion service used to coerse objects to the types required for Formatting purposes. + * @param conversionService the conversion service + * @see #add(Class, Formatter) + */ + public void setConversionService(ConversionService conversionService) { + this.conversionService = conversionService; + } + // implementing FormatterRegistry - + public void add(Formatter formatter) { - // TODO + typeFormatters.put(getFormattedObjectType(formatter.getClass()), formatter); } public void add(Class objectType, Formatter formatter) { @@ -57,11 +73,79 @@ public class GenericFormatterRegistry implements FormatterRegistry { } public void add(AnnotationFormatterFactory factory) { - annotationFormatters.put(getAnnotationType(factory), factory); + annotationFormatters.put(getAnnotationType(factory.getClass()), factory); } public Formatter getFormatter(TypeDescriptor type) { Assert.notNull(type, "The TypeDescriptor is required"); + Formatter formatter = getAnnotationFormatter(type); + if (formatter == null) { + formatter = getTypeFormatter(type.getType()); + } + return formatter; + } + + // internal helpers + + private Class getFormattedObjectType(Class formatterClass) { + Class classToIntrospect = formatterClass; + while (classToIntrospect != null) { + Type[] ifcs = classToIntrospect.getGenericInterfaces(); + for (Type ifc : ifcs) { + if (ifc instanceof ParameterizedType) { + ParameterizedType paramIfc = (ParameterizedType) ifc; + Type rawType = paramIfc.getRawType(); + if (Formatter.class.equals(rawType)) { + Type arg = paramIfc.getActualTypeArguments()[0]; + if (arg instanceof TypeVariable) { + arg = GenericTypeResolver.resolveTypeVariable((TypeVariable) arg, formatterClass); + } + if (arg instanceof Class) { + return (Class) arg; + } + } else if (Formatter.class.isAssignableFrom((Class) rawType)) { + return getFormattedObjectType((Class) rawType); + } + } else if (Formatter.class.isAssignableFrom((Class) ifc)) { + return getFormattedObjectType((Class) ifc); + } + } + classToIntrospect = classToIntrospect.getSuperclass(); + } + return null; + } + + private Class getAnnotationType(Class factoryClass) { + Class classToIntrospect = factoryClass; + while (classToIntrospect != null) { + Type[] ifcs = classToIntrospect.getGenericInterfaces(); + for (Type ifc : ifcs) { + if (ifc instanceof ParameterizedType) { + ParameterizedType paramIfc = (ParameterizedType) ifc; + Type rawType = paramIfc.getRawType(); + if (AnnotationFormatterFactory.class.equals(rawType)) { + Type arg = paramIfc.getActualTypeArguments()[0]; + if (arg instanceof TypeVariable) { + arg = GenericTypeResolver.resolveTypeVariable((TypeVariable) arg, factoryClass); + } + if (arg instanceof Class) { + return (Class) arg; + } + } else if (AnnotationFormatterFactory.class.isAssignableFrom((Class) rawType)) { + return getAnnotationType((Class) rawType); + } + } else if (AnnotationFormatterFactory.class.isAssignableFrom((Class) ifc)) { + return getAnnotationType((Class) ifc); + } + } + classToIntrospect = classToIntrospect.getSuperclass(); + } + throw new IllegalArgumentException( + "Unable to extract Annotation type A argument from AnnotationFormatterFactory [" + + factoryClass.getName() + "]; does the factory parameterize the generic type?"); + } + + private Formatter getAnnotationFormatter(TypeDescriptor type) { Annotation[] annotations = type.getAnnotations(); for (Annotation a : annotations) { AnnotationFormatterFactory factory = annotationFormatters.get(a.annotationType()); @@ -69,65 +153,42 @@ public class GenericFormatterRegistry implements FormatterRegistry { return factory.getFormatter(a); } } - return getFormatter(type.getType()); + return null; } - - // internal helpers - - private Formatter getFormatter(Class type) { + + private Formatter getTypeFormatter(Class type) { Assert.notNull(type, "The Class of the object to format is required"); Formatter formatter = typeFormatters.get(type); if (formatter != null) { - return formatter; - } else { - Formatted formatted = AnnotationUtils.findAnnotation(type, Formatted.class); - if (formatted != null) { - Class formatterClass = formatted.value(); - try { - formatter = (Formatter) formatterClass.newInstance(); - } catch (InstantiationException e) { - throw new IllegalStateException( - "Formatter referenced by @Formatted annotation does not have default constructor", e); - } catch (IllegalAccessException e) { - throw new IllegalStateException( - "Formatter referenced by @Formatted annotation does not have public constructor", e); - } - typeFormatters.put(type, formatter); + Class formattedObjectType = getFormattedObjectType(formatter.getClass()); + if (type.isAssignableFrom(formattedObjectType)) { return formatter; } else { - return null; + return new ConvertingFormatter(type, formattedObjectType, formatter); } + } else { + return getDefaultFormatter(type); } } - - 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); + private Formatter getDefaultFormatter(Class type) { + Formatted formatted = AnnotationUtils.findAnnotation(type, Formatted.class); + if (formatted != null) { + Class formatterClass = formatted.value(); + try { + Formatter formatter = (Formatter) formatterClass.newInstance(); + typeFormatters.put(type, formatter); + return formatter; + } catch (InstantiationException e) { + throw new IllegalStateException( + "Formatter referenced by @Formatted annotation does not have default constructor", e); + } catch (IllegalAccessException e) { + throw new IllegalStateException( + "Formatter referenced by @Formatted annotation does not have public constructor", e); + } + } else { + return null; } - if (parameterType instanceof Class) { - return (Class) parameterType; - } - throw new IllegalArgumentException("Unable to obtain the java.lang.Class for parameterType [" + parameterType - + "] on Formatter [" + converterClass.getName() + "]"); } private static class SimpleAnnotationFormatterFactory implements AnnotationFormatterFactory { @@ -144,4 +205,31 @@ public class GenericFormatterRegistry implements FormatterRegistry { } + private class ConvertingFormatter implements Formatter { + + private Class type; + + private Class formattedObjectType; + + private Formatter targetFormatter; + + public ConvertingFormatter(Class type, Class formattedObjectType, Formatter targetFormatter) { + this.type = type; + this.formattedObjectType = formattedObjectType; + this.targetFormatter = targetFormatter; + } + + public String format(Object object, Locale locale) { + object = conversionService.convert(object, formattedObjectType); + return targetFormatter.format(object, locale); + } + + public Object parse(String formatted, Locale locale) throws ParseException { + Object parsed = targetFormatter.parse(formatted, locale); + parsed = conversionService.convert(parsed, type); + return parsed; + } + + } + } \ No newline at end of file diff --git a/org.springframework.context/src/test/java/org/springframework/ui/format/GenericFormatterRegistryTests.java b/org.springframework.context/src/test/java/org/springframework/ui/format/GenericFormatterRegistryTests.java index 692f05ab5ee..266923c85cf 100644 --- a/org.springframework.context/src/test/java/org/springframework/ui/format/GenericFormatterRegistryTests.java +++ b/org.springframework.context/src/test/java/org/springframework/ui/format/GenericFormatterRegistryTests.java @@ -1,26 +1,30 @@ package org.springframework.ui.format; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import java.math.BigDecimal; +import java.text.ParseException; import java.util.Locale; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.springframework.core.convert.TypeDescriptor; +import org.springframework.core.style.ToStringCreator; +import org.springframework.ui.format.number.CurrencyFormat; +import org.springframework.ui.format.number.CurrencyFormatter; import org.springframework.ui.format.number.IntegerFormatter; public class GenericFormatterRegistryTests { private GenericFormatterRegistry registry; - + @Before public void setUp() { registry = new GenericFormatterRegistry(); } @Test - @Ignore public void testAdd() { registry.add(new IntegerFormatter()); Formatter formatter = registry.getFormatter(typeDescriptor(Long.class)); @@ -29,8 +33,7 @@ public class GenericFormatterRegistryTests { } @Test - @Ignore - public void testAddByOtherObjectType() { + public void testAddByObjectType() { registry.add(Integer.class, new IntegerFormatter()); Formatter formatter = registry.getFormatter(typeDescriptor(Integer.class)); String formatted = formatter.format(new Integer(3), Locale.US); @@ -38,11 +41,114 @@ public class GenericFormatterRegistryTests { } @Test - @Ignore - public void testAddAnnotationFormatterFactory() { + public void testAddAnnotationFormatterFactory() throws Exception { + registry.add(new CurrencyAnnotationFormatterFactory()); + Formatter formatter = registry.getFormatter(new TypeDescriptor(getClass().getField("currencyField"))); + String formatted = formatter.format(new BigDecimal("5.00"), Locale.US); + assertEquals("$5.00", formatted); } + @Test + public void testGetDefaultFormatterForType() { + Formatter formatter = registry.getFormatter(typeDescriptor(Address.class)); + Address address = new Address(); + address.street = "12345 Bel Aire Estates"; + address.city = "Palm Bay"; + address.state = "FL"; + address.zip = "12345"; + String formatted = formatter.format(address, Locale.US); + assertEquals("12345 Bel Aire Estates:Palm Bay:FL:12345", formatted); + } + + @Test + public void testGetNoFormatterForType() { + assertNull(registry.getFormatter(typeDescriptor(Integer.class))); + } + + @CurrencyFormat + public BigDecimal currencyField; + private static TypeDescriptor typeDescriptor(Class clazz) { return TypeDescriptor.valueOf(clazz); } -} + + public static class CurrencyAnnotationFormatterFactory implements + AnnotationFormatterFactory { + public Formatter getFormatter(CurrencyFormat annotation) { + return new CurrencyFormatter(); + } + } + + @Formatted(AddressFormatter.class) + public static class Address { + private String street; + private String city; + private String state; + private String zip; + private String country; + + public String getStreet() { + return street; + } + + public void setStreet(String street) { + this.street = street; + } + + public String getCity() { + return city; + } + + public void setCity(String city) { + this.city = city; + } + + public String getState() { + return state; + } + + public void setState(String state) { + this.state = state; + } + + public String getZip() { + return zip; + } + + public void setZip(String zip) { + this.zip = zip; + } + + public String getCountry() { + return country; + } + + public void setCountry(String country) { + this.country = country; + } + + public String toString() { + return new ToStringCreator(this).append("street", street).append("city", city).append("state", state) + .append("zip", zip).toString(); + } + } + + public static class AddressFormatter implements Formatter
{ + + public String format(Address address, Locale locale) { + return address.getStreet() + ":" + address.getCity() + ":" + address.getState() + ":" + address.getZip(); + } + + public Address parse(String formatted, Locale locale) throws ParseException { + Address address = new Address(); + String[] fields = formatted.split(":"); + address.setStreet(fields[0]); + address.setCity(fields[1]); + address.setState(fields[2]); + address.setZip(fields[3]); + return address; + } + + } + +} \ No newline at end of file