From 2381452e9a1f850a743216ab4ae9b18d0bb4c418 Mon Sep 17 00:00:00 2001 From: Keith Donald Date: Mon, 17 Aug 2009 18:35:04 +0000 Subject: [PATCH] SPR-6012, SPR-6013, SPR-6014 initial commit --- .../ui/format/AnnotationFormatterFactory.java | 37 +++++ .../springframework/ui/format/Formatted.java | 38 +++++ .../springframework/ui/format/Formatter.java | 45 ++++++ .../ui/format/FormatterRegistry.java | 61 ++++++++ .../ui/format/GenericFormatterRegistry.java | 145 ++++++++++++++++++ .../ui/format/date/DateFormatter.java | 90 +++++++++++ .../ui/format/date/package-info.java | 5 + .../ui/format/number/CurrencyFormat.java | 34 ++++ .../ui/format/number/CurrencyFormatter.java | 71 +++++++++ .../number/CurrencyNumberFormatFactory.java | 39 +++++ .../ui/format/number/DecimalFormatter.java | 81 ++++++++++ .../number/DefaultNumberFormatFactory.java | 80 ++++++++++ .../ui/format/number/IntegerFormatter.java | 65 ++++++++ .../number/IntegerNumberFormatFactory.java | 31 ++++ .../ui/format/number/NumberFormatFactory.java | 36 +++++ .../ui/format/number/PercentFormatter.java | 67 ++++++++ .../number/PercentNumberFormatFactory.java | 34 ++++ .../ui/format/number/package-info.java | 5 + .../ui/format/package-info.java | 5 + .../format/GenericFormatterRegistryTests.java | 48 ++++++ .../ui/format/date/DateFormatterTests.java | 36 +++++ .../format/number/CurrencyFormatterTests.java | 51 ++++++ .../format/number/DecimalFormatterTests.java | 41 +++++ .../format/number/IntegerFormatterTests.java | 40 +++++ .../format/number/PercentFormatterTests.java | 42 +++++ 25 files changed, 1227 insertions(+) create mode 100644 org.springframework.context/src/main/java/org/springframework/ui/format/AnnotationFormatterFactory.java create mode 100644 org.springframework.context/src/main/java/org/springframework/ui/format/Formatted.java create mode 100644 org.springframework.context/src/main/java/org/springframework/ui/format/Formatter.java create mode 100644 org.springframework.context/src/main/java/org/springframework/ui/format/FormatterRegistry.java create mode 100644 org.springframework.context/src/main/java/org/springframework/ui/format/GenericFormatterRegistry.java create mode 100644 org.springframework.context/src/main/java/org/springframework/ui/format/date/DateFormatter.java create mode 100644 org.springframework.context/src/main/java/org/springframework/ui/format/date/package-info.java create mode 100644 org.springframework.context/src/main/java/org/springframework/ui/format/number/CurrencyFormat.java create mode 100644 org.springframework.context/src/main/java/org/springframework/ui/format/number/CurrencyFormatter.java create mode 100644 org.springframework.context/src/main/java/org/springframework/ui/format/number/CurrencyNumberFormatFactory.java create mode 100644 org.springframework.context/src/main/java/org/springframework/ui/format/number/DecimalFormatter.java create mode 100644 org.springframework.context/src/main/java/org/springframework/ui/format/number/DefaultNumberFormatFactory.java create mode 100644 org.springframework.context/src/main/java/org/springframework/ui/format/number/IntegerFormatter.java create mode 100644 org.springframework.context/src/main/java/org/springframework/ui/format/number/IntegerNumberFormatFactory.java create mode 100644 org.springframework.context/src/main/java/org/springframework/ui/format/number/NumberFormatFactory.java create mode 100644 org.springframework.context/src/main/java/org/springframework/ui/format/number/PercentFormatter.java create mode 100644 org.springframework.context/src/main/java/org/springframework/ui/format/number/PercentNumberFormatFactory.java create mode 100644 org.springframework.context/src/main/java/org/springframework/ui/format/number/package-info.java create mode 100644 org.springframework.context/src/main/java/org/springframework/ui/format/package-info.java create mode 100644 org.springframework.context/src/test/java/org/springframework/ui/format/GenericFormatterRegistryTests.java create mode 100644 org.springframework.context/src/test/java/org/springframework/ui/format/date/DateFormatterTests.java create mode 100644 org.springframework.context/src/test/java/org/springframework/ui/format/number/CurrencyFormatterTests.java create mode 100644 org.springframework.context/src/test/java/org/springframework/ui/format/number/DecimalFormatterTests.java create mode 100644 org.springframework.context/src/test/java/org/springframework/ui/format/number/IntegerFormatterTests.java create mode 100644 org.springframework.context/src/test/java/org/springframework/ui/format/number/PercentFormatterTests.java diff --git a/org.springframework.context/src/main/java/org/springframework/ui/format/AnnotationFormatterFactory.java b/org.springframework.context/src/main/java/org/springframework/ui/format/AnnotationFormatterFactory.java new file mode 100644 index 00000000000..05bf91ba6ec --- /dev/null +++ b/org.springframework.context/src/main/java/org/springframework/ui/format/AnnotationFormatterFactory.java @@ -0,0 +1,37 @@ +/* + * Copyright 2004-2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ui.format; + +import java.lang.annotation.Annotation; + +/** + * A factory that creates {@link Formatter formatters} to format property values on properties annotated with a particular format {@link Annotation}. + * For example, a CurrencyAnnotationFormatterFactory might create a Formatter that formats a BigDecimal value set on a property annotated with @CurrencyFormat. + * @author Keith Donald + * @since 3.0 + * @param The type of Annotation this factory uses to create Formatter instances + * @param The type of Object Formatters created by this factory format + */ +public interface AnnotationFormatterFactory { + + /** + * Get the Formatter that will format the value of the property annotated with the provided annotation. + * The annotation instance can contain properties that may be used to configure the Formatter that is returned. + * @param annotation the annotation instance + * @return the Formatter to use to format values of properties annotated with the annotation. + */ + Formatter getFormatter(A annotation); +} \ No newline at end of file diff --git a/org.springframework.context/src/main/java/org/springframework/ui/format/Formatted.java b/org.springframework.context/src/main/java/org/springframework/ui/format/Formatted.java new file mode 100644 index 00000000000..bac18638852 --- /dev/null +++ b/org.springframework.context/src/main/java/org/springframework/ui/format/Formatted.java @@ -0,0 +1,38 @@ +/* + * Copyright 2004-2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ui.format; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * A type that can be formatted as a String for display in a UI. + * @author Keith Donald + * @since 3.0 + */ +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Formatted { + + /** + * The Formatter that handles the formatting. + */ + Class value(); +} diff --git a/org.springframework.context/src/main/java/org/springframework/ui/format/Formatter.java b/org.springframework.context/src/main/java/org/springframework/ui/format/Formatter.java new file mode 100644 index 00000000000..e9342f6169c --- /dev/null +++ b/org.springframework.context/src/main/java/org/springframework/ui/format/Formatter.java @@ -0,0 +1,45 @@ +/* + * Copyright 2004-2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ui.format; + +import java.text.ParseException; +import java.util.Locale; + +/** + * Formats objects of type T for display. + * @author Keith Donald + * @since 3.0 + * @param the type of object this formatter can format + */ +public interface Formatter { + + /** + * Format the object of type T for display. + * @param object the object to format + * @param locale the user's locale + * @return the formatted display string + */ + String format(T object, Locale locale); + + /** + * Parse an object from its formatted representation. + * @param formatted a formatted representation + * @param locale the user's locale + * @return the parsed object + * @throws ParseException when a parse exception occurs + */ + T parse(String formatted, Locale locale) throws ParseException; +} diff --git a/org.springframework.context/src/main/java/org/springframework/ui/format/FormatterRegistry.java b/org.springframework.context/src/main/java/org/springframework/ui/format/FormatterRegistry.java new file mode 100644 index 00000000000..62d997461de --- /dev/null +++ b/org.springframework.context/src/main/java/org/springframework/ui/format/FormatterRegistry.java @@ -0,0 +1,61 @@ +/* + * Copyright 2004-2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ui.format; + +import java.lang.annotation.Annotation; + +import org.springframework.core.convert.TypeDescriptor; + +/** + * A shared registry of Formatters. + * @author Keith Donald + * @since 3.0 + */ +public interface FormatterRegistry { + + /** + * Adds a Formatter to this registry indexed by . + * Calling getFormatter(<T>.class) returns formatter. + * @param formatter the formatter + * @param the type of object the formatter formats + */ + void add(Formatter 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 targetFormatter 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 targetFormatter the target formatter + * @param the type of object the target formatter formats + */ + void add(Class objectType, Formatter targetFormatter); + + /** + * Adds a AnnotationFormatterFactory that will format values of properties annotated with a specific annotation. + * @param factory the annotation formatter factory + */ + void add(AnnotationFormatterFactory factory); + + /** + * Get the Formatter for the type. + * @return the Formatter, or null if none is registered + */ + Formatter getFormatter(TypeDescriptor type); + +} diff --git a/org.springframework.context/src/main/java/org/springframework/ui/format/GenericFormatterRegistry.java b/org.springframework.context/src/main/java/org/springframework/ui/format/GenericFormatterRegistry.java new file mode 100644 index 00000000000..b4f85b0077b --- /dev/null +++ b/org.springframework.context/src/main/java/org/springframework/ui/format/GenericFormatterRegistry.java @@ -0,0 +1,145 @@ +/* + * Copyright 2004-2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ui.format; + +import java.lang.annotation.Annotation; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.springframework.core.GenericTypeResolver; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.core.convert.TypeDescriptor; +import org.springframework.util.Assert; + +/** + * A generic implementation of {@link FormatterRegistry} suitable for use in most binding environments. + * @author Keith Donald + * @since 3.0 + * @see #add(Class, Formatter) + * @see #add(AnnotationFormatterFactory) + */ +@SuppressWarnings("unchecked") +public class GenericFormatterRegistry implements FormatterRegistry { + + private Map typeFormatters = new ConcurrentHashMap(); + + private Map annotationFormatters = new HashMap(); + + public void add(Formatter formatter) { + // TODO + } + + public void add(Class objectType, Formatter formatter) { + if (objectType.isAnnotation()) { + annotationFormatters.put(objectType, new SimpleAnnotationFormatterFactory(formatter)); + } else { + typeFormatters.put(objectType, formatter); + } + } + + public void add(AnnotationFormatterFactory factory) { + annotationFormatters.put(getAnnotationType(factory), factory); + } + + public Formatter getFormatter(TypeDescriptor type) { + Assert.notNull(type, "The TypeDescriptor is required"); + Annotation[] annotations = type.getAnnotations(); + for (Annotation a : annotations) { + AnnotationFormatterFactory factory = annotationFormatters.get(a.annotationType()); + if (factory != null) { + return factory.getFormatter(a); + } + } + return getFormatter(type.getType()); + } + + // internal helpers + + private Formatter getFormatter(Class type) { + Assert.notNull(type, "The Class of the object to format is required"); + Formatter formatter = typeFormatters.get(type); + if (formatter != null) { + return formatter; + } else { + 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; + } else { + return null; + } + } + } + + private Class getAnnotationType(AnnotationFormatterFactory factory) { + Class classToIntrospect = factory.getClass(); + while (classToIntrospect != null) { + Type[] genericInterfaces = classToIntrospect.getGenericInterfaces(); + for (Type genericInterface : genericInterfaces) { + if (genericInterface instanceof ParameterizedType) { + ParameterizedType pInterface = (ParameterizedType) genericInterface; + if (AnnotationFormatterFactory.class.isAssignableFrom((Class) pInterface.getRawType())) { + return getParameterClass(pInterface.getActualTypeArguments()[0], factory.getClass()); + } + } + } + classToIntrospect = classToIntrospect.getSuperclass(); + } + throw new IllegalArgumentException( + "Unable to extract Annotation type A argument from AnnotationFormatterFactory [" + + factory.getClass().getName() + "]; does the factory parameterize the 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 Formatter formatter; + + public SimpleAnnotationFormatterFactory(Formatter formatter) { + this.formatter = formatter; + } + + public Formatter getFormatter(Annotation annotation) { + return formatter; + } + + } + +} \ No newline at end of file diff --git a/org.springframework.context/src/main/java/org/springframework/ui/format/date/DateFormatter.java b/org.springframework.context/src/main/java/org/springframework/ui/format/date/DateFormatter.java new file mode 100644 index 00000000000..d5c5c152bd8 --- /dev/null +++ b/org.springframework.context/src/main/java/org/springframework/ui/format/date/DateFormatter.java @@ -0,0 +1,90 @@ +/* + * Copyright 2004-2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ui.format.date; + +import java.text.DateFormat; +import java.text.ParseException; +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.ui.format.Formatter; + +/** + * A formatter for {@link Date} types. + * Allows the configuration of an explicit date pattern and locale. + * @author Keith Donald + * @since 3.0 + * @see SimpleDateFormat + */ +public class DateFormatter implements Formatter { + + private static Log logger = LogFactory.getLog(DateFormatter.class); + + /** + * The default date pattern. + */ + private static final String DEFAULT_PATTERN = "yyyy-MM-dd"; + + private String pattern; + + /** + * Sets the pattern to use to format date values. + * If not specified, the default pattern 'yyyy-MM-dd' is used. + * @param pattern the date formatting pattern + */ + public void setPattern(String pattern) { + this.pattern = pattern; + } + + public String format(Date date, Locale locale) { + if (date == null) { + return ""; + } + return getDateFormat(locale).format(date); + } + + public Date parse(String formatted, Locale locale) throws ParseException { + if (formatted.length() == 0) { + return null; + } + return getDateFormat(locale).parse(formatted); + } + + // subclassing hookings + + protected DateFormat getDateFormat(Locale 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 + + private String determinePattern(String pattern) { + return pattern != null ? pattern : DEFAULT_PATTERN; + } + +} \ No newline at end of file diff --git a/org.springframework.context/src/main/java/org/springframework/ui/format/date/package-info.java b/org.springframework.context/src/main/java/org/springframework/ui/format/date/package-info.java new file mode 100644 index 00000000000..60f5e723638 --- /dev/null +++ b/org.springframework.context/src/main/java/org/springframework/ui/format/date/package-info.java @@ -0,0 +1,5 @@ +/** + * Formatters for java.util.Date fields. + */ +package org.springframework.ui.format.date; + diff --git a/org.springframework.context/src/main/java/org/springframework/ui/format/number/CurrencyFormat.java b/org.springframework.context/src/main/java/org/springframework/ui/format/number/CurrencyFormat.java new file mode 100644 index 00000000000..bd37362d6d7 --- /dev/null +++ b/org.springframework.context/src/main/java/org/springframework/ui/format/number/CurrencyFormat.java @@ -0,0 +1,34 @@ +/* + * Copyright 2004-2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ui.format.number; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * A annotation to apply to a BigDecimal property to have its value formatted as currency amount using a {@link CurrencyFormatter}. + * @author Keith Donald + * @since 3.0 + */ +@Target( { ElementType.METHOD, ElementType.FIELD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface CurrencyFormat { + +} diff --git a/org.springframework.context/src/main/java/org/springframework/ui/format/number/CurrencyFormatter.java b/org.springframework.context/src/main/java/org/springframework/ui/format/number/CurrencyFormatter.java new file mode 100644 index 00000000000..d12e1ea4323 --- /dev/null +++ b/org.springframework.context/src/main/java/org/springframework/ui/format/number/CurrencyFormatter.java @@ -0,0 +1,71 @@ +/* + * Copyright 2004-2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ui.format.number; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.text.NumberFormat; +import java.text.ParseException; +import java.text.ParsePosition; +import java.util.Locale; + +import org.springframework.ui.format.Formatter; + +/** + * A BigDecimal formatter for currency values. + * Delegates to {@link NumberFormat#getCurrencyInstance(Locale)}. + * Configures BigDecimal parsing so there is no loss of precision. + * Sets the scale of parsed BigDecimal values to {@link NumberFormat#getMaximumFractionDigits()}. + * Applies {@link RoundingMode#DOWN} to parsed values. + * @author Keith Donald + * @since 3.0 + */ +public class CurrencyFormatter implements Formatter { + + private CurrencyNumberFormatFactory currencyFormatFactory = new CurrencyNumberFormatFactory(); + + private boolean lenient; + + public String format(BigDecimal decimal, Locale locale) { + if (decimal == null) { + return ""; + } + NumberFormat format = currencyFormatFactory.getNumberFormat(locale); + return format.format(decimal); + } + + public BigDecimal parse(String formatted, Locale locale) + throws ParseException { + if (formatted.length() == 0) { + return null; + } + NumberFormat format = currencyFormatFactory.getNumberFormat(locale); + ParsePosition position = new ParsePosition(0); + BigDecimal decimal = (BigDecimal) format.parse(formatted, position); + if (position.getErrorIndex() != -1) { + throw new ParseException(formatted, position.getIndex()); + } + if (!lenient) { + if (formatted.length() != position.getIndex()) { + // indicates a part of the string that was not parsed + throw new ParseException(formatted, position.getIndex()); + } + } + decimal = decimal.setScale(format.getMaximumFractionDigits(), format.getRoundingMode()); + return decimal; + } + +} \ No newline at end of file diff --git a/org.springframework.context/src/main/java/org/springframework/ui/format/number/CurrencyNumberFormatFactory.java b/org.springframework.context/src/main/java/org/springframework/ui/format/number/CurrencyNumberFormatFactory.java new file mode 100644 index 00000000000..648feb4ae0c --- /dev/null +++ b/org.springframework.context/src/main/java/org/springframework/ui/format/number/CurrencyNumberFormatFactory.java @@ -0,0 +1,39 @@ +/* + * Copyright 2004-2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ui.format.number; + +import java.math.RoundingMode; +import java.text.DecimalFormat; +import java.text.NumberFormat; +import java.util.Locale; + +/** + * Produces NumberFormat instances that format currency values. + * @author Keith Donald + * @since 3.0 + * @see NumberFormat + */ +final class CurrencyNumberFormatFactory extends NumberFormatFactory { + + private RoundingMode roundingMode = RoundingMode.DOWN; + + public NumberFormat getNumberFormat(Locale locale) { + DecimalFormat format = (DecimalFormat) NumberFormat.getCurrencyInstance(locale); + format.setParseBigDecimal(true); + format.setRoundingMode(roundingMode); + return format; + } +} \ No newline at end of file diff --git a/org.springframework.context/src/main/java/org/springframework/ui/format/number/DecimalFormatter.java b/org.springframework.context/src/main/java/org/springframework/ui/format/number/DecimalFormatter.java new file mode 100644 index 00000000000..1ba182f5625 --- /dev/null +++ b/org.springframework.context/src/main/java/org/springframework/ui/format/number/DecimalFormatter.java @@ -0,0 +1,81 @@ +/* + * Copyright 2004-2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ui.format.number; + +import java.math.BigDecimal; +import java.text.NumberFormat; +import java.text.ParseException; +import java.text.ParsePosition; +import java.util.Locale; + +import org.springframework.ui.format.Formatter; + +/** + * A BigDecimal 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)}. + * @author Keith Donald + * @since 3.0 + */ +public class DecimalFormatter implements Formatter { + + private DefaultNumberFormatFactory formatFactory = new DefaultNumberFormatFactory(); + + private boolean lenient; + + public DecimalFormatter() { + initDefaults(); + } + + public DecimalFormatter(String pattern) { + initDefaults(); + formatFactory.setPattern(pattern); + } + + public String format(BigDecimal decimal, Locale locale) { + if (decimal == null) { + return ""; + } + NumberFormat format = formatFactory.getNumberFormat(locale); + return format.format(decimal); + } + + public BigDecimal parse(String formatted, Locale locale) + throws ParseException { + if (formatted.length() == 0) { + return null; + } + NumberFormat format = formatFactory.getNumberFormat(locale); + ParsePosition position = new ParsePosition(0); + BigDecimal decimal = (BigDecimal) format.parse(formatted, position); + if (position.getErrorIndex() != -1) { + throw new ParseException(formatted, position.getIndex()); + } + if (!lenient) { + if (formatted.length() != position.getIndex()) { + // indicates a part of the string that was not parsed + throw new ParseException(formatted, position.getIndex()); + } + } + return decimal; + } + + private void initDefaults() { + formatFactory.setParseBigDecimal(true); + } + +} \ No newline at end of file diff --git a/org.springframework.context/src/main/java/org/springframework/ui/format/number/DefaultNumberFormatFactory.java b/org.springframework.context/src/main/java/org/springframework/ui/format/number/DefaultNumberFormatFactory.java new file mode 100644 index 00000000000..1cdd51262c8 --- /dev/null +++ b/org.springframework.context/src/main/java/org/springframework/ui/format/number/DefaultNumberFormatFactory.java @@ -0,0 +1,80 @@ +/* + * Copyright 2004-2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ui.format.number; + +import java.text.DecimalFormat; +import java.text.NumberFormat; +import java.util.Locale; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Works with a general purpose {@link DecimalFormat} instance returned by calling + * {@link NumberFormat#getInstance(Locale)} by default. + * @author Keith Donald + * @see NumberFormat + * @see DecimalFormat + * @since 3.0 + */ +class DefaultNumberFormatFactory extends NumberFormatFactory { + + private static Log logger = LogFactory.getLog(DefaultNumberFormatFactory.class); + + private String pattern; + + private Boolean parseBigDecimal; + + /** + * 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) { + this.pattern = pattern; + } + + /** + * Sets whether the format should always parse a big decimal. + * @param parseBigDecimal the big decimal parse status + * @see DecimalFormat#setParseBigDecimal(boolean) + */ + public void setParseBigDecimal(boolean parseBigDecimal) { + this.parseBigDecimal = parseBigDecimal; + } + + public NumberFormat getNumberFormat(Locale 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"); + } + } + if (parseBigDecimal != null) { + if (format instanceof DecimalFormat) { + ((DecimalFormat) format).setParseBigDecimal(parseBigDecimal); + } else { + logger.warn("Unable to call setParseBigDecimal; not a DecimalFormat"); + } + } + return format; + } + +} \ No newline at end of file diff --git a/org.springframework.context/src/main/java/org/springframework/ui/format/number/IntegerFormatter.java b/org.springframework.context/src/main/java/org/springframework/ui/format/number/IntegerFormatter.java new file mode 100644 index 00000000000..3535dfc3837 --- /dev/null +++ b/org.springframework.context/src/main/java/org/springframework/ui/format/number/IntegerFormatter.java @@ -0,0 +1,65 @@ +/* + * Copyright 2004-2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ui.format.number; + +import java.text.NumberFormat; +import java.text.ParseException; +import java.text.ParsePosition; +import java.util.Locale; + +import org.springframework.ui.format.Formatter; + +/** + * A Long formatter for whole integer values. + * Delegates to {@link NumberFormat#getIntegerInstance(Locale)}. + * @author Keith Donald + * @since 3.0 + */ +public class IntegerFormatter implements Formatter { + + private IntegerNumberFormatFactory formatFactory = new IntegerNumberFormatFactory(); + + private boolean lenient; + + public String format(Long integer, Locale locale) { + if (integer == null) { + return ""; + } + NumberFormat format = formatFactory.getNumberFormat(locale); + return format.format(integer); + } + + public Long parse(String formatted, Locale locale) + throws ParseException { + if (formatted.length() == 0) { + return null; + } + NumberFormat format = formatFactory.getNumberFormat(locale); + ParsePosition position = new ParsePosition(0); + Long integer = (Long) format.parse(formatted, position); + if (position.getErrorIndex() != -1) { + throw new ParseException(formatted, position.getIndex()); + } + if (!lenient) { + if (formatted.length() != position.getIndex()) { + // indicates a part of the string that was not parsed + throw new ParseException(formatted, position.getIndex()); + } + } + return integer; + } + +} \ No newline at end of file diff --git a/org.springframework.context/src/main/java/org/springframework/ui/format/number/IntegerNumberFormatFactory.java b/org.springframework.context/src/main/java/org/springframework/ui/format/number/IntegerNumberFormatFactory.java new file mode 100644 index 00000000000..502c6dac998 --- /dev/null +++ b/org.springframework.context/src/main/java/org/springframework/ui/format/number/IntegerNumberFormatFactory.java @@ -0,0 +1,31 @@ +/* + * Copyright 2004-2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ui.format.number; + +import java.text.NumberFormat; +import java.util.Locale; + +/** + * Produces NumberFormat instances that format integer values. + * @author Keith Donald + * @see NumberFormat + * @since 3.0 + */ +final class IntegerNumberFormatFactory extends NumberFormatFactory { + public NumberFormat getNumberFormat(Locale locale) { + return NumberFormat.getIntegerInstance(locale); + } +} \ No newline at end of file diff --git a/org.springframework.context/src/main/java/org/springframework/ui/format/number/NumberFormatFactory.java b/org.springframework.context/src/main/java/org/springframework/ui/format/number/NumberFormatFactory.java new file mode 100644 index 00000000000..66f871afa06 --- /dev/null +++ b/org.springframework.context/src/main/java/org/springframework/ui/format/number/NumberFormatFactory.java @@ -0,0 +1,36 @@ +/* + * Copyright 2004-2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ui.format.number; + +import java.text.NumberFormat; +import java.util.Locale; + +/** + * A factory for {@link NumberFormat} objects. + * Conceals the complexity associated with configuring, constructing, and/or caching number format instances. + * @author Keith Donald + * @since 3.0 + */ +abstract class NumberFormatFactory { + + /** + * Factory method that returns a fully-configured {@link NumberFormat} instance to use to format an object for + * display. + * @return the number format + */ + public abstract NumberFormat getNumberFormat(Locale locale); + +} \ No newline at end of file diff --git a/org.springframework.context/src/main/java/org/springframework/ui/format/number/PercentFormatter.java b/org.springframework.context/src/main/java/org/springframework/ui/format/number/PercentFormatter.java new file mode 100644 index 00000000000..5044b28464e --- /dev/null +++ b/org.springframework.context/src/main/java/org/springframework/ui/format/number/PercentFormatter.java @@ -0,0 +1,67 @@ +/* + * Copyright 2004-2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ui.format.number; + +import java.math.BigDecimal; +import java.text.NumberFormat; +import java.text.ParseException; +import java.text.ParsePosition; +import java.util.Locale; + +import org.springframework.ui.format.Formatter; + +/** + * A BigDecimal formatter for percent values. + * Delegates to {@link NumberFormat#getPercentInstance(Locale)}. + * Configures BigDecimal parsing so there is no loss in precision. + * @author Keith Donald + * @since 3.0 + */ +public class PercentFormatter implements Formatter { + + private PercentNumberFormatFactory percentFormatFactory = new PercentNumberFormatFactory(); + + private boolean lenient; + + public String format(BigDecimal decimal, Locale locale) { + if (decimal == null) { + return ""; + } + NumberFormat format = percentFormatFactory.getNumberFormat(locale); + return format.format(decimal); + } + + public BigDecimal parse(String formatted, Locale locale) + throws ParseException { + if (formatted.length() == 0) { + return null; + } + NumberFormat format = percentFormatFactory.getNumberFormat(locale); + ParsePosition position = new ParsePosition(0); + BigDecimal decimal = (BigDecimal) format.parse(formatted, position); + if (position.getErrorIndex() != -1) { + throw new ParseException(formatted, position.getIndex()); + } + if (!lenient) { + if (formatted.length() != position.getIndex()) { + // indicates a part of the string that was not parsed + throw new ParseException(formatted, position.getIndex()); + } + } + return decimal; + } + +} \ No newline at end of file diff --git a/org.springframework.context/src/main/java/org/springframework/ui/format/number/PercentNumberFormatFactory.java b/org.springframework.context/src/main/java/org/springframework/ui/format/number/PercentNumberFormatFactory.java new file mode 100644 index 00000000000..b6724a3e475 --- /dev/null +++ b/org.springframework.context/src/main/java/org/springframework/ui/format/number/PercentNumberFormatFactory.java @@ -0,0 +1,34 @@ +/* + * Copyright 2004-2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ui.format.number; + +import java.text.DecimalFormat; +import java.text.NumberFormat; +import java.util.Locale; + +/** + * Produces NumberFormat instances that format percent values. + * @see NumberFormat + * @author Keith Donald + * @since 3.0 + */ +final class PercentNumberFormatFactory extends NumberFormatFactory { + public NumberFormat getNumberFormat(Locale locale) { + DecimalFormat format = (DecimalFormat) NumberFormat.getPercentInstance(locale); + format.setParseBigDecimal(true); + return format; + } +} \ No newline at end of file diff --git a/org.springframework.context/src/main/java/org/springframework/ui/format/number/package-info.java b/org.springframework.context/src/main/java/org/springframework/ui/format/number/package-info.java new file mode 100644 index 00000000000..421a7800b6c --- /dev/null +++ b/org.springframework.context/src/main/java/org/springframework/ui/format/number/package-info.java @@ -0,0 +1,5 @@ +/** + * Formatters for java.lang.Number properties. + */ +package org.springframework.ui.format.number; + diff --git a/org.springframework.context/src/main/java/org/springframework/ui/format/package-info.java b/org.springframework.context/src/main/java/org/springframework/ui/format/package-info.java new file mode 100644 index 00000000000..1e04819f108 --- /dev/null +++ b/org.springframework.context/src/main/java/org/springframework/ui/format/package-info.java @@ -0,0 +1,5 @@ +/** + * A SPI for defining Formatters to format field model values for display in a UI. + */ +package org.springframework.ui.format; + diff --git a/org.springframework.context/src/test/java/org/springframework/ui/format/GenericFormatterRegistryTests.java b/org.springframework.context/src/test/java/org/springframework/ui/format/GenericFormatterRegistryTests.java new file mode 100644 index 00000000000..692f05ab5ee --- /dev/null +++ b/org.springframework.context/src/test/java/org/springframework/ui/format/GenericFormatterRegistryTests.java @@ -0,0 +1,48 @@ +package org.springframework.ui.format; + +import static org.junit.Assert.assertEquals; + +import java.util.Locale; + +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.springframework.core.convert.TypeDescriptor; +import org.springframework.ui.format.number.IntegerFormatter; + +public class GenericFormatterRegistryTests { + + private GenericFormatterRegistry registry; + + @Before + public void setUp() { + registry = new GenericFormatterRegistry(); + } + + @Test + @Ignore + public void testAdd() { + registry.add(new IntegerFormatter()); + Formatter formatter = registry.getFormatter(typeDescriptor(Long.class)); + String formatted = formatter.format(new Long(3), Locale.US); + assertEquals("3", formatted); + } + + @Test + @Ignore + public void testAddByOtherObjectType() { + registry.add(Integer.class, new IntegerFormatter()); + Formatter formatter = registry.getFormatter(typeDescriptor(Integer.class)); + String formatted = formatter.format(new Integer(3), Locale.US); + assertEquals("3", formatted); + } + + @Test + @Ignore + public void testAddAnnotationFormatterFactory() { + } + + private static TypeDescriptor typeDescriptor(Class clazz) { + return TypeDescriptor.valueOf(clazz); + } +} diff --git a/org.springframework.context/src/test/java/org/springframework/ui/format/date/DateFormatterTests.java b/org.springframework.context/src/test/java/org/springframework/ui/format/date/DateFormatterTests.java new file mode 100644 index 00000000000..8c12c709219 --- /dev/null +++ b/org.springframework.context/src/test/java/org/springframework/ui/format/date/DateFormatterTests.java @@ -0,0 +1,36 @@ +package org.springframework.ui.format.date; + +import static org.junit.Assert.assertEquals; + +import java.text.ParseException; +import java.util.Calendar; +import java.util.Locale; + +import org.junit.Test; +import org.springframework.ui.format.date.DateFormatter; + +public class DateFormatterTests { + + private DateFormatter formatter = new DateFormatter(); + + @Test + public void formatValue() { + Calendar cal = Calendar.getInstance(Locale.US); + cal.clear(); + cal.set(Calendar.YEAR, 2009); + cal.set(Calendar.MONTH, Calendar.JUNE); + cal.set(Calendar.DAY_OF_MONTH, 1); + assertEquals("2009-06-01", formatter.format(cal.getTime(), Locale.US)); + } + + @Test + public void parseValue() throws ParseException { + Calendar cal = Calendar.getInstance(Locale.US); + cal.clear(); + cal.set(Calendar.YEAR, 2009); + cal.set(Calendar.MONTH, Calendar.JUNE); + cal.set(Calendar.DAY_OF_MONTH, 1); + assertEquals(cal.getTime(), formatter.parse("2009-06-01", Locale.US)); + } + +} diff --git a/org.springframework.context/src/test/java/org/springframework/ui/format/number/CurrencyFormatterTests.java b/org.springframework.context/src/test/java/org/springframework/ui/format/number/CurrencyFormatterTests.java new file mode 100644 index 00000000000..9eb3c26ba5f --- /dev/null +++ b/org.springframework.context/src/test/java/org/springframework/ui/format/number/CurrencyFormatterTests.java @@ -0,0 +1,51 @@ +package org.springframework.ui.format.number; + +import static org.junit.Assert.assertEquals; + +import java.math.BigDecimal; +import java.text.ParseException; +import java.util.Locale; + +import org.junit.Test; +import org.springframework.ui.format.number.CurrencyFormatter; + +public class CurrencyFormatterTests { + + private CurrencyFormatter formatter = new CurrencyFormatter(); + + @Test + public void formatValue() { + assertEquals("$23.00", formatter.format(new BigDecimal("23"), Locale.US)); + } + + @Test + public void parseValue() throws ParseException { + assertEquals(new BigDecimal("23.56"), formatter.parse("$23.56", Locale.US)); + } + + @Test + public void parseEmptyValue() throws ParseException { + assertEquals(null, formatter.parse("", Locale.US)); + } + + @Test(expected = ParseException.class) + public void parseBogusValue() throws ParseException { + formatter.parse("bogus", Locale.US); + } + + @Test + public void parseValueDefaultRoundDown() throws ParseException { + assertEquals(new BigDecimal("23.56"), formatter.parse("$23.567", Locale.US)); + } + + @Test + public void parseWholeValue() throws ParseException { + assertEquals(new BigDecimal("23.00"), formatter.parse("$23", Locale.US)); + } + + @Test(expected=ParseException.class) + public void parseValueNotLenientFailure() throws ParseException { + formatter.parse("$23.56bogus", Locale.US); + } + +} diff --git a/org.springframework.context/src/test/java/org/springframework/ui/format/number/DecimalFormatterTests.java b/org.springframework.context/src/test/java/org/springframework/ui/format/number/DecimalFormatterTests.java new file mode 100644 index 00000000000..2842d6ba2ad --- /dev/null +++ b/org.springframework.context/src/test/java/org/springframework/ui/format/number/DecimalFormatterTests.java @@ -0,0 +1,41 @@ +package org.springframework.ui.format.number; + +import static org.junit.Assert.assertEquals; + +import java.math.BigDecimal; +import java.text.ParseException; +import java.util.Locale; + +import org.junit.Test; +import org.springframework.ui.format.number.DecimalFormatter; + +public class DecimalFormatterTests { + + private DecimalFormatter formatter = new DecimalFormatter(); + + @Test + public void formatValue() { + assertEquals("23.56", formatter.format(new BigDecimal("23.56"), Locale.US)); + } + + @Test + public void parseValue() throws ParseException { + assertEquals(new BigDecimal("23.56"), formatter.parse("23.56", Locale.US)); + } + + @Test + public void parseEmptyValue() throws ParseException { + assertEquals(null, formatter.parse("", Locale.US)); + } + + @Test(expected = ParseException.class) + public void parseBogusValue() throws ParseException { + formatter.parse("bogus", Locale.US); + } + + @Test(expected = ParseException.class) + public void parsePercentValueNotLenientFailure() throws ParseException { + formatter.parse("23.56bogus", Locale.US); + } + +} diff --git a/org.springframework.context/src/test/java/org/springframework/ui/format/number/IntegerFormatterTests.java b/org.springframework.context/src/test/java/org/springframework/ui/format/number/IntegerFormatterTests.java new file mode 100644 index 00000000000..60934f88cbe --- /dev/null +++ b/org.springframework.context/src/test/java/org/springframework/ui/format/number/IntegerFormatterTests.java @@ -0,0 +1,40 @@ +package org.springframework.ui.format.number; + +import static org.junit.Assert.assertEquals; + +import java.text.ParseException; +import java.util.Locale; + +import org.junit.Test; +import org.springframework.ui.format.number.IntegerFormatter; + +public class IntegerFormatterTests { + + private IntegerFormatter formatter = new IntegerFormatter(); + + @Test + public void formatValue() { + assertEquals("23", formatter.format(23L, Locale.US)); + } + + @Test + public void parseValue() throws ParseException { + assertEquals((Long) 2356L, formatter.parse("2356", Locale.US)); + } + + @Test + public void parseEmptyValue() throws ParseException { + assertEquals(null, formatter.parse("", Locale.US)); + } + + @Test(expected = ParseException.class) + public void parseBogusValue() throws ParseException { + formatter.parse("bogus", Locale.US); + } + + @Test(expected = ParseException.class) + public void parsePercentValueNotLenientFailure() throws ParseException { + formatter.parse("23.56", Locale.US); + } + +} diff --git a/org.springframework.context/src/test/java/org/springframework/ui/format/number/PercentFormatterTests.java b/org.springframework.context/src/test/java/org/springframework/ui/format/number/PercentFormatterTests.java new file mode 100644 index 00000000000..025767affe8 --- /dev/null +++ b/org.springframework.context/src/test/java/org/springframework/ui/format/number/PercentFormatterTests.java @@ -0,0 +1,42 @@ +package org.springframework.ui.format.number; + +import static org.junit.Assert.assertEquals; + +import java.math.BigDecimal; +import java.text.ParseException; +import java.util.Locale; + +import org.junit.Test; +import org.springframework.ui.format.number.PercentFormatter; + +public class PercentFormatterTests { + + private PercentFormatter formatter = new PercentFormatter(); + + @Test + public void formatValue() { + assertEquals("23%", formatter.format(new BigDecimal(".23"), Locale.US)); + } + + @Test + public void parseValue() throws ParseException { + assertEquals(new BigDecimal(".2356"), formatter.parse("23.56%", + Locale.US)); + } + + @Test + public void parseEmptyValue() throws ParseException { + assertEquals(null, formatter.parse("", Locale.US)); + } + + @Test(expected = ParseException.class) + public void parseBogusValue() throws ParseException { + formatter.parse("bogus", Locale.US); + } + + @Test(expected = ParseException.class) + public void parsePercentValueNotLenientFailure() throws ParseException { + formatter.parse("23.56%bogus", Locale.US); + } + +}