added convention-based default valueOf formatter

This commit is contained in:
Keith Donald 2009-10-14 18:57:36 +00:00
parent a696a4fec9
commit 728a1415d9
3 changed files with 235 additions and 122 deletions

View File

@ -46,16 +46,14 @@ public interface FormatterRegistry {
* Adds a Formatter to this registry indexed by <T>. * Adds a Formatter to this registry indexed by <T>.
* <o>Calling <code>getFormatter(&lt;T&gt;.class)</code> returns <code>formatter</code>. * <o>Calling <code>getFormatter(&lt;T&gt;.class)</code> returns <code>formatter</code>.
* @param formatter the formatter * @param formatter the formatter
* @param <T> the type of object the formatter formats
*/ */
<T> void addFormatterByType(Formatter<T> formatter); void addFormatterByType(Formatter<?> formatter);
/** /**
* Adds a Formatter to this registry indexed by the given annotation type. * Adds a Formatter to this registry indexed by the given annotation type.
* <o>Calling <code>getFormatter(...)</code> on a field or accessor method * <o>Calling <code>getFormatter(...)</code> on a field or accessor method
* with the given annotation returns <code>formatter</code>. * with the given annotation returns <code>formatter</code>.
* @param formatter the formatter * @param formatter the formatter
* @param <T> the type of object the formatter formats
*/ */
void addFormatterByAnnotation(Class<? extends Annotation> annotationType, Formatter<?> formatter); void addFormatterByAnnotation(Class<? extends Annotation> annotationType, Formatter<?> formatter);
@ -64,16 +62,8 @@ public interface FormatterRegistry {
* <o>Calling <code>getFormatter(...)</code> on a field or accessor method * <o>Calling <code>getFormatter(...)</code> on a field or accessor method
* with the given annotation returns <code>formatter</code>. * with the given annotation returns <code>formatter</code>.
* @param factory the annotation formatter factory * @param factory the annotation formatter factory
* @param <A> the type of Annotation this factory uses to create Formatter instances
* @param <T> the type of object that the factory's Formatters are dealing with
*/ */
<A extends Annotation, T> void addFormatterByAnnotation(AnnotationFormatterFactory<A, T> factory); void addFormatterByAnnotation(AnnotationFormatterFactory<?, ?> factory);
/**
* Get the Formatter for the specified type.
* @return the Formatter, or <code>null</code> if no suitable one is registered
*/
<T> Formatter<T> getFormatter(Class<T> targetType);
/** /**
* Get the Formatter for the type descriptor. * Get the Formatter for the type descriptor.

View File

@ -17,6 +17,8 @@
package org.springframework.ui.format.support; package org.springframework.ui.format.support;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.text.ParseException; import java.text.ParseException;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.Locale; import java.util.Locale;
@ -38,6 +40,7 @@ import org.springframework.ui.format.Formatted;
import org.springframework.ui.format.Formatter; import org.springframework.ui.format.Formatter;
import org.springframework.ui.format.FormatterRegistry; import org.springframework.ui.format.FormatterRegistry;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
/** /**
* A generic implementation of {@link org.springframework.ui.format.FormatterRegistry} * A generic implementation of {@link org.springframework.ui.format.FormatterRegistry}
@ -46,25 +49,28 @@ import org.springframework.util.Assert;
* @author Keith Donald * @author Keith Donald
* @author Juergen Hoeller * @author Juergen Hoeller
* @since 3.0 * @since 3.0
* @see #setFormatters(Set)
* @see #setFormatterMap(Map)
* @see #setAnnotationFormatterMap(Map)
* @see #setAnnotationFormatterFactories(Set)
* @see #setConversionService(ConversionService) * @see #setConversionService(ConversionService)
* @see #add(org.springframework.ui.format.Formatter) * @see #addFormatterByType(Formatter)
* @see #add(Class, org.springframework.ui.format.Formatter) * @see #addFormatterByType(Class, Formatter)
* @see #add(org.springframework.ui.format.AnnotationFormatterFactory) * @see #addFormatterByAnnotation(Class, Formatter)
* @see #addFormatterByAnnotation(AnnotationFormatterFactory)
*/ */
public class GenericFormatterRegistry implements FormatterRegistry, ApplicationContextAware, Cloneable { public class GenericFormatterRegistry implements FormatterRegistry, ApplicationContextAware, Cloneable {
private final Map<Class, Formatter> typeFormatters = new ConcurrentHashMap<Class, Formatter>(); private final Map<Class, FormatterHolder> typeFormatters = new ConcurrentHashMap<Class, FormatterHolder>();
private final Map<Class, AnnotationFormatterFactory> annotationFormatters = private final Map<Class, AnnotationFormatterFactoryHolder> annotationFormatters = new ConcurrentHashMap<Class, AnnotationFormatterFactoryHolder>();
new ConcurrentHashMap<Class, AnnotationFormatterFactory>();
private ConversionService conversionService = new DefaultConversionService(); private ConversionService conversionService;
private ApplicationContext applicationContext; private ApplicationContext applicationContext;
private boolean shared = true; private boolean shared = true;
/** /**
* Registers the formatters in the set provided. * Registers the formatters in the set provided.
* JavaBean-friendly alternative to calling {@link #addFormatterByType(Formatter)}. * JavaBean-friendly alternative to calling {@link #addFormatterByType(Formatter)}.
@ -130,15 +136,14 @@ public class GenericFormatterRegistry implements FormatterRegistry, ApplicationC
* Take the context's default ConversionService if none specified locally. * Take the context's default ConversionService if none specified locally.
*/ */
public void setApplicationContext(ApplicationContext context) { public void setApplicationContext(ApplicationContext context) {
if (this.conversionService == null && if (this.conversionService == null
context.containsBean(ConfigurableApplicationContext.CONVERSION_SERVICE_BEAN_NAME)) { && context.containsBean(ConfigurableApplicationContext.CONVERSION_SERVICE_BEAN_NAME)) {
this.conversionService = context.getBean( this.conversionService = context.getBean(ConfigurableApplicationContext.CONVERSION_SERVICE_BEAN_NAME,
ConfigurableApplicationContext.CONVERSION_SERVICE_BEAN_NAME, ConversionService.class); ConversionService.class);
} }
this.applicationContext = context; this.applicationContext = context;
} }
// cloning support // cloning support
/** /**
@ -175,98 +180,120 @@ public class GenericFormatterRegistry implements FormatterRegistry, ApplicationC
return clone; return clone;
} }
// implementing FormatterRegistry // implementing FormatterRegistry
public void addFormatterByType(Class<?> type, Formatter<?> formatter) { public void addFormatterByType(Class<?> type, Formatter<?> formatter) {
Class<?> formattedObjectType = GenericTypeResolver.resolveTypeArgument(formatter.getClass(), Formatter.class); Class<?> formattedObjectType = GenericTypeResolver.resolveTypeArgument(formatter.getClass(), Formatter.class);
if (formattedObjectType != null && !type.isAssignableFrom(formattedObjectType)) {
if (this.conversionService == null) {
throw new IllegalStateException("Unable to index Formatter " + formatter + " under type ["
+ type.getName() + "]; unable to convert from [" + formattedObjectType.getName()
+ "] parsed by Formatter because this.conversionService is null");
}
if (!this.conversionService.canConvert(formattedObjectType, type)) { if (!this.conversionService.canConvert(formattedObjectType, type)) {
throw new IllegalArgumentException("Unable to register Formatter " + formatter + " for type [" + throw new IllegalArgumentException("Unable to index Formatter " + formatter + " under type ["
type.getName() + "]; not able to convert from [" + formattedObjectType.getName() + "] to parse"); + type.getName() + "]; not able to convert from [" + formattedObjectType.getName()
+ "] to parse");
} }
if (!this.conversionService.canConvert(type, formattedObjectType)) { if (!this.conversionService.canConvert(type, formattedObjectType)) {
throw new IllegalArgumentException("Unable to register Formatter " + formatter + " for type [" + throw new IllegalArgumentException("Unable to index Formatter " + formatter + " under type ["
type.getName() + "]; not able to convert to [" + formattedObjectType.getName() + "] to format"); + type.getName() + "]; not able to convert to [" + formattedObjectType.getName()
+ "] to format");
} }
this.typeFormatters.put(type, formatter); }
this.typeFormatters.put(type, new FormatterHolder(formattedObjectType, formatter));
} }
public <T> void addFormatterByType(Formatter<T> formatter) { public void addFormatterByType(Formatter<?> formatter) {
Class<?> formattedObjectType = GenericTypeResolver.resolveTypeArgument(formatter.getClass(), Formatter.class); Class<?> formattedObjectType = GenericTypeResolver.resolveTypeArgument(formatter.getClass(), Formatter.class);
this.typeFormatters.put(formattedObjectType, formatter); if (formattedObjectType == null) {
throw new IllegalArgumentException("Unable to register Formatter " + formatter
+ "; cannot determine parameterized object type <T>");
}
this.typeFormatters.put(formattedObjectType, new FormatterHolder(formattedObjectType, formatter));
} }
public void addFormatterByAnnotation(Class<? extends Annotation> annotationType, Formatter<?> formatter) { public void addFormatterByAnnotation(Class<? extends Annotation> annotationType, Formatter<?> formatter) {
this.annotationFormatters.put(annotationType, new SimpleAnnotationFormatterFactory(formatter)); Class<?> formattedObjectType = GenericTypeResolver.resolveTypeArgument(formatter.getClass(), Formatter.class);
SimpleAnnotationFormatterFactory factory = new SimpleAnnotationFormatterFactory(formatter);
this.annotationFormatters.put(annotationType,
new AnnotationFormatterFactoryHolder(formattedObjectType, factory));
} }
public <A extends Annotation, T> void addFormatterByAnnotation(AnnotationFormatterFactory<A, T> factory) { public void addFormatterByAnnotation(AnnotationFormatterFactory<?, ?> factory) {
Class[] typeArgs = GenericTypeResolver.resolveTypeArguments(factory.getClass(), AnnotationFormatterFactory.class); Class[] typeArgs = GenericTypeResolver.resolveTypeArguments(factory.getClass(),
if (typeArgs == null) { AnnotationFormatterFactory.class);
if (typeArgs == null || typeArgs.length != 2) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
"Unable to extract Annotation type A argument from AnnotationFormatterFactory [" + "Unable to extract parameterized type arguments from AnnotationFormatterFactory ["
factory.getClass().getName() + "]; does the factory parameterize the <A> generic type?"); + factory.getClass().getName()
+ "]; does the factory parameterize the <A> and <T> generic types?");
} }
this.annotationFormatters.put(typeArgs[0], factory); this.annotationFormatters.put(typeArgs[0], new AnnotationFormatterFactoryHolder(typeArgs[1], factory));
} }
@SuppressWarnings("unchecked")
public <T> Formatter<T> getFormatter(Class<T> targetType) {
return (Formatter<T>) getFormatter(TypeDescriptor.valueOf(targetType));
}
@SuppressWarnings("unchecked")
public Formatter<Object> getFormatter(TypeDescriptor type) { public Formatter<Object> getFormatter(TypeDescriptor type) {
Assert.notNull(type, "TypeDescriptor is required"); Assert.notNull(type, "TypeDescriptor is required");
Formatter<Object> formatter = getAnnotationFormatter(type); FormatterHolder holder = findFormatterHolderForAnnotatedProperty(type.getAnnotations());
if (formatter == null) { if (holder == null) {
formatter = getTypeFormatter(type.getType()); holder = findFormatterHolderForType(type.getType());
} }
if (formatter != null) { if (holder == null) {
Class<?> formattedObjectType = GenericTypeResolver.resolveTypeArgument(formatter.getClass(), Formatter.class); holder = getDefaultFormatterHolder(type);
if (!type.getType().isAssignableFrom(formattedObjectType)) { }
return new ConvertingFormatter(type.getType(), formattedObjectType, formatter); if (holder == null) {
return null;
}
Class formattedObjectType = holder.getFormattedObjectType();
if (formattedObjectType != null && !type.getType().isAssignableFrom(formattedObjectType)) {
if (this.conversionService != null) {
return new ConvertingFormatter(type, holder);
} else {
return null;
}
} else {
return holder.getFormatter();
} }
} }
return formatter;
}
// internal helpers // internal helpers
@SuppressWarnings("unchecked") private FormatterHolder findFormatterHolderForAnnotatedProperty(Annotation[] annotations) {
private Formatter getAnnotationFormatter(TypeDescriptor type) { for (Annotation annotation : annotations) {
Annotation[] annotations = type.getAnnotations(); FormatterHolder holder = findFormatterHolderForAnnotation(annotation);
for (Annotation ann : annotations) { if (holder != null) {
AnnotationFormatterFactory factory = this.annotationFormatters.get(ann.annotationType()); return holder;
if (factory != null) {
return factory.getFormatter(ann);
}
else {
Formatted formattedAnnotation = ann.annotationType().getAnnotation(Formatted.class);
if (formattedAnnotation != null) {
Formatter formatter = createFormatter(formattedAnnotation.value());
this.annotationFormatters.put(ann.annotationType(), new SimpleAnnotationFormatterFactory(formatter));
return formatter;
}
} }
} }
return null; return null;
} }
private Formatter getTypeFormatter(Class<?> type) { private FormatterHolder findFormatterHolderForAnnotation(Annotation annotation) {
Formatter formatter = findFormatter(type); Class<? extends Annotation> annotationType = annotation.annotationType();
return (formatter != null ? formatter : getDefaultFormatter(type)); AnnotationFormatterFactoryHolder factory = this.annotationFormatters.get(annotationType);
if (factory != null) {
return factory.getFormatterHolder(annotation);
} else {
Formatted formattedAnnotation = annotationType.getAnnotation(Formatted.class);
if (formattedAnnotation != null) {
// annotation has @Formatted meta-annotation
Formatter formatter = createFormatter(formattedAnnotation.value());
addFormatterByAnnotation(annotationType, formatter);
return findFormatterHolderForAnnotation(annotation);
} else {
return null;
}
}
} }
private Formatter<?> findFormatter(Class<?> type) { private FormatterHolder findFormatterHolderForType(Class type) {
LinkedList<Class> classQueue = new LinkedList<Class>(); LinkedList<Class> classQueue = new LinkedList<Class>();
classQueue.addFirst(type); classQueue.addFirst(type);
while (!classQueue.isEmpty()) { while (!classQueue.isEmpty()) {
Class currentClass = classQueue.removeLast(); Class currentClass = classQueue.removeLast();
Formatter<?> formatter = this.typeFormatters.get(currentClass); FormatterHolder holder = this.typeFormatters.get(currentClass);
if (formatter != null) { if (holder != null) {
return formatter; return holder;
} }
if (currentClass.getSuperclass() != null) { if (currentClass.getSuperclass() != null) {
classQueue.addFirst(currentClass.getSuperclass()); classQueue.addFirst(currentClass.getSuperclass());
@ -279,52 +306,98 @@ public class GenericFormatterRegistry implements FormatterRegistry, ApplicationC
return null; return null;
} }
private Formatter<?> getDefaultFormatter(Class<?> type) { private FormatterHolder getDefaultFormatterHolder(TypeDescriptor typeDescriptor) {
Class type = typeDescriptor.getType();
Formatted formatted = AnnotationUtils.findAnnotation(type, Formatted.class); Formatted formatted = AnnotationUtils.findAnnotation(type, Formatted.class);
if (formatted != null) { if (formatted != null) {
Formatter formatter = createFormatter(formatted.value()); Formatter formatter = createFormatter(formatted.value());
this.typeFormatters.put(type, formatter); addFormatterByType(type, formatter);
return formatter; return findFormatterHolderForType(type);
} } else {
else { Method valueOfMethod = getValueOfMethod(type);
if (valueOfMethod != null) {
Formatter formatter = createFormatter(valueOfMethod);
addFormatterByType(type, formatter);
return findFormatterHolderForType(type);
} else {
return null; return null;
} }
} }
private Formatter<?> createFormatter(Class<? extends Formatter> formatterClass) {
return (this.applicationContext != null ?
this.applicationContext.getAutowireCapableBeanFactory().createBean(formatterClass) :
BeanUtils.instantiate(formatterClass));
} }
private Formatter createFormatter(Class<? extends Formatter> formatterClass) {
return (this.applicationContext != null ? this.applicationContext.getAutowireCapableBeanFactory().createBean(
formatterClass) : BeanUtils.instantiate(formatterClass));
}
private class ConvertingFormatter implements Formatter { private Method getValueOfMethod(Class type) {
Method[] methods = type.getDeclaredMethods();
for (int i = 0; i < methods.length; i++) {
Method method = methods[i];
if ("valueOf".equals(method.getName()) && acceptsSingleStringParameterType(method)
&& Modifier.isPublic(method.getModifiers()) && Modifier.isStatic(method.getModifiers())) {
return method;
}
}
return null;
}
private final Class<?> type; private boolean acceptsSingleStringParameterType(Method method) {
Class[] paramTypes = method.getParameterTypes();
if (paramTypes == null) {
return false;
} else {
return paramTypes.length == 1 && paramTypes[0] == String.class;
}
}
private final Class<?> formattedObjectType; private Formatter createFormatter(Method valueOfMethod) {
return new ValueOfMethodFormatter(valueOfMethod);
}
private final Formatter targetFormatter; private abstract static class AbstractFormatterHolder {
public ConvertingFormatter(Class<?> type, Class<?> formattedObjectType, Formatter targetFormatter) { private Class formattedObjectType;
this.type = type;
public AbstractFormatterHolder(Class formattedObjectType) {
this.formattedObjectType = formattedObjectType; this.formattedObjectType = formattedObjectType;
this.targetFormatter = targetFormatter;
} }
@SuppressWarnings("unchecked") public Class<?> getFormattedObjectType() {
public String format(Object object, Locale locale) { return formattedObjectType;
object = conversionService.convert(object, this.formattedObjectType);
return this.targetFormatter.format(object, locale);
} }
public Object parse(String formatted, Locale locale) throws ParseException {
Object parsed = this.targetFormatter.parse(formatted, locale);
parsed = conversionService.convert(parsed, this.type);
return parsed;
}
} }
private static class FormatterHolder extends AbstractFormatterHolder {
private Formatter formatter;
public FormatterHolder(Class formattedObjectType, Formatter formatter) {
super(formattedObjectType);
this.formatter = formatter;
}
public Formatter getFormatter() {
return this.formatter;
}
}
private static class AnnotationFormatterFactoryHolder extends AbstractFormatterHolder {
private AnnotationFormatterFactory factory;
public AnnotationFormatterFactoryHolder(Class formattedObjectType, AnnotationFormatterFactory factory) {
super(formattedObjectType);
this.factory = factory;
}
public FormatterHolder getFormatterHolder(Annotation annotation) {
return new FormatterHolder(getFormattedObjectType(), this.factory.getFormatter(annotation));
}
}
private static class SimpleAnnotationFormatterFactory implements AnnotationFormatterFactory { private static class SimpleAnnotationFormatterFactory implements AnnotationFormatterFactory {
@ -339,4 +412,51 @@ public class GenericFormatterRegistry implements FormatterRegistry, ApplicationC
} }
} }
private static class ValueOfMethodFormatter implements Formatter {
private Method valueOfMethod;
public ValueOfMethodFormatter(Method valueOfMethod) {
this.valueOfMethod = valueOfMethod;
}
public String format(Object object, Locale locale) {
if (object == null) {
return "";
} else {
return object.toString();
}
}
public Object parse(String formatted, Locale locale) throws ParseException {
return ReflectionUtils.invokeMethod(valueOfMethod, null, formatted);
}
}
private class ConvertingFormatter implements Formatter {
private final TypeDescriptor type;
private final FormatterHolder formatterHolder;
public ConvertingFormatter(TypeDescriptor type, FormatterHolder formatterHolder) {
this.type = type;
this.formatterHolder = formatterHolder;
}
public String format(Object object, Locale locale) {
object = GenericFormatterRegistry.this.conversionService.convert(object, this.formatterHolder
.getFormattedObjectType());
return this.formatterHolder.getFormatter().format(object, locale);
}
public Object parse(String formatted, Locale locale) throws ParseException {
Object parsed = this.formatterHolder.getFormatter().parse(formatted, locale);
parsed = GenericFormatterRegistry.this.conversionService.convert(parsed, TypeDescriptor
.valueOf(this.formatterHolder.getFormattedObjectType()), this.type);
return parsed;
}
}
} }

View File

@ -16,6 +16,8 @@
package org.springframework.ui.format; package org.springframework.ui.format;
import static org.junit.Assert.assertEquals;
import java.lang.annotation.ElementType; import java.lang.annotation.ElementType;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
@ -25,11 +27,10 @@ import java.math.BigInteger;
import java.text.ParseException; import java.text.ParseException;
import java.util.Locale; import java.util.Locale;
import static org.junit.Assert.*;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.style.ToStringCreator; import org.springframework.core.style.ToStringCreator;
import org.springframework.ui.format.number.CurrencyFormatter; import org.springframework.ui.format.number.CurrencyFormatter;
import org.springframework.ui.format.number.IntegerFormatter; import org.springframework.ui.format.number.IntegerFormatter;
@ -46,12 +47,13 @@ public class GenericFormatterRegistryTests {
@Before @Before
public void setUp() { public void setUp() {
registry = new GenericFormatterRegistry(); registry = new GenericFormatterRegistry();
registry.setConversionService(new DefaultConversionService());
} }
@Test @Test
public void testAdd() throws ParseException { public void testAdd() throws ParseException {
registry.addFormatterByType(new IntegerFormatter()); registry.addFormatterByType(new IntegerFormatter());
Formatter formatter = registry.getFormatter(Integer.class); Formatter formatter = registry.getFormatter(TypeDescriptor.valueOf(Integer.class));
String formatted = formatter.format(new Integer(3), Locale.US); String formatted = formatter.format(new Integer(3), Locale.US);
assertEquals("3", formatted); assertEquals("3", formatted);
Integer i = (Integer) formatter.parse("3", Locale.US); Integer i = (Integer) formatter.parse("3", Locale.US);
@ -61,7 +63,7 @@ public class GenericFormatterRegistryTests {
@Test @Test
public void testAddByObjectType() { public void testAddByObjectType() {
registry.addFormatterByType(BigInteger.class, new IntegerFormatter()); registry.addFormatterByType(BigInteger.class, new IntegerFormatter());
Formatter formatter = registry.getFormatter(BigInteger.class); Formatter formatter = registry.getFormatter(TypeDescriptor.valueOf(BigInteger.class));
String formatted = formatter.format(new BigInteger("3"), Locale.US); String formatted = formatter.format(new BigInteger("3"), Locale.US);
assertEquals("3", formatted); assertEquals("3", formatted);
} }
@ -91,7 +93,7 @@ public class GenericFormatterRegistryTests {
@Test @Test
public void testGetDefaultFormatterForType() { public void testGetDefaultFormatterForType() {
Formatter formatter = registry.getFormatter(Address.class); Formatter formatter = registry.getFormatter(TypeDescriptor.valueOf(Address.class));
Address address = new Address(); Address address = new Address();
address.street = "12345 Bel Aire Estates"; address.street = "12345 Bel Aire Estates";
address.city = "Palm Bay"; address.city = "Palm Bay";
@ -102,28 +104,29 @@ public class GenericFormatterRegistryTests {
} }
@Test @Test
public void testGetNoFormatterForType() { public void testGetDefaultFormatterForTypeValueOfMethod() throws ParseException {
assertNull(registry.getFormatter(Integer.class)); Formatter formatter = registry.getFormatter(TypeDescriptor.valueOf(Integer.class));
assertEquals("3", formatter.format(new Integer(3), null));
assertEquals(new Integer(3), formatter.parse("3", null));
} }
@Test(expected=IllegalArgumentException.class) @Test(expected = IllegalArgumentException.class)
public void testGetFormatterCannotConvert() { public void testGetFormatterCannotConvert() {
registry.addFormatterByType(Integer.class, new AddressFormatter()); registry.addFormatterByType(Integer.class, new AddressFormatter());
} }
@Currency @Currency
public BigDecimal currencyField; public BigDecimal currencyField;
@SmartCurrency @SmartCurrency
public BigDecimal smartCurrencyField; public BigDecimal smartCurrencyField;
@Target({ElementType.METHOD, ElementType.FIELD}) @Target( { ElementType.METHOD, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
public @interface Currency { public @interface Currency {
} }
@Target({ElementType.METHOD, ElementType.FIELD}) @Target( { ElementType.METHOD, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Formatted(CurrencyFormatter.class) @Formatted(CurrencyFormatter.class)
public @interface SmartCurrency { public @interface SmartCurrency {