diff --git a/spring-binding/src/main/java/org/springframework/binding/format/formatters/DateFormatter.java b/spring-binding/src/main/java/org/springframework/binding/format/formatters/DateFormatter.java index e4931c30..70359f52 100644 --- a/spring-binding/src/main/java/org/springframework/binding/format/formatters/DateFormatter.java +++ b/spring-binding/src/main/java/org/springframework/binding/format/formatters/DateFormatter.java @@ -21,8 +21,11 @@ import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; +import org.apache.commons.logging.Log; +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.StringUtils; /** @@ -32,6 +35,8 @@ import org.springframework.util.StringUtils; */ public class DateFormatter implements Formatter { + private static Log logger = LogFactory.getLog(DateFormatter.class); + /** * The default date pattern. */ @@ -42,7 +47,7 @@ public class DateFormatter implements Formatter { private Locale locale; /** - * The pattern to use to format date values. + * The pattern to use to format date values. If not specified, the default pattern 'yyyy-MM-dd' is used. * @return the date formatting pattern */ public String getPattern() { @@ -58,7 +63,8 @@ public class DateFormatter implements Formatter { } /** - * The locale to use in formatting date values. If null, the default locale is used. + * The locale to use in formatting date values. If not specified, the locale of the current thread is used. + * @see LocaleContextHolder#getLocale() * @return the locale */ public Locale getLocale() { @@ -88,16 +94,24 @@ public class DateFormatter implements Formatter { try { return dateFormat.parse(formattedString); } catch (ParseException e) { - throw new InvalidFormatException(formattedString, determinePattern(pattern), e); + throw new InvalidFormatException(formattedString, getPattern(dateFormat), e); } } // subclassing hookings protected DateFormat getDateFormat() { - String pattern = determinePattern(this.pattern); Locale locale = determineLocale(this.locale); - return new SimpleDateFormat(pattern, locale); + DateFormat format = DateFormat.getDateInstance(DateFormat.SHORT, locale); + format.setLenient(false); + if (format instanceof SimpleDateFormat) { + String pattern = determinePattern(this.pattern); + ((SimpleDateFormat) format).applyPattern(pattern); + } else { + logger.warn("Unable to apply format pattern '" + pattern + + "'; Returned DateFormat is not a SimpleDateFormat"); + } + return format; } // internal helpers @@ -107,7 +121,16 @@ public class DateFormatter implements Formatter { } private Locale determineLocale(Locale locale) { - return locale != null ? locale : Locale.getDefault(); + return locale != null ? locale : LocaleContextHolder.getLocale(); + } + + private String getPattern(DateFormat format) { + if (format instanceof SimpleDateFormat) { + return ((SimpleDateFormat) format).toPattern(); + } else { + logger.warn("Pattern string cannot be determined because DateFormat is not a SimpleDateFormat"); + return "defaultDateFormatInstance"; + } } } \ No newline at end of file diff --git a/spring-binding/src/main/java/org/springframework/binding/format/formatters/NumberFormatter.java b/spring-binding/src/main/java/org/springframework/binding/format/formatters/NumberFormatter.java index 3ba3a8a0..d98dd78a 100644 --- a/spring-binding/src/main/java/org/springframework/binding/format/formatters/NumberFormatter.java +++ b/spring-binding/src/main/java/org/springframework/binding/format/formatters/NumberFormatter.java @@ -17,25 +17,34 @@ package org.springframework.binding.format.formatters; import java.text.DecimalFormat; import java.text.NumberFormat; +import java.text.ParsePosition; +import java.util.Locale; +import org.apache.commons.logging.Log; +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.NumberUtils; import org.springframework.util.StringUtils; /** - * A formatter for common number types such as integers and big decimals. Supports basic decoding of number values from - * text, as well as applying custom number format patterns. + * A formatter for common number types such as integers and big decimals. Allows the configuration of an explicit number + * pattern and locale. * @see DecimalFormat * @author Keith Donald */ public class NumberFormatter implements Formatter { + private static Log logger = LogFactory.getLog(NumberFormatter.class); + private String pattern; private Class numberClass; + private Locale locale; + /** * Creates a number formatter for the specified number type. * @param numberClass the number type, a class extending from {@link Number}. @@ -45,6 +54,14 @@ public class NumberFormatter implements Formatter { this.numberClass = numberClass; } + /** + * The pattern to use to format number values. If not specified, the default DecimalFormat pattern is used. + * @return the date formatting pattern + */ + public String getPattern() { + return pattern; + } + /** * Sets the pattern for formatting numbers. * @param pattern the format pattern @@ -54,37 +71,72 @@ public class NumberFormatter implements Formatter { this.pattern = pattern; } + /** + * The locale to use in formatting number values. If null, the locale associated with the current thread is used. + * @see LocaleContextHolder#getLocale() + * @return the locale + */ + public Locale getLocale() { + return locale; + } + + /** + * Sets the locale to use in formatting number values. + * @param locale the locale + */ + public void setLocale(Locale locale) { + this.locale = locale; + } + public String format(Object number) { if (number == null) { return ""; } - if (pattern != null) { - return getNumberFormat().format(number); - } else { - return number.toString(); - } + return getNumberFormat().format(number); } public Object parse(String formattedString) throws InvalidFormatException { if (!StringUtils.hasText(formattedString)) { return null; } - if (pattern != null) { - try { - return NumberUtils.parseNumber(formattedString, numberClass, getNumberFormat()); - } catch (IllegalArgumentException e) { - throw new InvalidFormatException(formattedString, pattern, e); - } + ParsePosition parsePosition = new ParsePosition(0); + NumberFormat format = getNumberFormat(); + Number number = format.parse(formattedString, parsePosition); + if (number == null || formattedString.length() != parsePosition.getIndex()) { + throw new InvalidFormatException(formattedString, getPattern(format)); } else { - try { - return NumberUtils.parseNumber(formattedString, numberClass); - } catch (NumberFormatException e) { - throw new InvalidFormatException(formattedString, "A valid " + numberClass.getName() + " string", e); - } + return NumberUtils.convertNumberToTargetClass(number, numberClass); } } - private NumberFormat getNumberFormat() { - return new DecimalFormat(pattern); + // subclassing hookings + + protected NumberFormat getNumberFormat() { + Locale locale = determineLocale(this.locale); + NumberFormat format = NumberFormat.getInstance(locale); + if (pattern != null) { + if (format instanceof DecimalFormat) { + ((DecimalFormat) format).applyPattern(pattern); + } else { + logger.warn("Unable to apply format pattern '" + pattern + + "'; Returned NumberFormat is not a DecimalFormat"); + } + } + return format; + } + + // internal helpers + + private Locale determineLocale(Locale locale) { + return locale != null ? locale : LocaleContextHolder.getLocale(); + } + + private String getPattern(NumberFormat format) { + if (format instanceof DecimalFormat) { + return ((DecimalFormat) format).toPattern(); + } else { + logger.warn("Pattern string cannot be determined because NumberFormat is not a DecimalFormat"); + return "defaultNumberFormatInstance"; + } } } \ No newline at end of file diff --git a/spring-binding/src/test/java/org/springframework/binding/format/formatters/DateFormatterTests.java b/spring-binding/src/test/java/org/springframework/binding/format/formatters/DateFormatterTests.java index 58ebf8a8..79f8565d 100644 --- a/spring-binding/src/test/java/org/springframework/binding/format/formatters/DateFormatterTests.java +++ b/spring-binding/src/test/java/org/springframework/binding/format/formatters/DateFormatterTests.java @@ -3,6 +3,7 @@ package org.springframework.binding.format.formatters; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; +import java.util.Locale; import junit.framework.TestCase; @@ -12,6 +13,7 @@ public class DateFormatterTests extends TestCase { public void testFormatDefaultPattern() { DateFormatter dateFormatter = new DateFormatter(); + dateFormatter.setLocale(new Locale("nl")); Calendar calendar = new GregorianCalendar(2008, 3, 1); assertEquals("2008-04-01", dateFormatter.format(calendar.getTime())); } diff --git a/spring-binding/src/test/java/org/springframework/binding/format/formatters/NumberFormatterTests.java b/spring-binding/src/test/java/org/springframework/binding/format/formatters/NumberFormatterTests.java index 9ec1960a..ffb53884 100644 --- a/spring-binding/src/test/java/org/springframework/binding/format/formatters/NumberFormatterTests.java +++ b/spring-binding/src/test/java/org/springframework/binding/format/formatters/NumberFormatterTests.java @@ -1,6 +1,7 @@ package org.springframework.binding.format.formatters; import java.math.BigDecimal; +import java.util.Locale; import junit.framework.TestCase; @@ -12,13 +13,15 @@ public class NumberFormatterTests extends TestCase { public void testFormatIntegerDefaultPattern() { formatter = new NumberFormatter(Integer.class); + formatter.setLocale(Locale.ENGLISH); String value = formatter.format(new Integer(12345)); - assertEquals("12345", value); + assertEquals("12,345", value); } public void testFormatBigDecimalCustomPattern() { formatter = new NumberFormatter(BigDecimal.class); formatter.setPattern("000.00"); + formatter.setLocale(Locale.ENGLISH); BigDecimal dec = new BigDecimal("123.45"); String value = formatter.format(dec); assertEquals("123.45", value); @@ -31,21 +34,23 @@ public class NumberFormatterTests extends TestCase { public void testParseIntegerDefaultPattern() { formatter = new NumberFormatter(Integer.class); - Integer integer = (Integer) formatter.parse("12345"); - assertEquals(Integer.valueOf(12345), integer); + formatter.setLocale(Locale.ENGLISH); + Integer integer = (Integer) formatter.parse("123,450"); + assertEquals(Integer.valueOf(123450), integer); } public void testParseBigDecimalCustomPattern() { formatter = new NumberFormatter(BigDecimal.class); formatter.setPattern("000.00"); + formatter.setLocale(Locale.ENGLISH); BigDecimal dec = (BigDecimal) formatter.parse("123.45"); assertEquals(new BigDecimal("123.45"), dec); } - public void testParseInvalidFormatNoPattern() { + public void testParseInvalidFormatPatternTruncation() { try { formatter = new NumberFormatter(Integer.class); - formatter.parse("12345b"); + formatter.parse("123,450b"); fail("Should have failed"); } catch (InvalidFormatException e) { } @@ -56,6 +61,7 @@ public class NumberFormatterTests extends TestCase { formatter = new NumberFormatter(BigDecimal.class); formatter.setPattern("000.00"); formatter.parse("bogus"); + fail("Should have failed"); } catch (InvalidFormatException e) { } }