formatter polishing
This commit is contained in:
@@ -75,7 +75,9 @@ public class GenericConversionService implements ConversionService {
|
||||
}
|
||||
for (int j = 0; j < targetClasses.length; j++) {
|
||||
Class targetClass = targetClasses[j];
|
||||
sourceMap.put(targetClass, converter);
|
||||
if (!targetClass.equals(sourceClass)) {
|
||||
sourceMap.put(targetClass, converter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,17 +16,24 @@
|
||||
package org.springframework.binding.format;
|
||||
|
||||
/**
|
||||
* Formats objects for display.
|
||||
* Formats objects of a certain class for display.
|
||||
*
|
||||
* @author Keith Donald
|
||||
*/
|
||||
public interface Formatter {
|
||||
|
||||
/**
|
||||
* Returns the type of object this Formatter formats. Successful {@link #parse(String) parse calls} will return
|
||||
* instances of this type. {@link #format(Object)} callers must provide an instance of this type.
|
||||
* @return the type of object being formatted
|
||||
*/
|
||||
public Class getObjectType();
|
||||
|
||||
/**
|
||||
* Format the object for display.
|
||||
* @param object the object to format
|
||||
* @return the formatted string, fit for display in a UI
|
||||
* @throws IllegalArgumentException if the object could not be formatted
|
||||
* @throws IllegalArgumentException if the object is of the wrong type
|
||||
*/
|
||||
public String format(Object object) throws IllegalArgumentException;
|
||||
|
||||
@@ -34,7 +41,7 @@ public interface Formatter {
|
||||
* Parse the formatted string representation of an object and return the object.
|
||||
* @param formattedString the formatted string representation
|
||||
* @return the parsed object
|
||||
* @throws InvalidFormatException the formatted string was in an invalid form
|
||||
* @throws InvalidFormatException the formatted string was in an invalid form, often due to user input error
|
||||
*/
|
||||
public Object parse(String formattedString) throws InvalidFormatException;
|
||||
|
||||
|
||||
@@ -16,12 +16,7 @@
|
||||
package org.springframework.binding.format;
|
||||
|
||||
/**
|
||||
* Source for shared and commonly used <code>Formatters</code>.
|
||||
* <p>
|
||||
* Note: formatters are typically not thread safe as <code>Format</code> objects aren't thread safe. In general, you
|
||||
* should not attempt to share formatters between threads.
|
||||
* </p>
|
||||
* @see java.text.Format
|
||||
* A Source for shared and commonly used <code>Formatters</code>.
|
||||
*
|
||||
* @author Keith Donald
|
||||
*/
|
||||
|
||||
@@ -17,6 +17,7 @@ package org.springframework.binding.format.formatters;
|
||||
|
||||
import org.springframework.binding.format.Formatter;
|
||||
import org.springframework.binding.format.InvalidFormatException;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
@@ -26,16 +27,19 @@ import org.springframework.util.StringUtils;
|
||||
*/
|
||||
public class BooleanFormatter implements Formatter {
|
||||
|
||||
public Class getObjectType() {
|
||||
return Boolean.class;
|
||||
}
|
||||
|
||||
public String format(Object value) throws IllegalArgumentException {
|
||||
if (value == null) {
|
||||
return "";
|
||||
}
|
||||
Assert.isInstanceOf(Boolean.class, value, "Object is not a [java.lang.Boolean]");
|
||||
if (Boolean.TRUE.equals(value)) {
|
||||
return "true";
|
||||
} else if (Boolean.FALSE.equals(value)) {
|
||||
return "false";
|
||||
} else {
|
||||
throw new IllegalArgumentException("Not a Boolean: " + value);
|
||||
return "false";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.binding.format.Formatter;
|
||||
import org.springframework.binding.format.InvalidFormatException;
|
||||
import org.springframework.context.i18n.LocaleContextHolder;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
@@ -79,10 +80,17 @@ public class DateFormatter implements Formatter {
|
||||
this.locale = locale;
|
||||
}
|
||||
|
||||
// implementing Formatter
|
||||
|
||||
public Class getObjectType() {
|
||||
return Date.class;
|
||||
}
|
||||
|
||||
public String format(Object date) {
|
||||
if (date == null) {
|
||||
return "";
|
||||
}
|
||||
Assert.isInstanceOf(Date.class, date, "Object is not a [java.util.Date]");
|
||||
return getDateFormat().format((Date) date);
|
||||
}
|
||||
|
||||
|
||||
@@ -30,9 +30,19 @@ import org.springframework.util.NumberUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* A formatter for common number types such as integers and big decimals. Allows the configuration of an explicit number
|
||||
* pattern and locale.
|
||||
* A general formatter for common number types such as integers and big decimals. Allows the configuration of an
|
||||
* explicit number pattern and locale.
|
||||
*
|
||||
* Works with a general purpose {@link DecimalFormat} instance returned by calling
|
||||
* {@link NumberFormat#getInstance(Locale)} by default. This instance supports parsing any number type generally and
|
||||
* will not perform special type-specific logic such as rounding or truncation. Subclasses may override.
|
||||
*
|
||||
* Will coerse parsed Numbers to the desired numberClass as necessary. If type-coersion results in an overflow
|
||||
* condition; for example, what can occur with a Long being coersed to a Short, an exception will be thrown.
|
||||
*
|
||||
* @see NumberFormat
|
||||
* @see DecimalFormat
|
||||
*
|
||||
* @author Keith Donald
|
||||
*/
|
||||
public class NumberFormatter implements Formatter {
|
||||
@@ -45,12 +55,15 @@ public class NumberFormatter implements Formatter {
|
||||
|
||||
private Locale locale;
|
||||
|
||||
private boolean lenient;
|
||||
|
||||
/**
|
||||
* Creates a number formatter for the specified number type.
|
||||
* @param numberClass the number type, a class extending from {@link Number}.
|
||||
*/
|
||||
public NumberFormatter(Class numberClass) {
|
||||
Assert.notNull(numberClass, "The number class is required");
|
||||
Assert.isTrue(Number.class.isAssignableFrom(numberClass), "The class must extend from Number");
|
||||
this.numberClass = numberClass;
|
||||
}
|
||||
|
||||
@@ -88,10 +101,37 @@ public class NumberFormatter implements Formatter {
|
||||
this.locale = locale;
|
||||
}
|
||||
|
||||
/**
|
||||
* If this Formatter is "lenient" in parsing number strings. A lenient formatter does not require that all
|
||||
* characters in the String be parsed successfully. Default is false.
|
||||
* @return the lenient flag
|
||||
*/
|
||||
public boolean getLenient() {
|
||||
return lenient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets if this Formatter should parse leniently.
|
||||
* @param lenient the lenient flag
|
||||
*/
|
||||
public void setLenient(boolean lenient) {
|
||||
this.lenient = lenient;
|
||||
}
|
||||
|
||||
// implementing Formatter
|
||||
|
||||
public Class getObjectType() {
|
||||
return numberClass;
|
||||
}
|
||||
|
||||
public String format(Object number) {
|
||||
if (number == null) {
|
||||
return "";
|
||||
}
|
||||
if (!numberClass.isInstance(number)) {
|
||||
throw new IllegalArgumentException("Object is not a [" + numberClass.getName() + "]; it is a ["
|
||||
+ number.getClass().getName() + "]");
|
||||
}
|
||||
return getNumberFormat().format(number);
|
||||
}
|
||||
|
||||
@@ -102,18 +142,28 @@ public class NumberFormatter implements Formatter {
|
||||
ParsePosition parsePosition = new ParsePosition(0);
|
||||
NumberFormat format = getNumberFormat();
|
||||
Number number = format.parse(formattedString, parsePosition);
|
||||
if (number == null || formattedString.length() != parsePosition.getIndex()) {
|
||||
if (number == null) {
|
||||
// no object could be parsed
|
||||
throw new InvalidFormatException(formattedString, getPattern(format));
|
||||
} else {
|
||||
return NumberUtils.convertNumberToTargetClass(number, numberClass);
|
||||
}
|
||||
if (!lenient) {
|
||||
if (formattedString.length() != parsePosition.getIndex()) {
|
||||
// indicates a part of the string that was not parsed; e.g. ".5" in 1234.5 when parsing an Integer
|
||||
throw new InvalidFormatException(formattedString, getPattern(format));
|
||||
}
|
||||
}
|
||||
return convertToNumberClass(number);
|
||||
}
|
||||
|
||||
// subclassing hookings
|
||||
|
||||
/**
|
||||
* Factory method that returns a fully-configured {@link NumberFormat} instance to use to format an object for
|
||||
* display. Applies the locale and pattern properties if configured. Subclasses may override.
|
||||
*/
|
||||
protected NumberFormat getNumberFormat() {
|
||||
Locale locale = determineLocale(this.locale);
|
||||
NumberFormat format = NumberFormat.getInstance(locale);
|
||||
NumberFormat format = createNumberFormat(locale);
|
||||
if (pattern != null) {
|
||||
if (format instanceof DecimalFormat) {
|
||||
((DecimalFormat) format).applyPattern(pattern);
|
||||
@@ -125,6 +175,27 @@ public class NumberFormatter implements Formatter {
|
||||
return format;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegates to the {@link NumberFormat java.text.NumberFormat API} to construct the new NumberFormat instance.
|
||||
* Called by {@link #getNumberFormat()} after calculating the Locale. Subclasses may override to control how the
|
||||
* Format instance is constructed.
|
||||
* @param locale the calculated Locale
|
||||
* @return the new NumberFormat instance
|
||||
*/
|
||||
protected NumberFormat createNumberFormat(Locale locale) {
|
||||
return NumberFormat.getInstance(locale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Coerces the Number object returned by NumberFormat to the desired numberClass. Subclasses may override.
|
||||
* @param number the parsed number
|
||||
* @return the coersed number
|
||||
* @throws IllegalArgumentException when an overflow condition occurs during coersion
|
||||
*/
|
||||
protected Number convertToNumberClass(Number number) throws IllegalArgumentException {
|
||||
return NumberUtils.convertNumberToTargetClass(number, numberClass);
|
||||
}
|
||||
|
||||
// internal helpers
|
||||
|
||||
private Locale determineLocale(Locale locale) {
|
||||
|
||||
@@ -21,20 +21,27 @@ import org.springframework.binding.format.Formatter;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Adapts a {@link PropertyEditor} to the formatter interface.
|
||||
* Adapts a {@link PropertyEditor} to the formatter interface. Useful for re-using pre-existing PropertyEditors as
|
||||
* Formatters.
|
||||
*
|
||||
* Note: this class synchronizes calls into the configured PropertyEditor instance to ensure thread safety.
|
||||
*
|
||||
* @author Keith Donald
|
||||
*/
|
||||
public class PropertyEditorFormatter implements Formatter {
|
||||
|
||||
private Class objectType;
|
||||
|
||||
private PropertyEditor propertyEditor;
|
||||
|
||||
/**
|
||||
* Wrap the given property editor in a formatter.
|
||||
*/
|
||||
public PropertyEditorFormatter(PropertyEditor propertyEditor) {
|
||||
public PropertyEditorFormatter(PropertyEditor propertyEditor, Class objectType) {
|
||||
Assert.notNull(propertyEditor, "The PropertyEditor is required");
|
||||
Assert.notNull(objectType, "The object type is required");
|
||||
this.propertyEditor = propertyEditor;
|
||||
this.objectType = objectType;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -44,13 +51,27 @@ public class PropertyEditorFormatter implements Formatter {
|
||||
return propertyEditor;
|
||||
}
|
||||
|
||||
// implementing Formatter
|
||||
|
||||
public Class getObjectType() {
|
||||
return objectType;
|
||||
}
|
||||
|
||||
public String format(Object value) {
|
||||
propertyEditor.setValue(value);
|
||||
return propertyEditor.getAsText();
|
||||
synchronized (propertyEditor) {
|
||||
propertyEditor.setValue(value);
|
||||
String text = propertyEditor.getAsText();
|
||||
propertyEditor.setValue(null);
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
public Object parse(String formattedValue) {
|
||||
propertyEditor.setAsText(formattedValue);
|
||||
return propertyEditor.getValue();
|
||||
synchronized (propertyEditor) {
|
||||
propertyEditor.setAsText(formattedValue);
|
||||
Object value = propertyEditor.getValue();
|
||||
propertyEditor.setValue(null);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,12 +17,12 @@ package org.springframework.binding.format.registry;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.util.Date;
|
||||
|
||||
import org.springframework.binding.convert.service.DefaultConversionService;
|
||||
import org.springframework.binding.format.FormatterRegistry;
|
||||
import org.springframework.binding.format.formatters.BooleanFormatter;
|
||||
import org.springframework.binding.format.formatters.DateFormatter;
|
||||
import org.springframework.binding.format.formatters.IntegerFormatter;
|
||||
import org.springframework.binding.format.formatters.NumberFormatter;
|
||||
|
||||
public class DefaultFormatterRegistry extends GenericFormatterRegistry {
|
||||
@@ -43,16 +43,16 @@ public class DefaultFormatterRegistry extends GenericFormatterRegistry {
|
||||
* Registers the default formatters. Subclasses may override.
|
||||
*/
|
||||
protected void registerDefaultFormatters() {
|
||||
registerFormatter(Integer.class, new NumberFormatter(Integer.class));
|
||||
registerFormatter(Long.class, new NumberFormatter(Long.class));
|
||||
registerFormatter(Short.class, new NumberFormatter(Short.class));
|
||||
registerFormatter(Float.class, new NumberFormatter(Float.class));
|
||||
registerFormatter(Double.class, new NumberFormatter(Double.class));
|
||||
registerFormatter(Byte.class, new NumberFormatter(Byte.class));
|
||||
registerFormatter(BigInteger.class, new NumberFormatter(BigInteger.class));
|
||||
registerFormatter(BigDecimal.class, new NumberFormatter(BigDecimal.class));
|
||||
registerFormatter(Boolean.class, new BooleanFormatter());
|
||||
registerFormatter(Date.class, new DateFormatter());
|
||||
registerFormatter(new IntegerFormatter(Integer.class));
|
||||
registerFormatter(new IntegerFormatter(BigInteger.class));
|
||||
registerFormatter(new IntegerFormatter(Long.class));
|
||||
registerFormatter(new IntegerFormatter(Short.class));
|
||||
registerFormatter(new NumberFormatter(Float.class));
|
||||
registerFormatter(new NumberFormatter(Double.class));
|
||||
registerFormatter(new NumberFormatter(BigDecimal.class));
|
||||
registerFormatter(new NumberFormatter(Byte.class));
|
||||
registerFormatter(new BooleanFormatter());
|
||||
registerFormatter(new DateFormatter());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
package org.springframework.binding.format.registry;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.binding.format.Formatter;
|
||||
@@ -24,8 +23,10 @@ import org.springframework.binding.format.FormatterRegistry;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Base class for formatter factories. Manages the locale used by the produced formatters using Spring's
|
||||
* {@link org.springframework.context.i18n.LocaleContext} system.
|
||||
* A general-purpose {@link FormatterRegistry} implementation allows formatters to be registered programatically.
|
||||
*
|
||||
* @see #registerFormatter(Class, Formatter)
|
||||
* @see #registerFormatter(String, Formatter)
|
||||
*
|
||||
* @author Keith Donald
|
||||
*/
|
||||
@@ -38,7 +39,7 @@ public class GenericFormatterRegistry implements FormatterRegistry {
|
||||
public Formatter getFormatter(Class clazz) {
|
||||
Assert.notNull(clazz, "The formatted class argument is required");
|
||||
clazz = convertToWrapperClassIfNecessary(clazz);
|
||||
return findFormatter(clazz);
|
||||
return (Formatter) formattersByClass.get(clazz);
|
||||
}
|
||||
|
||||
public Formatter getFormatter(String id) {
|
||||
@@ -48,9 +49,9 @@ public class GenericFormatterRegistry implements FormatterRegistry {
|
||||
|
||||
// impl
|
||||
|
||||
public void registerFormatter(Class clazz, Formatter formatter) {
|
||||
public void registerFormatter(Formatter formatter) {
|
||||
Assert.notNull(formatter, "The formatter to register is required");
|
||||
formattersByClass.put(clazz, formatter);
|
||||
formattersByClass.put(formatter.getObjectType(), formatter);
|
||||
}
|
||||
|
||||
public void registerFormatter(String id, Formatter formatter) {
|
||||
@@ -61,26 +62,6 @@ public class GenericFormatterRegistry implements FormatterRegistry {
|
||||
|
||||
// helpers
|
||||
|
||||
private Formatter findFormatter(Class clazz) {
|
||||
LinkedList classQueue = new LinkedList();
|
||||
classQueue.addFirst(clazz);
|
||||
while (!classQueue.isEmpty()) {
|
||||
clazz = (Class) classQueue.removeLast();
|
||||
Formatter factory = (Formatter) formattersByClass.get(clazz);
|
||||
if (factory != null) {
|
||||
return factory;
|
||||
}
|
||||
if (!clazz.isInterface() && clazz.getSuperclass() != null) {
|
||||
classQueue.add(clazz.getSuperclass());
|
||||
}
|
||||
Class[] interfaces = clazz.getInterfaces();
|
||||
for (int i = 0; i < interfaces.length; i++) {
|
||||
classQueue.addFirst(interfaces[i]);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Class convertToWrapperClassIfNecessary(Class targetType) {
|
||||
if (targetType.isPrimitive()) {
|
||||
if (targetType.equals(int.class)) {
|
||||
|
||||
Reference in New Issue
Block a user