DateTimeFormat annotation supports use as a meta-annotation as well

This commit is contained in:
Juergen Hoeller 2012-10-31 02:22:09 +01:00 committed by unknown
parent ee50d849a1
commit 5b93b14392
3 changed files with 73 additions and 26 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2009 the original author or authors.
* Copyright 2002-2012 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.
@ -41,10 +41,11 @@ import java.lang.annotation.Target;
* When no annotation attributes are specified, the default format applied is style-based with a style code of 'SS' (short date, short time).
*
* @author Keith Donald
* @author Juergen Hoeller
* @since 3.0
* @see org.joda.time.format.DateTimeFormat
*/
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER})
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface DateTimeFormat {

View File

@ -16,6 +16,8 @@
package org.springframework.format.support;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Date;
@ -106,6 +108,23 @@ public class FormattingConversionServiceTests {
assertEquals(new LocalDate(2009, 10, 31), new LocalDate(valueBean.date));
}
@Test
public void testFormatFieldForValueInjectionUsingMetaAnnotations() {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
ac.registerBeanDefinition("valueBean", new RootBeanDefinition(MetaValueBean.class, false));
ac.registerBeanDefinition("conversionService", new RootBeanDefinition(FormattingConversionServiceFactoryBean.class));
ac.registerBeanDefinition("ppc", new RootBeanDefinition(PropertyPlaceholderConfigurer.class));
ac.refresh();
System.setProperty("myDate", "10-31-09");
try {
MetaValueBean valueBean = ac.getBean(MetaValueBean.class);
assertEquals(new LocalDate(2009, 10, 31), new LocalDate(valueBean.date));
}
finally {
System.clearProperty("myDate");
}
}
@Test
public void testFormatFieldForAnnotation() throws Exception {
formattingService.addFormatterForFieldAnnotation(new JodaDateTimeFormatAnnotationFormatterFactory());
@ -287,6 +306,20 @@ public class FormattingConversionServiceTests {
}
public static class MetaValueBean {
@MyDateAnn
public Date date;
}
@Value("${myDate}")
@org.springframework.format.annotation.DateTimeFormat(pattern="MM-d-yy")
@Retention(RetentionPolicy.RUNTIME)
public static @interface MyDateAnn {
}
public static class Model {
@org.springframework.format.annotation.DateTimeFormat(style="S-")
@ -310,7 +343,7 @@ public class FormattingConversionServiceTests {
@org.springframework.format.annotation.DateTimeFormat(style="${dateStyle}")
public Date date;
@org.springframework.format.annotation.DateTimeFormat(pattern="${datePattern}")
@MyDatePattern
public List<Date> dates;
public List<Date> getDates() {
@ -321,7 +354,14 @@ public class FormattingConversionServiceTests {
this.dates = dates;
}
}
@org.springframework.format.annotation.DateTimeFormat(pattern="${datePattern}")
@Retention(RetentionPolicy.RUNTIME)
public static @interface MyDatePattern {
}
public static class NullReturningFormatter implements Formatter<Integer> {
public String print(Integer object, Locale locale) {

View File

@ -215,8 +215,9 @@ public class TypeDescriptor {
/**
* The type of the backing class, method parameter, field, or property described by this TypeDescriptor.
* Returns primitive types as-is.
* See {@link #getObjectType()} for a variation of this operation that resolves primitive types to their corresponding Object types if necessary.
* @return the type, or <code>null</code> if this is {@link TypeDescriptor#NULL}
* <p>See {@link #getObjectType()} for a variation of this operation that resolves primitive types
* to their corresponding Object types if necessary.
* @return the type, or <code>null</code>
* @see #getObjectType()
*/
public Class<?> getType() {
@ -225,7 +226,8 @@ public class TypeDescriptor {
/**
* Variation of {@link #getType()} that accounts for a primitive type by returning its object wrapper type.
* This is useful for conversion service implementations that wish to normalize to object-based types and not work with primitive types directly.
* <p>This is useful for conversion service implementations that wish to normalize to object-based types
* and not work with primitive types directly.
*/
public Class<?> getObjectType() {
return ClassUtils.resolvePrimitiveIfNecessary(getType());
@ -233,12 +235,12 @@ public class TypeDescriptor {
/**
* Narrows this {@link TypeDescriptor} by setting its type to the class of the provided value.
* If the value is null, no narrowing is performed and this TypeDescriptor is returned unchanged.
* Designed to be called by binding frameworks when they read property, field, or method return values.
* If the value is <code>null</code>, no narrowing is performed and this TypeDescriptor is returned unchanged.
* <p>Designed to be called by binding frameworks when they read property, field, or method return values.
* Allows such frameworks to narrow a TypeDescriptor built from a declared property, field, or method return value type.
* For example, a field declared as java.lang.Object would be narrowed to java.util.HashMap if it was set to a java.util.HashMap value.
* The narrowed TypeDescriptor can then be used to convert the HashMap to some other type.
* Annotation and nested type context is preserved by the narrowed copy.
* For example, a field declared as <code>java.lang.Object</code> would be narrowed to <code>java.util.HashMap</code>
* if it was set to a <code>java.util.HashMap</code> value. The narrowed TypeDescriptor can then be used to convert
* the HashMap to some other type. Annotation and nested type context is preserved by the narrowed copy.
* @param value the value to use for narrowing this type descriptor
* @return this TypeDescriptor narrowed (returns a copy with its type updated to the class of the provided value)
*/
@ -302,15 +304,21 @@ public class TypeDescriptor {
/**
* Obtain the annotation associated with this type descriptor of the specified type.
* @param annotationType the annotation type
* @return the annotation, or null if no such annotation exists on this type descriptor
* @return the annotation, or <code>null</code> if no such annotation exists on this type descriptor
*/
@SuppressWarnings("unchecked")
public <T extends Annotation> T getAnnotation(Class<T> annotationType) {
for (Annotation annotation : getAnnotations()) {
for (Annotation annotation : this.annotations) {
if (annotation.annotationType().equals(annotationType)) {
return (T) annotation;
}
}
for (Annotation metaAnn : this.annotations) {
T ann = metaAnn.annotationType().getAnnotation(annotationType);
if (ann != null) {
return ann;
}
}
return null;
}
@ -573,24 +581,23 @@ public class TypeDescriptor {
return false;
}
TypeDescriptor other = (TypeDescriptor) obj;
if (!ObjectUtils.nullSafeEquals(getType(), other.getType())) {
if (!ObjectUtils.nullSafeEquals(this.type, other.type)) {
return false;
}
Annotation[] annotations = getAnnotations();
if (annotations.length != other.getAnnotations().length) {
if (this.annotations.length != other.annotations.length) {
return false;
}
for (Annotation ann : annotations) {
for (Annotation ann : this.annotations) {
if (other.getAnnotation(ann.annotationType()) == null) {
return false;
}
}
if (isCollection() || isArray()) {
return ObjectUtils.nullSafeEquals(getElementTypeDescriptor(), other.getElementTypeDescriptor());
return ObjectUtils.nullSafeEquals(this.elementTypeDescriptor, other.elementTypeDescriptor);
}
else if (isMap()) {
return ObjectUtils.nullSafeEquals(getMapKeyTypeDescriptor(), other.getMapKeyTypeDescriptor()) &&
ObjectUtils.nullSafeEquals(getMapValueTypeDescriptor(), other.getMapValueTypeDescriptor());
return ObjectUtils.nullSafeEquals(this.mapKeyTypeDescriptor, other.mapKeyTypeDescriptor) &&
ObjectUtils.nullSafeEquals(this.mapValueTypeDescriptor, other.mapValueTypeDescriptor);
}
else {
return true;
@ -603,17 +610,16 @@ public class TypeDescriptor {
public String toString() {
StringBuilder builder = new StringBuilder();
Annotation[] anns = getAnnotations();
for (Annotation ann : anns) {
for (Annotation ann : this.annotations) {
builder.append("@").append(ann.annotationType().getName()).append(' ');
}
builder.append(ClassUtils.getQualifiedName(getType()));
if (isMap()) {
builder.append("<").append(wildcard(getMapKeyTypeDescriptor()));
builder.append(", ").append(wildcard(getMapValueTypeDescriptor())).append(">");
builder.append("<").append(wildcard(this.mapKeyTypeDescriptor));
builder.append(", ").append(wildcard(this.mapValueTypeDescriptor)).append(">");
}
else if (isCollection()) {
builder.append("<").append(wildcard(getElementTypeDescriptor())).append(">");
builder.append("<").append(wildcard(this.elementTypeDescriptor)).append(">");
}
return builder.toString();
}