Browse Source

SPR-6012, SPR-6013, SPR-6014 initial commit

conversation
Keith Donald 16 years ago
parent
commit
2381452e9a
  1. 37
      org.springframework.context/src/main/java/org/springframework/ui/format/AnnotationFormatterFactory.java
  2. 38
      org.springframework.context/src/main/java/org/springframework/ui/format/Formatted.java
  3. 45
      org.springframework.context/src/main/java/org/springframework/ui/format/Formatter.java
  4. 61
      org.springframework.context/src/main/java/org/springframework/ui/format/FormatterRegistry.java
  5. 145
      org.springframework.context/src/main/java/org/springframework/ui/format/GenericFormatterRegistry.java
  6. 90
      org.springframework.context/src/main/java/org/springframework/ui/format/date/DateFormatter.java
  7. 5
      org.springframework.context/src/main/java/org/springframework/ui/format/date/package-info.java
  8. 34
      org.springframework.context/src/main/java/org/springframework/ui/format/number/CurrencyFormat.java
  9. 71
      org.springframework.context/src/main/java/org/springframework/ui/format/number/CurrencyFormatter.java
  10. 39
      org.springframework.context/src/main/java/org/springframework/ui/format/number/CurrencyNumberFormatFactory.java
  11. 81
      org.springframework.context/src/main/java/org/springframework/ui/format/number/DecimalFormatter.java
  12. 80
      org.springframework.context/src/main/java/org/springframework/ui/format/number/DefaultNumberFormatFactory.java
  13. 65
      org.springframework.context/src/main/java/org/springframework/ui/format/number/IntegerFormatter.java
  14. 31
      org.springframework.context/src/main/java/org/springframework/ui/format/number/IntegerNumberFormatFactory.java
  15. 36
      org.springframework.context/src/main/java/org/springframework/ui/format/number/NumberFormatFactory.java
  16. 67
      org.springframework.context/src/main/java/org/springframework/ui/format/number/PercentFormatter.java
  17. 34
      org.springframework.context/src/main/java/org/springframework/ui/format/number/PercentNumberFormatFactory.java
  18. 5
      org.springframework.context/src/main/java/org/springframework/ui/format/number/package-info.java
  19. 5
      org.springframework.context/src/main/java/org/springframework/ui/format/package-info.java
  20. 48
      org.springframework.context/src/test/java/org/springframework/ui/format/GenericFormatterRegistryTests.java
  21. 36
      org.springframework.context/src/test/java/org/springframework/ui/format/date/DateFormatterTests.java
  22. 51
      org.springframework.context/src/test/java/org/springframework/ui/format/number/CurrencyFormatterTests.java
  23. 41
      org.springframework.context/src/test/java/org/springframework/ui/format/number/DecimalFormatterTests.java
  24. 40
      org.springframework.context/src/test/java/org/springframework/ui/format/number/IntegerFormatterTests.java
  25. 42
      org.springframework.context/src/test/java/org/springframework/ui/format/number/PercentFormatterTests.java

37
org.springframework.context/src/main/java/org/springframework/ui/format/AnnotationFormatterFactory.java

@ -0,0 +1,37 @@ @@ -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 <code>CurrencyAnnotationFormatterFactory</code> might create a <code>Formatter</code> that formats a <code>BigDecimal</code> value set on a property annotated with <code>@CurrencyFormat</code>.
* @author Keith Donald
* @since 3.0
* @param <A> The type of Annotation this factory uses to create Formatter instances
* @param <T> The type of Object Formatters created by this factory format
*/
public interface AnnotationFormatterFactory<A extends Annotation, T> {
/**
* 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<T> getFormatter(A annotation);
}

38
org.springframework.context/src/main/java/org/springframework/ui/format/Formatted.java

@ -0,0 +1,38 @@ @@ -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();
}

45
org.springframework.context/src/main/java/org/springframework/ui/format/Formatter.java

@ -0,0 +1,45 @@ @@ -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 <T> the type of object this formatter can format
*/
public interface Formatter<T> {
/**
* 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;
}

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

@ -0,0 +1,61 @@ @@ -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 <T>.
* Calling getFormatter(&lt;T&gt;.class) returns <code>formatter</code>.
* @param formatter the formatter
* @param <T> the type of object the formatter formats
*/
<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.
* 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 targetFormatter the target formatter
* @param <T> the type of object the target formatter formats
*/
<T> void add(Class<?> objectType, Formatter<T> targetFormatter);
/**
* Adds a AnnotationFormatterFactory that will format values of properties annotated with a specific annotation.
* @param factory the annotation formatter factory
*/
<A extends Annotation, T> void add(AnnotationFormatterFactory<A, T> factory);
/**
* Get the Formatter for the type.
* @return the Formatter, or <code>null</code> if none is registered
*/
Formatter getFormatter(TypeDescriptor type);
}

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

@ -0,0 +1,145 @@ @@ -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<Class, Formatter> typeFormatters = new ConcurrentHashMap<Class, Formatter>();
private Map<Class, AnnotationFormatterFactory> annotationFormatters = new HashMap<Class, AnnotationFormatterFactory>();
public <T> void add(Formatter<T> formatter) {
// TODO
}
public <T> void add(Class<?> objectType, Formatter<T> formatter) {
if (objectType.isAnnotation()) {
annotationFormatters.put(objectType, new SimpleAnnotationFormatterFactory(formatter));
} else {
typeFormatters.put(objectType, formatter);
}
}
public <A extends Annotation, T> void add(AnnotationFormatterFactory<A, T> 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 <A> 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;
}
}
}

