SPR-6013, SPR-6014, SPR-6015 tests
This commit is contained in:
parent
db40e15a3e
commit
20f5f99e9a
|
|
@ -19,19 +19,24 @@ import java.lang.annotation.Annotation;
|
||||||
import java.lang.reflect.ParameterizedType;
|
import java.lang.reflect.ParameterizedType;
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
import java.lang.reflect.TypeVariable;
|
import java.lang.reflect.TypeVariable;
|
||||||
|
import java.text.ParseException;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
import org.springframework.core.GenericTypeResolver;
|
import org.springframework.core.GenericTypeResolver;
|
||||||
import org.springframework.core.annotation.AnnotationUtils;
|
import org.springframework.core.annotation.AnnotationUtils;
|
||||||
|
import org.springframework.core.convert.ConversionService;
|
||||||
import org.springframework.core.convert.TypeDescriptor;
|
import org.springframework.core.convert.TypeDescriptor;
|
||||||
|
import org.springframework.core.convert.support.DefaultConversionService;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A generic implementation of {@link FormatterRegistry} suitable for use in most binding environments.
|
* A generic implementation of {@link FormatterRegistry} suitable for use in most binding environments.
|
||||||
* @author Keith Donald
|
* @author Keith Donald
|
||||||
* @since 3.0
|
* @since 3.0
|
||||||
|
* @see #add(Formatter)
|
||||||
* @see #add(Class, Formatter)
|
* @see #add(Class, Formatter)
|
||||||
* @see #add(AnnotationFormatterFactory)
|
* @see #add(AnnotationFormatterFactory)
|
||||||
*/
|
*/
|
||||||
|
|
@ -42,10 +47,21 @@ public class GenericFormatterRegistry implements FormatterRegistry {
|
||||||
|
|
||||||
private Map<Class, AnnotationFormatterFactory> annotationFormatters = new HashMap<Class, AnnotationFormatterFactory>();
|
private Map<Class, AnnotationFormatterFactory> annotationFormatters = new HashMap<Class, AnnotationFormatterFactory>();
|
||||||
|
|
||||||
|
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
|
// implementing FormatterRegistry
|
||||||
|
|
||||||
public <T> void add(Formatter<T> formatter) {
|
public <T> void add(Formatter<T> formatter) {
|
||||||
// TODO
|
typeFormatters.put(getFormattedObjectType(formatter.getClass()), formatter);
|
||||||
}
|
}
|
||||||
|
|
||||||
public <T> void add(Class<?> objectType, Formatter<T> formatter) {
|
public <T> void add(Class<?> objectType, Formatter<T> formatter) {
|
||||||
|
|
@ -57,11 +73,79 @@ public class GenericFormatterRegistry implements FormatterRegistry {
|
||||||
}
|
}
|
||||||
|
|
||||||
public <A extends Annotation, T> void add(AnnotationFormatterFactory<A, T> factory) {
|
public <A extends Annotation, T> void add(AnnotationFormatterFactory<A, T> factory) {
|
||||||
annotationFormatters.put(getAnnotationType(factory), factory);
|
annotationFormatters.put(getAnnotationType(factory.getClass()), factory);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Formatter<?> getFormatter(TypeDescriptor type) {
|
public Formatter<?> getFormatter(TypeDescriptor type) {
|
||||||
Assert.notNull(type, "The TypeDescriptor is required");
|
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 <A> generic type?");
|
||||||
|
}
|
||||||
|
|
||||||
|
private Formatter<?> getAnnotationFormatter(TypeDescriptor type) {
|
||||||
Annotation[] annotations = type.getAnnotations();
|
Annotation[] annotations = type.getAnnotations();
|
||||||
for (Annotation a : annotations) {
|
for (Annotation a : annotations) {
|
||||||
AnnotationFormatterFactory factory = annotationFormatters.get(a.annotationType());
|
AnnotationFormatterFactory factory = annotationFormatters.get(a.annotationType());
|
||||||
|
|
@ -69,65 +153,42 @@ public class GenericFormatterRegistry implements FormatterRegistry {
|
||||||
return factory.getFormatter(a);
|
return factory.getFormatter(a);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return getFormatter(type.getType());
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// internal helpers
|
private Formatter<?> getTypeFormatter(Class<?> type) {
|
||||||
|
|
||||||
private Formatter<?> getFormatter(Class<?> type) {
|
|
||||||
Assert.notNull(type, "The Class of the object to format is required");
|
Assert.notNull(type, "The Class of the object to format is required");
|
||||||
Formatter formatter = typeFormatters.get(type);
|
Formatter formatter = typeFormatters.get(type);
|
||||||
if (formatter != null) {
|
if (formatter != null) {
|
||||||
return formatter;
|
Class<?> formattedObjectType = getFormattedObjectType(formatter.getClass());
|
||||||
} else {
|
if (type.isAssignableFrom(formattedObjectType)) {
|
||||||
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);
|
|
||||||
return formatter;
|
return formatter;
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return new ConvertingFormatter(type, formattedObjectType, formatter);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
return getDefaultFormatter(type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Class getAnnotationType(AnnotationFormatterFactory factory) {
|
private Formatter<?> getDefaultFormatter(Class<?> type) {
|
||||||
Class classToIntrospect = factory.getClass();
|
Formatted formatted = AnnotationUtils.findAnnotation(type, Formatted.class);
|
||||||
while (classToIntrospect != null) {
|
if (formatted != null) {
|
||||||
Type[] genericInterfaces = classToIntrospect.getGenericInterfaces();
|
Class formatterClass = formatted.value();
|
||||||
for (Type genericInterface : genericInterfaces) {
|
try {
|
||||||
if (genericInterface instanceof ParameterizedType) {
|
Formatter formatter = (Formatter) formatterClass.newInstance();
|
||||||
ParameterizedType pInterface = (ParameterizedType) genericInterface;
|
typeFormatters.put(type, formatter);
|
||||||
if (AnnotationFormatterFactory.class.isAssignableFrom((Class) pInterface.getRawType())) {
|
return formatter;
|
||||||
return getParameterClass(pInterface.getActualTypeArguments()[0], factory.getClass());
|
} 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);
|
||||||
}
|
}
|
||||||
classToIntrospect = classToIntrospect.getSuperclass();
|
} else {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
throw new IllegalArgumentException(
|
|
||||||
"Unable to extract Annotation type A argument from AnnotationFormatterFactory ["
|
|
||||||
+ factory.getClass().getName() + "]; does the factory parameterize the <A> generic type?");
|
|
||||||
}
|
|
||||||
|
|
||||||
private Class getParameterClass(Type parameterType, Class converterClass) {
|
|
||||||
if (parameterType instanceof TypeVariable) {
|
|
||||||
parameterType = GenericTypeResolver.resolveTypeVariable((TypeVariable) parameterType, converterClass);
|
|
||||||
}
|
|
||||||
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 {
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,13 +1,18 @@
|
||||||
package org.springframework.ui.format;
|
package org.springframework.ui.format;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
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 java.util.Locale;
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Ignore;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.springframework.core.convert.TypeDescriptor;
|
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;
|
import org.springframework.ui.format.number.IntegerFormatter;
|
||||||
|
|
||||||
public class GenericFormatterRegistryTests {
|
public class GenericFormatterRegistryTests {
|
||||||
|
|
@ -20,7 +25,6 @@ public class GenericFormatterRegistryTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Ignore
|
|
||||||
public void testAdd() {
|
public void testAdd() {
|
||||||
registry.add(new IntegerFormatter());
|
registry.add(new IntegerFormatter());
|
||||||
Formatter formatter = registry.getFormatter(typeDescriptor(Long.class));
|
Formatter formatter = registry.getFormatter(typeDescriptor(Long.class));
|
||||||
|
|
@ -29,8 +33,7 @@ public class GenericFormatterRegistryTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Ignore
|
public void testAddByObjectType() {
|
||||||
public void testAddByOtherObjectType() {
|
|
||||||
registry.add(Integer.class, new IntegerFormatter());
|
registry.add(Integer.class, new IntegerFormatter());
|
||||||
Formatter formatter = registry.getFormatter(typeDescriptor(Integer.class));
|
Formatter formatter = registry.getFormatter(typeDescriptor(Integer.class));
|
||||||
String formatted = formatter.format(new Integer(3), Locale.US);
|
String formatted = formatter.format(new Integer(3), Locale.US);
|
||||||
|
|
@ -38,11 +41,114 @@ public class GenericFormatterRegistryTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Ignore
|
public void testAddAnnotationFormatterFactory() throws Exception {
|
||||||
public void testAddAnnotationFormatterFactory() {
|
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) {
|
private static TypeDescriptor typeDescriptor(Class<?> clazz) {
|
||||||
return TypeDescriptor.valueOf(clazz);
|
return TypeDescriptor.valueOf(clazz);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class CurrencyAnnotationFormatterFactory implements
|
||||||
|
AnnotationFormatterFactory<CurrencyFormat, BigDecimal> {
|
||||||
|
public Formatter<BigDecimal> 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<Address> {
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue