polish
This commit is contained in:
parent
8d7d3cff1b
commit
704cc79cee
|
|
@ -27,7 +27,7 @@ import org.springframework.core.convert.TypeDescriptor;
|
|||
public interface FormatterRegistry {
|
||||
|
||||
/**
|
||||
* Adds a Formatter to this registry indexed by <T>.
|
||||
* Adds a Formatter to this registry indexed by <T>.
|
||||
* Calling getFormatter(<T>.class) returns <code>formatter</code>.
|
||||
* @param formatter the formatter
|
||||
* @param <T> the type of object the formatter formats
|
||||
|
|
@ -35,16 +35,16 @@ public interface FormatterRegistry {
|
|||
<T> void add(Formatter<T> formatter);
|
||||
|
||||
/**
|
||||
* Adds a Formatter to this registry indexed by objectType.
|
||||
* Use this add method when objectType differs from <T>.
|
||||
* Calling getFormatter(objectType) returns a decorator that wraps the targetFormatter.
|
||||
* On format, the decorator first coerses the instance of objectType to <T>, then delegates to <code>targetFormatter</code> to format the value.
|
||||
* Adds a Formatter to this registry indexed by type.
|
||||
* Use this add method when type differs from <T>.
|
||||
* Calling getFormatter(type) returns a decorator that wraps the targetFormatter.
|
||||
* On format, the decorator first coerses the instance of tType to <T>, then delegates to <code>targetFormatter</code> to format the value.
|
||||
* On parse, the decorator first delegates to the formatter to parse a <T>, then coerses the parsed value to objectType.
|
||||
* @param objectType the object type
|
||||
* @param type the object type
|
||||
* @param targetFormatter the target formatter
|
||||
* @param <T> the type of object the target formatter formats
|
||||
*/
|
||||
<T> void add(Class<?> objectType, Formatter<T> targetFormatter);
|
||||
<T> void add(Class<?> type, Formatter<T> targetFormatter);
|
||||
|
||||
/**
|
||||
* Adds a AnnotationFormatterFactory that will format values of properties annotated with a specific annotation.
|
||||
|
|
@ -53,7 +53,7 @@ public interface FormatterRegistry {
|
|||
<A extends Annotation, T> void add(AnnotationFormatterFactory<A, T> factory);
|
||||
|
||||
/**
|
||||
* Get the Formatter for the type.
|
||||
* Get the Formatter for the type descriptor.
|
||||
* @return the Formatter, or <code>null</code> if none is registered
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
|
|
|
|||
|
|
@ -21,8 +21,10 @@ import java.lang.reflect.Type;
|
|||
import java.lang.reflect.TypeVariable;
|
||||
import java.text.ParseException;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.springframework.core.GenericTypeResolver;
|
||||
|
|
@ -33,9 +35,10 @@ 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.
|
||||
* A generic implementation of {@link FormatterRegistry} suitable for use in most environments.
|
||||
* @author Keith Donald
|
||||
* @since 3.0
|
||||
* @see #setConversionService(ConversionService)
|
||||
* @see #add(Formatter)
|
||||
* @see #add(Class, Formatter)
|
||||
* @see #add(AnnotationFormatterFactory)
|
||||
|
|
@ -50,7 +53,8 @@ public class GenericFormatterRegistry implements FormatterRegistry {
|
|||
private ConversionService conversionService = new DefaultConversionService();
|
||||
|
||||
/**
|
||||
* Sets the type conversion service used to coerse objects to the types required for Formatting purposes.
|
||||
* Sets the type conversion service that will be used to coerse objects to the types required for formatting.
|
||||
* Defaults to a {@link DefaultConversionService}.
|
||||
* @param conversionService the conversion service
|
||||
* @see #add(Class, Formatter)
|
||||
*/
|
||||
|
|
@ -58,18 +62,44 @@ public class GenericFormatterRegistry implements FormatterRegistry {
|
|||
this.conversionService = conversionService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the formatters in the map provided by type.
|
||||
* JavaBean-friendly alternative to calling {@link #add(Class, Formatter)}.
|
||||
* @param formatters the formatters map
|
||||
* @see #add(Class, Formatter)
|
||||
*/
|
||||
public void setFormatters(Map<Class<?>, Formatter<?>> formatters) {
|
||||
for (Map.Entry<Class<?>, Formatter<?>> entry : formatters.entrySet()) {
|
||||
add(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the annotation formatter factories in the set provided.
|
||||
* JavaBean-friendly alternative to calling {@link #add(AnnotationFormatterFactory)}.
|
||||
* @see #add(AnnotationFormatterFactory)
|
||||
*/
|
||||
public void setAnnotationFormatterFactories(Set<AnnotationFormatterFactory> factories) {
|
||||
for (AnnotationFormatterFactory factory : factories) {
|
||||
add(factory);
|
||||
}
|
||||
}
|
||||
|
||||
// implementing FormatterRegistry
|
||||
|
||||
public <T> void add(Formatter<T> formatter) {
|
||||
typeFormatters.put(getFormattedObjectType(formatter.getClass()), formatter);
|
||||
}
|
||||
|
||||
public <T> void add(Class<?> objectType, Formatter<T> formatter) {
|
||||
if (objectType.isAnnotation()) {
|
||||
annotationFormatters.put(objectType, new SimpleAnnotationFormatterFactory(formatter));
|
||||
} else {
|
||||
typeFormatters.put(objectType, formatter);
|
||||
public <T> void add(Class<?> type, Formatter<T> formatter) {
|
||||
Class<?> formattedObjectType = getFormattedObjectType(formatter.getClass());
|
||||
if (!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 (!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");
|
||||
}
|
||||
typeFormatters.put(type, formatter);
|
||||
}
|
||||
|
||||
public <A extends Annotation, T> void add(AnnotationFormatterFactory<A, T> factory) {
|
||||
|
|
@ -158,7 +188,7 @@ public class GenericFormatterRegistry implements FormatterRegistry {
|
|||
|
||||
private Formatter<?> getTypeFormatter(Class<?> type) {
|
||||
Assert.notNull(type, "The Class of the object to format is required");
|
||||
Formatter formatter = typeFormatters.get(type);
|
||||
Formatter formatter = findFormatter(type);
|
||||
if (formatter != null) {
|
||||
Class<?> formattedObjectType = getFormattedObjectType(formatter.getClass());
|
||||
if (type.isAssignableFrom(formattedObjectType)) {
|
||||
|
|
@ -171,6 +201,26 @@ public class GenericFormatterRegistry implements FormatterRegistry {
|
|||
}
|
||||
}
|
||||
|
||||
private Formatter<?> findFormatter(Class<?> type) {
|
||||
LinkedList<Class> classQueue = new LinkedList<Class>();
|
||||
classQueue.addFirst(type);
|
||||
while (!classQueue.isEmpty()) {
|
||||
Class currentClass = classQueue.removeLast();
|
||||
Formatter<?> formatter = typeFormatters.get(currentClass);
|
||||
if (formatter != null) {
|
||||
return formatter;
|
||||
}
|
||||
if (currentClass.getSuperclass() != null) {
|
||||
classQueue.addFirst(currentClass.getSuperclass());
|
||||
}
|
||||
Class[] interfaces = currentClass.getInterfaces();
|
||||
for (Class ifc : interfaces) {
|
||||
classQueue.addFirst(ifc);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Formatter<?> getDefaultFormatter(Class<?> type) {
|
||||
Formatted formatted = AnnotationUtils.findAnnotation(type, Formatted.class);
|
||||
if (formatted != null) {
|
||||
|
|
@ -191,20 +241,6 @@ public class GenericFormatterRegistry implements FormatterRegistry {
|
|||
}
|
||||
}
|
||||
|
||||
private static class SimpleAnnotationFormatterFactory implements AnnotationFormatterFactory {
|
||||
|
||||
private Formatter formatter;
|
||||
|
||||
public SimpleAnnotationFormatterFactory(Formatter formatter) {
|
||||
this.formatter = formatter;
|
||||
}
|
||||
|
||||
public Formatter getFormatter(Annotation annotation) {
|
||||
return formatter;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class ConvertingFormatter implements Formatter {
|
||||
|
||||
private Class<?> type;
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ import org.springframework.ui.format.Formatter;
|
|||
* Applies {@link RoundingMode#DOWN} to parsed values.
|
||||
* @author Keith Donald
|
||||
* @since 3.0
|
||||
* @see #setLenient(boolean)
|
||||
*/
|
||||
public final class CurrencyFormatter implements Formatter<BigDecimal> {
|
||||
|
||||
|
|
@ -39,6 +40,16 @@ public final class CurrencyFormatter implements Formatter<BigDecimal> {
|
|||
|
||||
private boolean lenient;
|
||||
|
||||
/**
|
||||
* Specify whether or not parsing is to be lenient.
|
||||
* With lenient parsing, the parser may allow inputs that do not precisely match the format.
|
||||
* With strict parsing, inputs must match the format exactly.
|
||||
* Default is false.
|
||||
*/
|
||||
public void setLenient(boolean lenient) {
|
||||
this.lenient = lenient;
|
||||
}
|
||||
|
||||
public String format(BigDecimal decimal, Locale locale) {
|
||||
if (decimal == null) {
|
||||
return "";
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
package org.springframework.ui.format.number;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.NumberFormat;
|
||||
import java.text.ParseException;
|
||||
import java.text.ParsePosition;
|
||||
|
|
@ -24,14 +25,17 @@ import java.util.Locale;
|
|||
import org.springframework.ui.format.Formatter;
|
||||
|
||||
/**
|
||||
* A BigDecimal formatter for decimal values.
|
||||
* A Number formatter for decimal values.
|
||||
* Delegates to {@link NumberFormat#getInstance(Locale)}.
|
||||
* Configures BigDecimal parsing so there is no loss in precision.
|
||||
* Allows configuration over the decimal number pattern; see {@link #DecimalFormatter(String)}.
|
||||
* Allows configuration over the decimal number pattern.
|
||||
* The {@link #parse(String, Locale)} routine always returns a BigDecimal.
|
||||
* @author Keith Donald
|
||||
* @since 3.0
|
||||
* @see #setPattern(String)
|
||||
* @see #setLenient(boolean)
|
||||
*/
|
||||
public final class DecimalFormatter implements Formatter<BigDecimal> {
|
||||
public final class DecimalFormatter implements Formatter<Number> {
|
||||
|
||||
private DefaultNumberFormatFactory formatFactory = new DefaultNumberFormatFactory();
|
||||
|
||||
|
|
@ -41,12 +45,27 @@ public final class DecimalFormatter implements Formatter<BigDecimal> {
|
|||
initDefaults();
|
||||
}
|
||||
|
||||
public DecimalFormatter(String pattern) {
|
||||
initDefaults();
|
||||
/**
|
||||
* Sets the pattern to use to format number values.
|
||||
* If not specified, the default DecimalFormat pattern is used.
|
||||
* @param pattern the format pattern
|
||||
* @see DecimalFormat#applyPattern(String)
|
||||
*/
|
||||
public void setPattern(String pattern) {
|
||||
formatFactory.setPattern(pattern);
|
||||
}
|
||||
|
||||
public String format(BigDecimal decimal, Locale locale) {
|
||||
/**
|
||||
* Specify whether or not parsing is to be lenient.
|
||||
* With lenient parsing, the parser may allow inputs that do not precisely match the format.
|
||||
* With strict parsing, inputs must match the format exactly.
|
||||
* Default is false.
|
||||
*/
|
||||
public void setLenient(boolean lenient) {
|
||||
this.lenient = lenient;
|
||||
}
|
||||
|
||||
public String format(Number decimal, Locale locale) {
|
||||
if (decimal == null) {
|
||||
return "";
|
||||
}
|
||||
|
|
@ -54,8 +73,7 @@ public final class DecimalFormatter implements Formatter<BigDecimal> {
|
|||
return format.format(decimal);
|
||||
}
|
||||
|
||||
public BigDecimal parse(String formatted, Locale locale)
|
||||
throws ParseException {
|
||||
public Number parse(String formatted, Locale locale) throws ParseException {
|
||||
if (formatted.length() == 0) {
|
||||
return null;
|
||||
}
|
||||
|
|
@ -74,6 +92,8 @@ public final class DecimalFormatter implements Formatter<BigDecimal> {
|
|||
return decimal;
|
||||
}
|
||||
|
||||
// internal helpers
|
||||
|
||||
private void initDefaults() {
|
||||
formatFactory.setParseBigDecimal(true);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,18 +23,30 @@ import java.util.Locale;
|
|||
import org.springframework.ui.format.Formatter;
|
||||
|
||||
/**
|
||||
* A Long formatter for whole integer values.
|
||||
* A Number formatter for whole integer values.
|
||||
* Delegates to {@link NumberFormat#getIntegerInstance(Locale)}.
|
||||
* The {@link #parse(String, Locale)} routine always returns a Long.
|
||||
* @author Keith Donald
|
||||
* @since 3.0
|
||||
* @see #setLenient(boolean)
|
||||
*/
|
||||
public final class IntegerFormatter implements Formatter<Long> {
|
||||
public final class IntegerFormatter implements Formatter<Number> {
|
||||
|
||||
private IntegerNumberFormatFactory formatFactory = new IntegerNumberFormatFactory();
|
||||
|
||||
private boolean lenient;
|
||||
|
||||
public String format(Long integer, Locale locale) {
|
||||
/**
|
||||
* Specify whether or not parsing is to be lenient.
|
||||
* With lenient parsing, the parser may allow inputs that do not precisely match the format.
|
||||
* With strict parsing, inputs must match the format exactly.
|
||||
* Default is false.
|
||||
*/
|
||||
public void setLenient(boolean lenient) {
|
||||
this.lenient = lenient;
|
||||
}
|
||||
|
||||
public String format(Number integer, Locale locale) {
|
||||
if (integer == null) {
|
||||
return "";
|
||||
}
|
||||
|
|
@ -42,7 +54,7 @@ public final class IntegerFormatter implements Formatter<Long> {
|
|||
return format.format(integer);
|
||||
}
|
||||
|
||||
public Long parse(String formatted, Locale locale)
|
||||
public Number parse(String formatted, Locale locale)
|
||||
throws ParseException {
|
||||
if (formatted.length() == 0) {
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -24,27 +24,39 @@ import java.util.Locale;
|
|||
import org.springframework.ui.format.Formatter;
|
||||
|
||||
/**
|
||||
* A BigDecimal formatter for percent values.
|
||||
* A Number formatter for percent values.
|
||||
* Delegates to {@link NumberFormat#getPercentInstance(Locale)}.
|
||||
* Configures BigDecimal parsing so there is no loss in precision.
|
||||
* The {@link #parse(String, Locale)} routine always returns a BigDecimal.
|
||||
* @author Keith Donald
|
||||
* @since 3.0
|
||||
* @see #setLenient(boolean)
|
||||
*/
|
||||
public final class PercentFormatter implements Formatter<BigDecimal> {
|
||||
public final class PercentFormatter implements Formatter<Number> {
|
||||
|
||||
private PercentNumberFormatFactory percentFormatFactory = new PercentNumberFormatFactory();
|
||||
|
||||
private boolean lenient;
|
||||
|
||||
public String format(BigDecimal decimal, Locale locale) {
|
||||
if (decimal == null) {
|
||||
/**
|
||||
* Specify whether or not parsing is to be lenient.
|
||||
* With lenient parsing, the parser may allow inputs that do not precisely match the format.
|
||||
* With strict parsing, inputs must match the format exactly.
|
||||
* Default is false.
|
||||
*/
|
||||
public void setLenient(boolean lenient) {
|
||||
this.lenient = lenient;
|
||||
}
|
||||
|
||||
public String format(Number number, Locale locale) {
|
||||
if (number == null) {
|
||||
return "";
|
||||
}
|
||||
NumberFormat format = percentFormatFactory.getNumberFormat(locale);
|
||||
return format.format(decimal);
|
||||
return format.format(number);
|
||||
}
|
||||
|
||||
public BigDecimal parse(String formatted, Locale locale)
|
||||
public Number parse(String formatted, Locale locale)
|
||||
throws ParseException {
|
||||
if (formatted.length() == 0) {
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import static org.junit.Assert.assertEquals;
|
|||
import static org.junit.Assert.assertNull;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.text.ParseException;
|
||||
import java.util.Locale;
|
||||
|
||||
|
|
@ -25,18 +26,20 @@ public class GenericFormatterRegistryTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testAdd() {
|
||||
public void testAdd() throws ParseException {
|
||||
registry.add(new IntegerFormatter());
|
||||
Formatter formatter = registry.getFormatter(typeDescriptor(Long.class));
|
||||
String formatted = formatter.format(new Long(3), Locale.US);
|
||||
Formatter formatter = registry.getFormatter(typeDescriptor(Integer.class));
|
||||
String formatted = formatter.format(new Integer(3), Locale.US);
|
||||
assertEquals("3", formatted);
|
||||
Integer i = (Integer) formatter.parse("3", Locale.US);
|
||||
assertEquals(new Integer(3), i);
|
||||
}
|
||||
|
||||
@Test
|
||||
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);
|
||||
registry.add(BigInteger.class, new IntegerFormatter());
|
||||
Formatter formatter = registry.getFormatter(typeDescriptor(BigInteger.class));
|
||||
String formatted = formatter.format(new BigInteger("3"), Locale.US);
|
||||
assertEquals("3", formatted);
|
||||
}
|
||||
|
||||
|
|
@ -65,6 +68,11 @@ public class GenericFormatterRegistryTests {
|
|||
assertNull(registry.getFormatter(typeDescriptor(Integer.class)));
|
||||
}
|
||||
|
||||
@Test(expected=IllegalArgumentException.class)
|
||||
public void testGetFormatterCannotConvert() {
|
||||
registry.add(Integer.class, new AddressFormatter());
|
||||
}
|
||||
|
||||
@CurrencyFormat
|
||||
public BigDecimal currencyField;
|
||||
|
||||
|
|
@ -74,8 +82,11 @@ public class GenericFormatterRegistryTests {
|
|||
|
||||
public static class CurrencyAnnotationFormatterFactory implements
|
||||
AnnotationFormatterFactory<CurrencyFormat, BigDecimal> {
|
||||
|
||||
private CurrencyFormatter currencyFormatter = new CurrencyFormatter();
|
||||
|
||||
public Formatter<BigDecimal> getFormatter(CurrencyFormat annotation) {
|
||||
return new CurrencyFormatter();
|
||||
return currencyFormatter;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue