diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAttributes.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAttributes.java index dd98bb794e..426648be87 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAttributes.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAttributes.java @@ -26,8 +26,8 @@ import org.springframework.util.Assert; import org.springframework.util.StringUtils; /** - * {@link LinkedHashMap} subclass representing annotation attribute key/value - * pairs as read by Spring's reflection- or ASM-based + * {@link LinkedHashMap} subclass representing annotation attribute + * key-value pairs as read by Spring's reflection- or ASM-based * {@link org.springframework.core.type.AnnotationMetadata} implementations, * {@link AnnotationUtils}, and {@link AnnotatedElementUtils}. * @@ -59,17 +59,18 @@ public class AnnotationAttributes extends LinkedHashMap { * Create a new, empty {@link AnnotationAttributes} instance for the * specified {@code annotationType}. * @param annotationType the type of annotation represented by this - * {@code AnnotationAttributes} instance + * {@code AnnotationAttributes} instance; never {@code null} * @since 4.2 */ public AnnotationAttributes(Class annotationType) { + Assert.notNull(annotationType, "annotationType must not be null"); this.annotationType = annotationType; - this.displayName = (annotationType() != null ? annotationType.getName() : "unknown"); + this.displayName = annotationType.getName(); } /** - * Create a new, empty {@link AnnotationAttributes} instance with the given initial - * capacity to optimize performance. + * Create a new, empty {@link AnnotationAttributes} instance with the + * given initial capacity to optimize performance. * @param initialCapacity initial size of the underlying map */ public AnnotationAttributes(int initialCapacity) { @@ -79,9 +80,10 @@ public class AnnotationAttributes extends LinkedHashMap { } /** - * Create a new {@link AnnotationAttributes} instance, wrapping the provided map - * and all its key/value pairs. - * @param map original source of annotation attribute key/value pairs to wrap + * Create a new {@link AnnotationAttributes} instance, wrapping the + * provided map and all its key-value pairs. + * @param map original source of annotation attribute key-value + * pairs * @see #fromMap(Map) */ public AnnotationAttributes(Map map) { @@ -91,8 +93,8 @@ public class AnnotationAttributes extends LinkedHashMap { } /** - * Get the type of annotation represented by this {@code AnnotationAttributes} - * instance. + * Get the type of annotation represented by this + * {@code AnnotationAttributes} instance. * @return the annotation type, or {@code null} if unknown * @since 4.2 */ @@ -100,45 +102,151 @@ public class AnnotationAttributes extends LinkedHashMap { return this.annotationType; } + /** + * Get the value stored under the specified {@code attributeName} as a + * string. + * @param attributeName the name of the attribute to get; never + * {@code null} or empty + * @return the value + * @throws IllegalArgumentException if the attribute does not exist or + * if it is not of the expected type + */ public String getString(String attributeName) { return doGet(attributeName, String.class); } + /** + * Get the value stored under the specified {@code attributeName} as an + * array of strings. + *

If the value stored under the specified {@code attributeName} is + * a string, it will be wrapped in a single-element array before + * returning it. + * @param attributeName the name of the attribute to get; never + * {@code null} or empty + * @return the value + * @throws IllegalArgumentException if the attribute does not exist or + * if it is not of the expected type + */ public String[] getStringArray(String attributeName) { return doGet(attributeName, String[].class); } + /** + * Get the value stored under the specified {@code attributeName} as a + * boolean. + * @param attributeName the name of the attribute to get; never + * {@code null} or empty + * @return the value + * @throws IllegalArgumentException if the attribute does not exist or + * if it is not of the expected type + */ public boolean getBoolean(String attributeName) { return doGet(attributeName, Boolean.class); } + /** + * Get the value stored under the specified {@code attributeName} as a + * number. + * @param attributeName the name of the attribute to get; never + * {@code null} or empty + * @return the value + * @throws IllegalArgumentException if the attribute does not exist or + * if it is not of the expected type + */ @SuppressWarnings("unchecked") public N getNumber(String attributeName) { return (N) doGet(attributeName, Number.class); } + /** + * Get the value stored under the specified {@code attributeName} as an + * enum. + * @param attributeName the name of the attribute to get; never + * {@code null} or empty + * @return the value + * @throws IllegalArgumentException if the attribute does not exist or + * if it is not of the expected type + */ @SuppressWarnings("unchecked") public > E getEnum(String attributeName) { return (E) doGet(attributeName, Enum.class); } + /** + * Get the value stored under the specified {@code attributeName} as a + * class. + * @param attributeName the name of the attribute to get; never + * {@code null} or empty + * @return the value + * @throws IllegalArgumentException if the attribute does not exist or + * if it is not of the expected type + */ @SuppressWarnings("unchecked") public Class getClass(String attributeName) { return doGet(attributeName, Class.class); } + /** + * Get the value stored under the specified {@code attributeName} as an + * array of classes. + *

If the value stored under the specified {@code attributeName} is + * a class, it will be wrapped in a single-element array before + * returning it. + * @param attributeName the name of the attribute to get; never + * {@code null} or empty + * @return the value + * @throws IllegalArgumentException if the attribute does not exist or + * if it is not of the expected type + */ public Class[] getClassArray(String attributeName) { return doGet(attributeName, Class[].class); } + /** + * Get the {@link AnnotationAttributes} stored under the specified + * {@code attributeName}. + * @param attributeName the name of the attribute to get; never + * {@code null} or empty + * @return the {@code AnnotationAttributes} + * @throws IllegalArgumentException if the attribute does not exist or + * if it is not of the expected type + */ public AnnotationAttributes getAnnotation(String attributeName) { return doGet(attributeName, AnnotationAttributes.class); } + /** + * Get the array of {@link AnnotationAttributes} stored under the specified + * {@code attributeName}. + *

If the value stored under the specified {@code attributeName} is + * an instance of {@code AnnotationAttributes}, it will be wrapped in + * a single-element array before returning it. + * @param attributeName the name of the attribute to get; never + * {@code null} or empty + * @return the array of {@code AnnotationAttributes} + * @throws IllegalArgumentException if the attribute does not exist or + * if it is not of the expected type + */ public AnnotationAttributes[] getAnnotationArray(String attributeName) { return doGet(attributeName, AnnotationAttributes[].class); } + /** + * Get the value stored under the specified {@code attributeName}, + * ensuring that the value is of the {@code expectedType}. + *

If the {@code expectedType} is an array and the value stored + * under the specified {@code attributeName} is a single element of the + * component type of the expected array type, the single element will be + * wrapped in a single-element array of the appropriate type before + * returning it. + * @param attributeName the name of the attribute to get; never + * {@code null} or empty + * @param expectedType the expected type; never {@code null} + * @return the value + * @throws IllegalArgumentException if the attribute does not exist or + * if it is not of the expected type + * @since 4.2 + */ @SuppressWarnings("unchecked") private T doGet(String attributeName, Class expectedType) { Assert.hasText(attributeName, "attributeName must not be null or empty"); @@ -149,9 +257,9 @@ public class AnnotationAttributes extends LinkedHashMap { } if (!expectedType.isInstance(value)) { if (expectedType.isArray() && expectedType.getComponentType().isInstance(value)) { - Object arrayValue = Array.newInstance(expectedType.getComponentType(), 1); - Array.set(arrayValue, 0, value); - value = arrayValue; + Object array = Array.newInstance(expectedType.getComponentType(), 1); + Array.set(array, 0, value); + value = array; } else { throw new IllegalArgumentException(String.format( @@ -171,6 +279,7 @@ public class AnnotationAttributes extends LinkedHashMap { * value was previously stored in this map * @see #get * @see #put + * @since 4.2 */ @Override public Object putIfAbsent(String key, Object value) { @@ -213,7 +322,7 @@ public class AnnotationAttributes extends LinkedHashMap { * will be cast and returned immediately without creating a new instance. * Otherwise a new instance will be created by passing the supplied map * to the {@link #AnnotationAttributes(Map)} constructor. - * @param map original source of annotation attribute key/value pairs + * @param map original source of annotation attribute key-value pairs */ public static AnnotationAttributes fromMap(Map map) { if (map == null) { diff --git a/spring-core/src/test/java/org/springframework/core/annotation/AnnotationAttributesTests.java b/spring-core/src/test/java/org/springframework/core/annotation/AnnotationAttributesTests.java index 16d8f9a5f6..a547599748 100644 --- a/spring-core/src/test/java/org/springframework/core/annotation/AnnotationAttributesTests.java +++ b/spring-core/src/test/java/org/springframework/core/annotation/AnnotationAttributesTests.java @@ -44,18 +44,18 @@ public class AnnotationAttributesTests { @Test public void typeSafeAttributeAccess() { + AnnotationAttributes nestedAttributes = new AnnotationAttributes(); + nestedAttributes.put("value", 10); + nestedAttributes.put("name", "algernon"); + attributes.put("name", "dave"); attributes.put("names", new String[] { "dave", "frank", "hal" }); attributes.put("bool1", true); attributes.put("bool2", false); attributes.put("color", Color.RED); - attributes.put("clazz", Integer.class); + attributes.put("class", Integer.class); attributes.put("classes", new Class[] { Number.class, Short.class, Integer.class }); attributes.put("number", 42); - attributes.put("numbers", new int[] { 42, 43 }); - AnnotationAttributes nestedAttributes = new AnnotationAttributes(); - nestedAttributes.put("value", 10); - nestedAttributes.put("name", "algernon"); attributes.put("anno", nestedAttributes); attributes.put("annoArray", new AnnotationAttributes[] { nestedAttributes }); @@ -64,13 +64,33 @@ public class AnnotationAttributesTests { assertThat(attributes.getBoolean("bool1"), equalTo(true)); assertThat(attributes.getBoolean("bool2"), equalTo(false)); assertThat(attributes.getEnum("color"), equalTo(Color.RED)); - assertTrue(attributes.getClass("clazz").equals(Integer.class)); + assertTrue(attributes.getClass("class").equals(Integer.class)); assertThat(attributes.getClassArray("classes"), equalTo(new Class[] { Number.class, Short.class, Integer.class })); assertThat(attributes.getNumber("number"), equalTo(42)); assertThat(attributes.getAnnotation("anno").getNumber("value"), equalTo(10)); assertThat(attributes.getAnnotationArray("annoArray")[0].getString("name"), equalTo("algernon")); } + @Test + public void singleElementToSingleElementArrayConversionSupport() { + AnnotationAttributes nestedAttributes = new AnnotationAttributes(); + nestedAttributes.put("name", "Dilbert"); + + // Store single elements + attributes.put("names", "Dogbert"); + attributes.put("classes", Number.class); + attributes.put("nestedAttributes", nestedAttributes); + + // Get back arrays of single elements + assertThat(attributes.getStringArray("names"), equalTo(new String[] { "Dogbert" })); + assertThat(attributes.getClassArray("classes"), equalTo(new Class[] { Number.class })); + AnnotationAttributes[] array = attributes.getAnnotationArray("nestedAttributes"); + assertNotNull(array); + assertTrue(array.getClass().isArray()); + assertThat(array.length, is(1)); + assertThat(array[0].getString("name"), equalTo("Dilbert")); + } + @Test public void getEnumWithNullAttributeName() { exception.expect(IllegalArgumentException.class);