90
org.springframework.context/src/main/java/org/springframework/ui/format/date/DateFormatter.java

@ -0,0 +1,90 @@ @@ -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<Date> {
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;
}
}

5
org.springframework.context/src/main/java/org/springframework/ui/format/date/package-info.java

@ -0,0 +1,5 @@ @@ -0,0 +1,5 @@
/**
* Formatters for <code>java.util.Date</code> fields.
*/
package org.springframework.ui.format.date;

34
org.springframework.context/src/main/java/org/springframework/ui/format/number/CurrencyFormat.java

@ -0,0 +1,34 @@ @@ -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 {
}

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

@ -0,0 +1,71 @@ @@ -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<BigDecimal> {
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;
}
}

39
org.springframework.context/src/main/java/org/springframework/ui/format/number/CurrencyNumberFormatFactory.java

@ -0,0 +1,39 @@ @@ -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;
}
}

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

@ -0,0 +1,81 @@ @@ -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<BigDecimal> {
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);
}
}

80
org.springframework.context/src/main/java/org/springframework/ui/format/number/DefaultNumberFormatFactory.java

@ -0,0 +1,80 @@ @@ -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;
}
}

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

@ -0,0 +1,65 @@ @@ -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<Long> {
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;
}
}

31
org.springframework.context/src/main/java/org/springframework/ui/format/number/IntegerNumberFormatFactory.java

@ -0,0 +1,31 @@ @@ -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);
}
}

36
org.springframework.context/src/main/java/org/springframework/ui/format/number/NumberFormatFactory.java

@ -0,0 +1,36 @@ @@ -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);
}

67
org.springframework.context/src/main/java/org/springframework/ui/format/number/PercentFormatter.java

@ -0,0 +1,67 @@ @@ -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<BigDecimal> {
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;
}
}

34
org.springframework.context/src/main/java/org/springframework/ui/format/number/PercentNumberFormatFactory.java

@ -0,0 +1,34 @@ @@ -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;
}
}

5
org.springframework.context/src/main/java/org/springframework/ui/format/number/package-info.java

@ -0,0 +1,5 @@ @@ -0,0 +1,5 @@
/**
* Formatters for <code>java.lang.Number</code> properties.
*/
package org.springframework.ui.format.number;

5
org.springframework.context/src/main/java/org/springframework/ui/format/package-info.java

@ -0,0 +1,5 @@ @@ -0,0 +1,5 @@
/**
* A SPI for defining Formatters to format field model values for display in a UI.
*/
package org.springframework.ui.format;

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

@ -0,0 +1,48 @@ @@ -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);
}
}

36
org.springframework.context/src/test/java/org/springframework/ui/format/date/DateFormatterTests.java

@ -0,0 +1,36 @@ @@ -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));
}
}

51
org.springframework.context/src/test/java/org/springframework/ui/format/number/CurrencyFormatterTests.java

@ -0,0 +1,51 @@ @@ -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);
}
}

41
org.springframework.context/src/test/java/org/springframework/ui/format/number/DecimalFormatterTests.java

@ -0,0 +1,41 @@ @@ -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);
}
}

40
org.springframework.context/src/test/java/org/springframework/ui/format/number/IntegerFormatterTests.java

@ -0,0 +1,40 @@ @@ -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);
}
}

42
org.springframework.context/src/test/java/org/springframework/ui/format/number/PercentFormatterTests.java

@ -0,0 +1,42 @@ @@ -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);
}
}
Loading…
Cancel
Save