Browse Source

polish

conversation
Keith Donald 16 years ago
parent
commit
704cc79cee
  1. 16
      org.springframework.context/src/main/java/org/springframework/ui/format/FormatterRegistry.java
  2. 80
      org.springframework.context/src/main/java/org/springframework/ui/format/GenericFormatterRegistry.java
  3. 11
      org.springframework.context/src/main/java/org/springframework/ui/format/number/CurrencyFormatter.java
  4. 42
      org.springframework.context/src/main/java/org/springframework/ui/format/number/DecimalFormatter.java
  5. 20
      org.springframework.context/src/main/java/org/springframework/ui/format/number/IntegerFormatter.java
  6. 24
      org.springframework.context/src/main/java/org/springframework/ui/format/number/PercentFormatter.java
  7. 25
      org.springframework.context/src/test/java/org/springframework/ui/format/GenericFormatterRegistryTests.java

16
org.springframework.context/src/main/java/org/springframework/ui/format/FormatterRegistry.java

@ -27,7 +27,7 @@ import org.springframework.core.convert.TypeDescriptor; @@ -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 &lt;T&gt;.
* Calling getFormatter(&lt;T&gt;.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 { @@ -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 &lt;T&gt;.
* Calling getFormatter(objectType) returns a decorator that wraps the targetFormatter.
* On format, the decorator first coerses the instance of objectType to &lt;T&gt;, 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 &lt;T&gt;.
* Calling getFormatter(type) returns a decorator that wraps the targetFormatter.
* On format, the decorator first coerses the instance of tType to &lt;T&gt;, then delegates to <code>targetFormatter</code> to format the value.
* On parse, the decorator first delegates to the formatter to parse a &lt;T&gt;, 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 { @@ -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")

80
org.springframework.context/src/main/java/org/springframework/ui/format/GenericFormatterRegistry.java

@ -21,8 +21,10 @@ import java.lang.reflect.Type; @@ -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; @@ -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,13 +53,37 @@ public class GenericFormatterRegistry implements FormatterRegistry { @@ -50,13 +53,37 @@ 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)
*/
public void setConversionService(ConversionService conversionService) {
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
@ -64,12 +91,15 @@ public class GenericFormatterRegistry implements FormatterRegistry { @@ -64,12 +91,15 @@ public class GenericFormatterRegistry implements FormatterRegistry {
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 { @@ -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)) {
@ -170,6 +200,26 @@ public class GenericFormatterRegistry implements FormatterRegistry { @@ -170,6 +200,26 @@ public class GenericFormatterRegistry implements FormatterRegistry {
return getDefaultFormatter(type);
}
}
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);
@ -191,20 +241,6 @@ public class GenericFormatterRegistry implements FormatterRegistry { @@ -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;

11
org.springframework.context/src/main/java/org/springframework/ui/format/number/CurrencyFormatter.java

@ -32,6 +32,7 @@ import org.springframework.ui.format.Formatter; @@ -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> { @@ -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 "";

42
org.springframework.context/src/main/java/org/springframework/ui/format/number/DecimalFormatter.java

@ -16,6 +16,7 @@ @@ -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; @@ -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();
@ -40,13 +44,28 @@ public final class DecimalFormatter implements Formatter<BigDecimal> { @@ -40,13 +44,28 @@ public final class DecimalFormatter implements Formatter<BigDecimal> {
public DecimalFormatter() {
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> { @@ -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;
}
@ -73,7 +91,9 @@ public final class DecimalFormatter implements Formatter<BigDecimal> { @@ -73,7 +91,9 @@ public final class DecimalFormatter implements Formatter<BigDecimal> {
}
return decimal;
}
// internal helpers
private void initDefaults() {
formatFactory.setParseBigDecimal(true);
}

20
org.springframework.context/src/main/java/org/springframework/ui/format/number/IntegerFormatter.java

@ -23,18 +23,30 @@ import java.util.Locale; @@ -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> { @@ -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
org.springframework.context/src/main/java/org/springframework/ui/format/number/PercentFormatter.java

@ -24,27 +24,39 @@ import java.util.Locale; @@ -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;

25
org.springframework.context/src/test/java/org/springframework/ui/format/GenericFormatterRegistryTests.java

@ -4,6 +4,7 @@ import static org.junit.Assert.assertEquals; @@ -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 { @@ -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);
}
@ -64,6 +67,11 @@ public class GenericFormatterRegistryTests { @@ -64,6 +67,11 @@ public class GenericFormatterRegistryTests {
public void testGetNoFormatterForType() {
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 { @@ -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…
Cancel
Save