diff --git a/org.springframework.context/src/main/java/org/springframework/ui/format/FormatterRegistry.java b/org.springframework.context/src/main/java/org/springframework/ui/format/FormatterRegistry.java index a5ec12b974a..ed59fac5276 100644 --- a/org.springframework.context/src/main/java/org/springframework/ui/format/FormatterRegistry.java +++ b/org.springframework.context/src/main/java/org/springframework/ui/format/FormatterRegistry.java @@ -46,16 +46,14 @@ public interface FormatterRegistry { * Adds a Formatter to this registry indexed by <T>. * Calling getFormatter(<T>.class) returns formatter. * @param formatter the formatter - * @param the type of object the formatter formats */ - void addFormatterByType(Formatter formatter); + void addFormatterByType(Formatter formatter); /** * Adds a Formatter to this registry indexed by the given annotation type. * Calling getFormatter(...) on a field or accessor method * with the given annotation returns formatter. * @param formatter the formatter - * @param the type of object the formatter formats */ void addFormatterByAnnotation(Class annotationType, Formatter formatter); @@ -64,16 +62,8 @@ public interface FormatterRegistry { * Calling getFormatter(...) on a field or accessor method * with the given annotation returns formatter. * @param factory the annotation formatter factory - * @param the type of Annotation this factory uses to create Formatter instances - * @param the type of object that the factory's Formatters are dealing with */ - void addFormatterByAnnotation(AnnotationFormatterFactory factory); - - /** - * Get the Formatter for the specified type. - * @return the Formatter, or null if no suitable one is registered - */ - Formatter getFormatter(Class targetType); + void addFormatterByAnnotation(AnnotationFormatterFactory factory); /** * Get the Formatter for the type descriptor. diff --git a/org.springframework.context/src/main/java/org/springframework/ui/format/support/GenericFormatterRegistry.java b/org.springframework.context/src/main/java/org/springframework/ui/format/support/GenericFormatterRegistry.java index 4beb2e3668a..2f48584facf 100644 --- a/org.springframework.context/src/main/java/org/springframework/ui/format/support/GenericFormatterRegistry.java +++ b/org.springframework.context/src/main/java/org/springframework/ui/format/support/GenericFormatterRegistry.java @@ -17,6 +17,8 @@ package org.springframework.ui.format.support; import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; import java.text.ParseException; import java.util.LinkedList; import java.util.Locale; @@ -38,6 +40,7 @@ import org.springframework.ui.format.Formatted; import org.springframework.ui.format.Formatter; import org.springframework.ui.format.FormatterRegistry; import org.springframework.util.Assert; +import org.springframework.util.ReflectionUtils; /** * A generic implementation of {@link org.springframework.ui.format.FormatterRegistry} @@ -46,25 +49,28 @@ import org.springframework.util.Assert; * @author Keith Donald * @author Juergen Hoeller * @since 3.0 + * @see #setFormatters(Set) + * @see #setFormatterMap(Map) + * @see #setAnnotationFormatterMap(Map) + * @see #setAnnotationFormatterFactories(Set) * @see #setConversionService(ConversionService) - * @see #add(org.springframework.ui.format.Formatter) - * @see #add(Class, org.springframework.ui.format.Formatter) - * @see #add(org.springframework.ui.format.AnnotationFormatterFactory) + * @see #addFormatterByType(Formatter) + * @see #addFormatterByType(Class, Formatter) + * @see #addFormatterByAnnotation(Class, Formatter) + * @see #addFormatterByAnnotation(AnnotationFormatterFactory) */ public class GenericFormatterRegistry implements FormatterRegistry, ApplicationContextAware, Cloneable { - private final Map typeFormatters = new ConcurrentHashMap(); + private final Map typeFormatters = new ConcurrentHashMap(); - private final Map annotationFormatters = - new ConcurrentHashMap(); + private final Map annotationFormatters = new ConcurrentHashMap(); - private ConversionService conversionService = new DefaultConversionService(); + private ConversionService conversionService; private ApplicationContext applicationContext; private boolean shared = true; - /** * Registers the formatters in the set provided. * JavaBean-friendly alternative to calling {@link #addFormatterByType(Formatter)}. @@ -130,15 +136,14 @@ public class GenericFormatterRegistry implements FormatterRegistry, ApplicationC * Take the context's default ConversionService if none specified locally. */ public void setApplicationContext(ApplicationContext context) { - if (this.conversionService == null && - context.containsBean(ConfigurableApplicationContext.CONVERSION_SERVICE_BEAN_NAME)) { - this.conversionService = context.getBean( - ConfigurableApplicationContext.CONVERSION_SERVICE_BEAN_NAME, ConversionService.class); + if (this.conversionService == null + && context.containsBean(ConfigurableApplicationContext.CONVERSION_SERVICE_BEAN_NAME)) { + this.conversionService = context.getBean(ConfigurableApplicationContext.CONVERSION_SERVICE_BEAN_NAME, + ConversionService.class); } this.applicationContext = context; } - // cloning support /** @@ -175,98 +180,120 @@ public class GenericFormatterRegistry implements FormatterRegistry, ApplicationC return clone; } - // implementing FormatterRegistry public void addFormatterByType(Class type, Formatter formatter) { Class formattedObjectType = GenericTypeResolver.resolveTypeArgument(formatter.getClass(), Formatter.class); - if (!this.conversionService.canConvert(formattedObjectType, type)) { - throw new IllegalArgumentException("Unable to register Formatter " + formatter + " for type [" + - type.getName() + "]; not able to convert from [" + formattedObjectType.getName() + "] to parse"); + if (formattedObjectType != null && !type.isAssignableFrom(formattedObjectType)) { + if (this.conversionService == null) { + throw new IllegalStateException("Unable to index Formatter " + formatter + " under type [" + + type.getName() + "]; unable to convert from [" + formattedObjectType.getName() + + "] parsed by Formatter because this.conversionService is null"); + } + if (!this.conversionService.canConvert(formattedObjectType, type)) { + throw new IllegalArgumentException("Unable to index Formatter " + formatter + " under type [" + + type.getName() + "]; not able to convert from [" + formattedObjectType.getName() + + "] to parse"); + } + if (!this.conversionService.canConvert(type, formattedObjectType)) { + throw new IllegalArgumentException("Unable to index Formatter " + formatter + " under type [" + + type.getName() + "]; not able to convert to [" + formattedObjectType.getName() + + "] to format"); + } } - if (!this.conversionService.canConvert(type, formattedObjectType)) { - throw new IllegalArgumentException("Unable to register Formatter " + formatter + " for type [" + - type.getName() + "]; not able to convert to [" + formattedObjectType.getName() + "] to format"); - } - this.typeFormatters.put(type, formatter); + this.typeFormatters.put(type, new FormatterHolder(formattedObjectType, formatter)); } - public void addFormatterByType(Formatter formatter) { + public void addFormatterByType(Formatter formatter) { Class formattedObjectType = GenericTypeResolver.resolveTypeArgument(formatter.getClass(), Formatter.class); - this.typeFormatters.put(formattedObjectType, formatter); + if (formattedObjectType == null) { + throw new IllegalArgumentException("Unable to register Formatter " + formatter + + "; cannot determine parameterized object type "); + } + this.typeFormatters.put(formattedObjectType, new FormatterHolder(formattedObjectType, formatter)); } public void addFormatterByAnnotation(Class annotationType, Formatter formatter) { - this.annotationFormatters.put(annotationType, new SimpleAnnotationFormatterFactory(formatter)); + Class formattedObjectType = GenericTypeResolver.resolveTypeArgument(formatter.getClass(), Formatter.class); + SimpleAnnotationFormatterFactory factory = new SimpleAnnotationFormatterFactory(formatter); + this.annotationFormatters.put(annotationType, + new AnnotationFormatterFactoryHolder(formattedObjectType, factory)); } - public void addFormatterByAnnotation(AnnotationFormatterFactory factory) { - Class[] typeArgs = GenericTypeResolver.resolveTypeArguments(factory.getClass(), AnnotationFormatterFactory.class); - if (typeArgs == null) { + public void addFormatterByAnnotation(AnnotationFormatterFactory factory) { + Class[] typeArgs = GenericTypeResolver.resolveTypeArguments(factory.getClass(), + AnnotationFormatterFactory.class); + if (typeArgs == null || typeArgs.length != 2) { throw new IllegalArgumentException( - "Unable to extract Annotation type A argument from AnnotationFormatterFactory [" + - factory.getClass().getName() + "]; does the factory parameterize the generic type?"); + "Unable to extract parameterized type arguments from AnnotationFormatterFactory [" + + factory.getClass().getName() + + "]; does the factory parameterize the and generic types?"); } - this.annotationFormatters.put(typeArgs[0], factory); + this.annotationFormatters.put(typeArgs[0], new AnnotationFormatterFactoryHolder(typeArgs[1], factory)); } - @SuppressWarnings("unchecked") - public Formatter getFormatter(Class targetType) { - return (Formatter) getFormatter(TypeDescriptor.valueOf(targetType)); - } - - @SuppressWarnings("unchecked") public Formatter getFormatter(TypeDescriptor type) { Assert.notNull(type, "TypeDescriptor is required"); - Formatter formatter = getAnnotationFormatter(type); - if (formatter == null) { - formatter = getTypeFormatter(type.getType()); + FormatterHolder holder = findFormatterHolderForAnnotatedProperty(type.getAnnotations()); + if (holder == null) { + holder = findFormatterHolderForType(type.getType()); } - if (formatter != null) { - Class formattedObjectType = GenericTypeResolver.resolveTypeArgument(formatter.getClass(), Formatter.class); - if (!type.getType().isAssignableFrom(formattedObjectType)) { - return new ConvertingFormatter(type.getType(), formattedObjectType, formatter); + if (holder == null) { + holder = getDefaultFormatterHolder(type); + } + if (holder == null) { + return null; + } + Class formattedObjectType = holder.getFormattedObjectType(); + if (formattedObjectType != null && !type.getType().isAssignableFrom(formattedObjectType)) { + if (this.conversionService != null) { + return new ConvertingFormatter(type, holder); + } else { + return null; } + } else { + return holder.getFormatter(); } - return formatter; } - // internal helpers - @SuppressWarnings("unchecked") - private Formatter getAnnotationFormatter(TypeDescriptor type) { - Annotation[] annotations = type.getAnnotations(); - for (Annotation ann : annotations) { - AnnotationFormatterFactory factory = this.annotationFormatters.get(ann.annotationType()); - if (factory != null) { - return factory.getFormatter(ann); - } - else { - Formatted formattedAnnotation = ann.annotationType().getAnnotation(Formatted.class); - if (formattedAnnotation != null) { - Formatter formatter = createFormatter(formattedAnnotation.value()); - this.annotationFormatters.put(ann.annotationType(), new SimpleAnnotationFormatterFactory(formatter)); - return formatter; - } + private FormatterHolder findFormatterHolderForAnnotatedProperty(Annotation[] annotations) { + for (Annotation annotation : annotations) { + FormatterHolder holder = findFormatterHolderForAnnotation(annotation); + if (holder != null) { + return holder; } } return null; } - private Formatter getTypeFormatter(Class type) { - Formatter formatter = findFormatter(type); - return (formatter != null ? formatter : getDefaultFormatter(type)); + private FormatterHolder findFormatterHolderForAnnotation(Annotation annotation) { + Class annotationType = annotation.annotationType(); + AnnotationFormatterFactoryHolder factory = this.annotationFormatters.get(annotationType); + if (factory != null) { + return factory.getFormatterHolder(annotation); + } else { + Formatted formattedAnnotation = annotationType.getAnnotation(Formatted.class); + if (formattedAnnotation != null) { + // annotation has @Formatted meta-annotation + Formatter formatter = createFormatter(formattedAnnotation.value()); + addFormatterByAnnotation(annotationType, formatter); + return findFormatterHolderForAnnotation(annotation); + } else { + return null; + } + } } - - private Formatter findFormatter(Class type) { + + private FormatterHolder findFormatterHolderForType(Class type) { LinkedList classQueue = new LinkedList(); classQueue.addFirst(type); while (!classQueue.isEmpty()) { Class currentClass = classQueue.removeLast(); - Formatter formatter = this.typeFormatters.get(currentClass); - if (formatter != null) { - return formatter; + FormatterHolder holder = this.typeFormatters.get(currentClass); + if (holder != null) { + return holder; } if (currentClass.getSuperclass() != null) { classQueue.addFirst(currentClass.getSuperclass()); @@ -279,52 +306,98 @@ public class GenericFormatterRegistry implements FormatterRegistry, ApplicationC return null; } - private Formatter getDefaultFormatter(Class type) { + private FormatterHolder getDefaultFormatterHolder(TypeDescriptor typeDescriptor) { + Class type = typeDescriptor.getType(); Formatted formatted = AnnotationUtils.findAnnotation(type, Formatted.class); if (formatted != null) { Formatter formatter = createFormatter(formatted.value()); - this.typeFormatters.put(type, formatter); - return formatter; - } - else { - return null; + addFormatterByType(type, formatter); + return findFormatterHolderForType(type); + } else { + Method valueOfMethod = getValueOfMethod(type); + if (valueOfMethod != null) { + Formatter formatter = createFormatter(valueOfMethod); + addFormatterByType(type, formatter); + return findFormatterHolderForType(type); + } else { + return null; + } } } - private Formatter createFormatter(Class formatterClass) { - return (this.applicationContext != null ? - this.applicationContext.getAutowireCapableBeanFactory().createBean(formatterClass) : - BeanUtils.instantiate(formatterClass)); + private Formatter createFormatter(Class formatterClass) { + return (this.applicationContext != null ? this.applicationContext.getAutowireCapableBeanFactory().createBean( + formatterClass) : BeanUtils.instantiate(formatterClass)); } + private Method getValueOfMethod(Class type) { + Method[] methods = type.getDeclaredMethods(); + for (int i = 0; i < methods.length; i++) { + Method method = methods[i]; + if ("valueOf".equals(method.getName()) && acceptsSingleStringParameterType(method) + && Modifier.isPublic(method.getModifiers()) && Modifier.isStatic(method.getModifiers())) { + return method; + } + } + return null; + } - private class ConvertingFormatter implements Formatter { + private boolean acceptsSingleStringParameterType(Method method) { + Class[] paramTypes = method.getParameterTypes(); + if (paramTypes == null) { + return false; + } else { + return paramTypes.length == 1 && paramTypes[0] == String.class; + } + } - private final Class type; + private Formatter createFormatter(Method valueOfMethod) { + return new ValueOfMethodFormatter(valueOfMethod); + } - private final Class formattedObjectType; + private abstract static class AbstractFormatterHolder { - private final Formatter targetFormatter; + private Class formattedObjectType; - public ConvertingFormatter(Class type, Class formattedObjectType, Formatter targetFormatter) { - this.type = type; + public AbstractFormatterHolder(Class formattedObjectType) { this.formattedObjectType = formattedObjectType; - this.targetFormatter = targetFormatter; } - @SuppressWarnings("unchecked") - public String format(Object object, Locale locale) { - object = conversionService.convert(object, this.formattedObjectType); - return this.targetFormatter.format(object, locale); + public Class getFormattedObjectType() { + return formattedObjectType; } - public Object parse(String formatted, Locale locale) throws ParseException { - Object parsed = this.targetFormatter.parse(formatted, locale); - parsed = conversionService.convert(parsed, this.type); - return parsed; - } } + private static class FormatterHolder extends AbstractFormatterHolder { + + private Formatter formatter; + + public FormatterHolder(Class formattedObjectType, Formatter formatter) { + super(formattedObjectType); + this.formatter = formatter; + } + + public Formatter getFormatter() { + return this.formatter; + } + + } + + private static class AnnotationFormatterFactoryHolder extends AbstractFormatterHolder { + + private AnnotationFormatterFactory factory; + + public AnnotationFormatterFactoryHolder(Class formattedObjectType, AnnotationFormatterFactory factory) { + super(formattedObjectType); + this.factory = factory; + } + + public FormatterHolder getFormatterHolder(Annotation annotation) { + return new FormatterHolder(getFormattedObjectType(), this.factory.getFormatter(annotation)); + } + + } private static class SimpleAnnotationFormatterFactory implements AnnotationFormatterFactory { @@ -339,4 +412,51 @@ public class GenericFormatterRegistry implements FormatterRegistry, ApplicationC } } + private static class ValueOfMethodFormatter implements Formatter { + + private Method valueOfMethod; + + public ValueOfMethodFormatter(Method valueOfMethod) { + this.valueOfMethod = valueOfMethod; + } + + public String format(Object object, Locale locale) { + if (object == null) { + return ""; + } else { + return object.toString(); + } + } + + public Object parse(String formatted, Locale locale) throws ParseException { + return ReflectionUtils.invokeMethod(valueOfMethod, null, formatted); + } + + } + + private class ConvertingFormatter implements Formatter { + + private final TypeDescriptor type; + + private final FormatterHolder formatterHolder; + + public ConvertingFormatter(TypeDescriptor type, FormatterHolder formatterHolder) { + this.type = type; + this.formatterHolder = formatterHolder; + } + + public String format(Object object, Locale locale) { + object = GenericFormatterRegistry.this.conversionService.convert(object, this.formatterHolder + .getFormattedObjectType()); + return this.formatterHolder.getFormatter().format(object, locale); + } + + public Object parse(String formatted, Locale locale) throws ParseException { + Object parsed = this.formatterHolder.getFormatter().parse(formatted, locale); + parsed = GenericFormatterRegistry.this.conversionService.convert(parsed, TypeDescriptor + .valueOf(this.formatterHolder.getFormattedObjectType()), this.type); + return parsed; + } + } + } 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 30e8ea1c6c0..9bd4141e549 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 @@ -16,6 +16,8 @@ package org.springframework.ui.format; +import static org.junit.Assert.assertEquals; + import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -25,11 +27,10 @@ import java.math.BigInteger; import java.text.ParseException; import java.util.Locale; -import static org.junit.Assert.*; import org.junit.Before; import org.junit.Test; - import org.springframework.core.convert.TypeDescriptor; +import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.core.style.ToStringCreator; import org.springframework.ui.format.number.CurrencyFormatter; import org.springframework.ui.format.number.IntegerFormatter; @@ -46,12 +47,13 @@ public class GenericFormatterRegistryTests { @Before public void setUp() { registry = new GenericFormatterRegistry(); + registry.setConversionService(new DefaultConversionService()); } @Test public void testAdd() throws ParseException { registry.addFormatterByType(new IntegerFormatter()); - Formatter formatter = registry.getFormatter(Integer.class); + Formatter formatter = registry.getFormatter(TypeDescriptor.valueOf(Integer.class)); String formatted = formatter.format(new Integer(3), Locale.US); assertEquals("3", formatted); Integer i = (Integer) formatter.parse("3", Locale.US); @@ -61,7 +63,7 @@ public class GenericFormatterRegistryTests { @Test public void testAddByObjectType() { registry.addFormatterByType(BigInteger.class, new IntegerFormatter()); - Formatter formatter = registry.getFormatter(BigInteger.class); + Formatter formatter = registry.getFormatter(TypeDescriptor.valueOf(BigInteger.class)); String formatted = formatter.format(new BigInteger("3"), Locale.US); assertEquals("3", formatted); } @@ -91,7 +93,7 @@ public class GenericFormatterRegistryTests { @Test public void testGetDefaultFormatterForType() { - Formatter formatter = registry.getFormatter(Address.class); + Formatter formatter = registry.getFormatter(TypeDescriptor.valueOf(Address.class)); Address address = new Address(); address.street = "12345 Bel Aire Estates"; address.city = "Palm Bay"; @@ -100,17 +102,18 @@ public class GenericFormatterRegistryTests { String formatted = formatter.format(address, Locale.US); assertEquals("12345 Bel Aire Estates:Palm Bay:FL:12345", formatted); } - + @Test - public void testGetNoFormatterForType() { - assertNull(registry.getFormatter(Integer.class)); + public void testGetDefaultFormatterForTypeValueOfMethod() throws ParseException { + Formatter formatter = registry.getFormatter(TypeDescriptor.valueOf(Integer.class)); + assertEquals("3", formatter.format(new Integer(3), null)); + assertEquals(new Integer(3), formatter.parse("3", null)); } - @Test(expected=IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) public void testGetFormatterCannotConvert() { registry.addFormatterByType(Integer.class, new AddressFormatter()); } - @Currency public BigDecimal currencyField; @@ -118,26 +121,26 @@ public class GenericFormatterRegistryTests { @SmartCurrency public BigDecimal smartCurrencyField; - @Target({ElementType.METHOD, ElementType.FIELD}) + @Target( { ElementType.METHOD, ElementType.FIELD }) @Retention(RetentionPolicy.RUNTIME) public @interface Currency { } - @Target({ElementType.METHOD, ElementType.FIELD}) + @Target( { ElementType.METHOD, ElementType.FIELD }) @Retention(RetentionPolicy.RUNTIME) @Formatted(CurrencyFormatter.class) public @interface SmartCurrency { } public static class CurrencyAnnotationFormatterFactory implements AnnotationFormatterFactory { - + private final CurrencyFormatter currencyFormatter = new CurrencyFormatter(); - + public Formatter getFormatter(Currency annotation) { return this.currencyFormatter; } } - + @Formatted(AddressFormatter.class) public static class Address {