Skip to content

Commit 5b93b14

Browse files
jhoellerunknown
authored andcommitted
DateTimeFormat annotation supports use as a meta-annotation as well
1 parent ee50d84 commit 5b93b14

File tree

3 files changed

+73
-26
lines changed

3 files changed

+73
-26
lines changed

spring-context/src/main/java/org/springframework/format/annotation/DateTimeFormat.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2009 the original author or authors.
2+
* Copyright 2002-2012 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -41,10 +41,11 @@
4141
* When no annotation attributes are specified, the default format applied is style-based with a style code of 'SS' (short date, short time).
4242
*
4343
* @author Keith Donald
44+
* @author Juergen Hoeller
4445
* @since 3.0
4546
* @see org.joda.time.format.DateTimeFormat
4647
*/
47-
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER})
48+
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
4849
@Retention(RetentionPolicy.RUNTIME)
4950
public @interface DateTimeFormat {
5051

spring-context/src/test/java/org/springframework/format/support/FormattingConversionServiceTests.java

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
package org.springframework.format.support;
1818

19+
import java.lang.annotation.Retention;
20+
import java.lang.annotation.RetentionPolicy;
1921
import java.text.ParseException;
2022
import java.util.ArrayList;
2123
import java.util.Date;
@@ -106,6 +108,23 @@ public void testFormatFieldForValueInjection() {
106108
assertEquals(new LocalDate(2009, 10, 31), new LocalDate(valueBean.date));
107109
}
108110

111+
@Test
112+
public void testFormatFieldForValueInjectionUsingMetaAnnotations() {
113+
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
114+
ac.registerBeanDefinition("valueBean", new RootBeanDefinition(MetaValueBean.class, false));
115+
ac.registerBeanDefinition("conversionService", new RootBeanDefinition(FormattingConversionServiceFactoryBean.class));
116+
ac.registerBeanDefinition("ppc", new RootBeanDefinition(PropertyPlaceholderConfigurer.class));
117+
ac.refresh();
118+
System.setProperty("myDate", "10-31-09");
119+
try {
120+
MetaValueBean valueBean = ac.getBean(MetaValueBean.class);
121+
assertEquals(new LocalDate(2009, 10, 31), new LocalDate(valueBean.date));
122+
}
123+
finally {
124+
System.clearProperty("myDate");
125+
}
126+
}
127+
109128
@Test
110129
public void testFormatFieldForAnnotation() throws Exception {
111130
formattingService.addFormatterForFieldAnnotation(new JodaDateTimeFormatAnnotationFormatterFactory());
@@ -287,6 +306,20 @@ public static class ValueBean {
287306
}
288307

289308

309+
public static class MetaValueBean {
310+
311+
@MyDateAnn
312+
public Date date;
313+
}
314+
315+
316+
@Value("${myDate}")
317+
@org.springframework.format.annotation.DateTimeFormat(pattern="MM-d-yy")
318+
@Retention(RetentionPolicy.RUNTIME)
319+
public static @interface MyDateAnn {
320+
}
321+
322+
290323
public static class Model {
291324

292325
@org.springframework.format.annotation.DateTimeFormat(style="S-")
@@ -310,7 +343,7 @@ public static class ModelWithPlaceholders {
310343
@org.springframework.format.annotation.DateTimeFormat(style="${dateStyle}")
311344
public Date date;
312345

313-
@org.springframework.format.annotation.DateTimeFormat(pattern="${datePattern}")
346+
@MyDatePattern
314347
public List<Date> dates;
315348

316349
public List<Date> getDates() {
@@ -321,7 +354,14 @@ public void setDates(List<Date> dates) {
321354
this.dates = dates;
322355
}
323356
}
324-
357+
358+
359+
@org.springframework.format.annotation.DateTimeFormat(pattern="${datePattern}")
360+
@Retention(RetentionPolicy.RUNTIME)
361+
public static @interface MyDatePattern {
362+
}
363+
364+
325365
public static class NullReturningFormatter implements Formatter<Integer> {
326366

327367
public String print(Integer object, Locale locale) {

spring-core/src/main/java/org/springframework/core/convert/TypeDescriptor.java

Lines changed: 28 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -215,8 +215,9 @@ public static TypeDescriptor forObject(Object source) {
215215
/**
216216
* The type of the backing class, method parameter, field, or property described by this TypeDescriptor.
217217
* Returns primitive types as-is.
218-
* See {@link #getObjectType()} for a variation of this operation that resolves primitive types to their corresponding Object types if necessary.
219-
* @return the type, or <code>null</code> if this is {@link TypeDescriptor#NULL}
218+
* <p>See {@link #getObjectType()} for a variation of this operation that resolves primitive types
219+
* to their corresponding Object types if necessary.
220+
* @return the type, or <code>null</code>
220221
* @see #getObjectType()
221222
*/
222223
public Class<?> getType() {
@@ -225,20 +226,21 @@ public Class<?> getType() {
225226

226227
/**
227228
* Variation of {@link #getType()} that accounts for a primitive type by returning its object wrapper type.
228-
* This is useful for conversion service implementations that wish to normalize to object-based types and not work with primitive types directly.
229+
* <p>This is useful for conversion service implementations that wish to normalize to object-based types
230+
* and not work with primitive types directly.
229231
*/
230232
public Class<?> getObjectType() {
231233
return ClassUtils.resolvePrimitiveIfNecessary(getType());
232234
}
233235

234236
/**
235237
* Narrows this {@link TypeDescriptor} by setting its type to the class of the provided value.
236-
* If the value is null, no narrowing is performed and this TypeDescriptor is returned unchanged.
237-
* Designed to be called by binding frameworks when they read property, field, or method return values.
238+
* If the value is <code>null</code>, no narrowing is performed and this TypeDescriptor is returned unchanged.
239+
* <p>Designed to be called by binding frameworks when they read property, field, or method return values.
238240
* Allows such frameworks to narrow a TypeDescriptor built from a declared property, field, or method return value type.
239-
* 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.
240-
* The narrowed TypeDescriptor can then be used to convert the HashMap to some other type.
241-
* Annotation and nested type context is preserved by the narrowed copy.
241+
* For example, a field declared as <code>java.lang.Object</code> would be narrowed to <code>java.util.HashMap</code>
242+
* if it was set to a <code>java.util.HashMap</code> value. The narrowed TypeDescriptor can then be used to convert
243+
* the HashMap to some other type. Annotation and nested type context is preserved by the narrowed copy.
242244
* @param value the value to use for narrowing this type descriptor
243245
* @return this TypeDescriptor narrowed (returns a copy with its type updated to the class of the provided value)
244246
*/
@@ -302,15 +304,21 @@ public boolean hasAnnotation(Class<? extends Annotation> annotationType) {
302304
/**
303305
* Obtain the annotation associated with this type descriptor of the specified type.
304306
* @param annotationType the annotation type
305-
* @return the annotation, or null if no such annotation exists on this type descriptor
307+
* @return the annotation, or <code>null</code> if no such annotation exists on this type descriptor
306308
*/
307309
@SuppressWarnings("unchecked")
308310
public <T extends Annotation> T getAnnotation(Class<T> annotationType) {
309-
for (Annotation annotation : getAnnotations()) {
311+
for (Annotation annotation : this.annotations) {
310312
if (annotation.annotationType().equals(annotationType)) {
311313
return (T) annotation;
312314
}
313315
}
316+
for (Annotation metaAnn : this.annotations) {
317+
T ann = metaAnn.annotationType().getAnnotation(annotationType);
318+
if (ann != null) {
319+
return ann;
320+
}
321+
}
314322
return null;
315323
}
316324

@@ -573,24 +581,23 @@ public boolean equals(Object obj) {
573581
return false;
574582
}
575583
TypeDescriptor other = (TypeDescriptor) obj;
576-
if (!ObjectUtils.nullSafeEquals(getType(), other.getType())) {
584+
if (!ObjectUtils.nullSafeEquals(this.type, other.type)) {
577585
return false;
578586
}
579-
Annotation[] annotations = getAnnotations();
580-
if (annotations.length != other.getAnnotations().length) {
587+
if (this.annotations.length != other.annotations.length) {
581588
return false;
582589
}
583-
for (Annotation ann : annotations) {
590+
for (Annotation ann : this.annotations) {
584591
if (other.getAnnotation(ann.annotationType()) == null) {
585592
return false;
586593
}
587594
}
588595
if (isCollection() || isArray()) {
589-
return ObjectUtils.nullSafeEquals(getElementTypeDescriptor(), other.getElementTypeDescriptor());
596+
return ObjectUtils.nullSafeEquals(this.elementTypeDescriptor, other.elementTypeDescriptor);
590597
}
591598
else if (isMap()) {
592-
return ObjectUtils.nullSafeEquals(getMapKeyTypeDescriptor(), other.getMapKeyTypeDescriptor()) &&
593-
ObjectUtils.nullSafeEquals(getMapValueTypeDescriptor(), other.getMapValueTypeDescriptor());
599+
return ObjectUtils.nullSafeEquals(this.mapKeyTypeDescriptor, other.mapKeyTypeDescriptor) &&
600+
ObjectUtils.nullSafeEquals(this.mapValueTypeDescriptor, other.mapValueTypeDescriptor);
594601
}
595602
else {
596603
return true;
@@ -603,17 +610,16 @@ public int hashCode() {
603610

604611
public String toString() {
605612
StringBuilder builder = new StringBuilder();
606-
Annotation[] anns = getAnnotations();
607-
for (Annotation ann : anns) {
613+
for (Annotation ann : this.annotations) {
608614
builder.append("@").append(ann.annotationType().getName()).append(' ');
609615
}
610616
builder.append(ClassUtils.getQualifiedName(getType()));
611617
if (isMap()) {
612-
builder.append("<").append(wildcard(getMapKeyTypeDescriptor()));
613-
builder.append(", ").append(wildcard(getMapValueTypeDescriptor())).append(">");
618+
builder.append("<").append(wildcard(this.mapKeyTypeDescriptor));
619+
builder.append(", ").append(wildcard(this.mapValueTypeDescriptor)).append(">");
614620
}
615621
else if (isCollection()) {
616-
builder.append("<").append(wildcard(getElementTypeDescriptor())).append(">");
622+
builder.append("<").append(wildcard(this.elementTypeDescriptor)).append(">");
617623
}
618624
return builder.toString();
619625
}

0 commit comments

Comments
 (0)