From 5b93b143928941ce155ad0fc471dbab04106c1bc Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Wed, 31 Oct 2012 02:22:09 +0100 Subject: [PATCH] DateTimeFormat annotation supports use as a meta-annotation as well --- .../format/annotation/DateTimeFormat.java | 5 +- .../FormattingConversionServiceTests.java | 44 +++++++++++++++- .../core/convert/TypeDescriptor.java | 50 +++++++++++-------- 3 files changed, 73 insertions(+), 26 deletions(-) diff --git a/spring-context/src/main/java/org/springframework/format/annotation/DateTimeFormat.java b/spring-context/src/main/java/org/springframework/format/annotation/DateTimeFormat.java index 2480cf65dd..8646fa3415 100644 --- a/spring-context/src/main/java/org/springframework/format/annotation/DateTimeFormat.java +++ b/spring-context/src/main/java/org/springframework/format/annotation/DateTimeFormat.java @@ -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 { diff --git a/spring-context/src/test/java/org/springframework/format/support/FormattingConversionServiceTests.java b/spring-context/src/test/java/org/springframework/format/support/FormattingConversionServiceTests.java index eb640a497a..e6e0d82a5e 100644 --- a/spring-context/src/test/java/org/springframework/format/support/FormattingConversionServiceTests.java +++ b/spring-context/src/test/java/org/springframework/format/support/FormattingConversionServiceTests.java @@ -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 dates; public List 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 { public String print(Integer object, Locale locale) { diff --git a/spring-core/src/main/java/org/springframework/core/convert/TypeDescriptor.java b/spring-core/src/main/java/org/springframework/core/convert/TypeDescriptor.java index c4969fdb56..b4236c2c59 100644 --- a/spring-core/src/main/java/org/springframework/core/convert/TypeDescriptor.java +++ b/spring-core/src/main/java/org/springframework/core/convert/TypeDescriptor.java @@ -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 null if this is {@link TypeDescriptor#NULL} + *

See {@link #getObjectType()} for a variation of this operation that resolves primitive types + * to their corresponding Object types if necessary. + * @return the type, or null * @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. + *

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 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. * 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 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. * @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 null if no such annotation exists on this type descriptor */ @SuppressWarnings("unchecked") public T getAnnotation(Class 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(); }