001/*
002 * Copyright 2002-2017 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.core.annotation;
018
019import java.lang.annotation.Annotation;
020import java.lang.reflect.AnnotatedElement;
021import java.lang.reflect.Array;
022import java.util.Iterator;
023import java.util.LinkedHashMap;
024import java.util.List;
025import java.util.Map;
026
027import org.springframework.util.Assert;
028import org.springframework.util.ObjectUtils;
029import org.springframework.util.StringUtils;
030
031/**
032 * {@link LinkedHashMap} subclass representing annotation attribute
033 * <em>key-value</em> pairs as read by {@link AnnotationUtils},
034 * {@link AnnotatedElementUtils}, and Spring's reflection- and ASM-based
035 * {@link org.springframework.core.type.AnnotationMetadata} implementations.
036 *
037 * <p>Provides 'pseudo-reification' to avoid noisy Map generics in the calling
038 * code as well as convenience methods for looking up annotation attributes
039 * in a type-safe fashion.
040 *
041 * @author Chris Beams
042 * @author Sam Brannen
043 * @author Juergen Hoeller
044 * @since 3.1.1
045 * @see AnnotationUtils#getAnnotationAttributes
046 * @see AnnotatedElementUtils
047 */
048@SuppressWarnings("serial")
049public class AnnotationAttributes extends LinkedHashMap<String, Object> {
050
051        private static final String UNKNOWN = "unknown";
052
053        private final Class<? extends Annotation> annotationType;
054
055        private final String displayName;
056
057        boolean validated = false;
058
059
060        /**
061         * Create a new, empty {@link AnnotationAttributes} instance.
062         */
063        public AnnotationAttributes() {
064                this.annotationType = null;
065                this.displayName = UNKNOWN;
066        }
067
068        /**
069         * Create a new, empty {@link AnnotationAttributes} instance with the
070         * given initial capacity to optimize performance.
071         * @param initialCapacity initial size of the underlying map
072         */
073        public AnnotationAttributes(int initialCapacity) {
074                super(initialCapacity);
075                this.annotationType = null;
076                this.displayName = UNKNOWN;
077        }
078
079        /**
080         * Create a new, empty {@link AnnotationAttributes} instance for the
081         * specified {@code annotationType}.
082         * @param annotationType the type of annotation represented by this
083         * {@code AnnotationAttributes} instance; never {@code null}
084         * @since 4.2
085         */
086        public AnnotationAttributes(Class<? extends Annotation> annotationType) {
087                Assert.notNull(annotationType, "'annotationType' must not be null");
088                this.annotationType = annotationType;
089                this.displayName = annotationType.getName();
090        }
091
092        /**
093         * Create a new, empty {@link AnnotationAttributes} instance for the
094         * specified {@code annotationType}.
095         * @param annotationType the annotation type name represented by this
096         * {@code AnnotationAttributes} instance; never {@code null}
097         * @param classLoader the ClassLoader to try to load the annotation type on,
098         * or {@code null} to just store the annotation type name
099         * @since 4.3.2
100         */
101        public AnnotationAttributes(String annotationType, ClassLoader classLoader) {
102                Assert.notNull(annotationType, "'annotationType' must not be null");
103                this.annotationType = getAnnotationType(annotationType, classLoader);
104                this.displayName = annotationType;
105        }
106
107        @SuppressWarnings("unchecked")
108        private static Class<? extends Annotation> getAnnotationType(String annotationType, ClassLoader classLoader) {
109                if (classLoader != null) {
110                        try {
111                                return (Class<? extends Annotation>) classLoader.loadClass(annotationType);
112                        }
113                        catch (ClassNotFoundException ex) {
114                                // Annotation Class not resolvable
115                        }
116                }
117                return null;
118        }
119
120        /**
121         * Create a new {@link AnnotationAttributes} instance, wrapping the provided
122         * map and all its <em>key-value</em> pairs.
123         * @param map original source of annotation attribute <em>key-value</em> pairs
124         * @see #fromMap(Map)
125         */
126        public AnnotationAttributes(Map<String, Object> map) {
127                super(map);
128                this.annotationType = null;
129                this.displayName = UNKNOWN;
130        }
131
132        /**
133         * Create a new {@link AnnotationAttributes} instance, wrapping the provided
134         * map and all its <em>key-value</em> pairs.
135         * @param other original source of annotation attribute <em>key-value</em> pairs
136         * @see #fromMap(Map)
137         */
138        public AnnotationAttributes(AnnotationAttributes other) {
139                super(other);
140                this.annotationType = other.annotationType;
141                this.displayName = other.displayName;
142                this.validated = other.validated;
143        }
144
145
146        /**
147         * Get the type of annotation represented by this
148         * {@code AnnotationAttributes} instance.
149         * @return the annotation type, or {@code null} if unknown
150         * @since 4.2
151         */
152        public Class<? extends Annotation> annotationType() {
153                return this.annotationType;
154        }
155
156        /**
157         * Get the value stored under the specified {@code attributeName} as a
158         * string.
159         * @param attributeName the name of the attribute to get; never
160         * {@code null} or empty
161         * @return the value
162         * @throws IllegalArgumentException if the attribute does not exist or
163         * if it is not of the expected type
164         */
165        public String getString(String attributeName) {
166                return getRequiredAttribute(attributeName, String.class);
167        }
168
169        /**
170         * Get the value stored under the specified {@code attributeName} as a
171         * string, taking into account alias semantics defined via
172         * {@link AliasFor @AliasFor}.
173         * <p>If there is no value stored under the specified {@code attributeName}
174         * but the attribute has an alias declared via {@code @AliasFor}, the
175         * value of the alias will be returned.
176         * @param attributeName the name of the attribute to get; never
177         * {@code null} or empty
178         * @param annotationType the type of annotation represented by this
179         * {@code AnnotationAttributes} instance; never {@code null}
180         * @param annotationSource the source of the annotation represented by
181         * this {@code AnnotationAttributes} (e.g., the {@link AnnotatedElement});
182         * or {@code null} if unknown
183         * @return the string value
184         * @throws IllegalArgumentException if the attribute and its alias do
185         * not exist or are not of type {@code String}
186         * @throws AnnotationConfigurationException if the attribute and its
187         * alias are both present with different non-empty values
188         * @since 4.2
189         * @deprecated as of Spring 4.3.2, in favor of built-in alias resolution
190         * in {@link #getString} itself
191         */
192        @Deprecated
193        public String getAliasedString(String attributeName, Class<? extends Annotation> annotationType,
194                        Object annotationSource) {
195
196                return getRequiredAttributeWithAlias(attributeName, annotationType, annotationSource, String.class);
197        }
198
199        /**
200         * Get the value stored under the specified {@code attributeName} as an
201         * array of strings.
202         * <p>If the value stored under the specified {@code attributeName} is
203         * a string, it will be wrapped in a single-element array before
204         * returning it.
205         * @param attributeName the name of the attribute to get; never
206         * {@code null} or empty
207         * @return the value
208         * @throws IllegalArgumentException if the attribute does not exist or
209         * if it is not of the expected type
210         */
211        public String[] getStringArray(String attributeName) {
212                return getRequiredAttribute(attributeName, String[].class);
213        }
214
215        /**
216         * Get the value stored under the specified {@code attributeName} as an
217         * array of strings, taking into account alias semantics defined via
218         * {@link AliasFor @AliasFor}.
219         * <p>If there is no value stored under the specified {@code attributeName}
220         * but the attribute has an alias declared via {@code @AliasFor}, the
221         * value of the alias will be returned.
222         * @param attributeName the name of the attribute to get; never
223         * {@code null} or empty
224         * @param annotationType the type of annotation represented by this
225         * {@code AnnotationAttributes} instance; never {@code null}
226         * @param annotationSource the source of the annotation represented by
227         * this {@code AnnotationAttributes} (e.g., the {@link AnnotatedElement});
228         * or {@code null} if unknown
229         * @return the array of strings
230         * @throws IllegalArgumentException if the attribute and its alias do
231         * not exist or are not of type {@code String[]}
232         * @throws AnnotationConfigurationException if the attribute and its
233         * alias are both present with different non-empty values
234         * @since 4.2
235         * @deprecated as of Spring 4.3.2, in favor of built-in alias resolution
236         * in {@link #getStringArray} itself
237         */
238        @Deprecated
239        public String[] getAliasedStringArray(String attributeName, Class<? extends Annotation> annotationType,
240                        Object annotationSource) {
241
242                return getRequiredAttributeWithAlias(attributeName, annotationType, annotationSource, String[].class);
243        }
244
245        /**
246         * Get the value stored under the specified {@code attributeName} as a boolean.
247         * @param attributeName the name of the attribute to get;
248         * never {@code null} or empty
249         * @return the value
250         * @throws IllegalArgumentException if the attribute does not exist or
251         * if it is not of the expected type
252         */
253        public boolean getBoolean(String attributeName) {
254                return getRequiredAttribute(attributeName, Boolean.class);
255        }
256
257        /**
258         * Get the value stored under the specified {@code attributeName} as a number.
259         * @param attributeName the name of the attribute to get;
260         * never {@code null} or empty
261         * @return the value
262         * @throws IllegalArgumentException if the attribute does not exist or
263         * if it is not of the expected type
264         */
265        @SuppressWarnings("unchecked")
266        public <N extends Number> N getNumber(String attributeName) {
267                return (N) getRequiredAttribute(attributeName, Number.class);
268        }
269
270        /**
271         * Get the value stored under the specified {@code attributeName} as an enum.
272         * @param attributeName the name of the attribute to get;
273         * never {@code null} or empty
274         * @return the value
275         * @throws IllegalArgumentException if the attribute does not exist or
276         * if it is not of the expected type
277         */
278        @SuppressWarnings("unchecked")
279        public <E extends Enum<?>> E getEnum(String attributeName) {
280                return (E) getRequiredAttribute(attributeName, Enum.class);
281        }
282
283        /**
284         * Get the value stored under the specified {@code attributeName} as a class.
285         * @param attributeName the name of the attribute to get;
286         * never {@code null} or empty
287         * @return the value
288         * @throws IllegalArgumentException if the attribute does not exist or
289         * if it is not of the expected type
290         */
291        @SuppressWarnings("unchecked")
292        public <T> Class<? extends T> getClass(String attributeName) {
293                return getRequiredAttribute(attributeName, Class.class);
294        }
295
296        /**
297         * Get the value stored under the specified {@code attributeName} as an
298         * array of classes.
299         * <p>If the value stored under the specified {@code attributeName} is a class,
300         * it will be wrapped in a single-element array before returning it.
301         * @param attributeName the name of the attribute to get;
302         * never {@code null} or empty
303         * @return the value
304         * @throws IllegalArgumentException if the attribute does not exist or
305         * if it is not of the expected type
306         */
307        public Class<?>[] getClassArray(String attributeName) {
308                return getRequiredAttribute(attributeName, Class[].class);
309        }
310
311        /**
312         * Get the value stored under the specified {@code attributeName} as an
313         * array of classes, taking into account alias semantics defined via
314         * {@link AliasFor @AliasFor}.
315         * <p>If there is no value stored under the specified {@code attributeName}
316         * but the attribute has an alias declared via {@code @AliasFor}, the
317         * value of the alias will be returned.
318         * @param attributeName the name of the attribute to get; never
319         * {@code null} or empty
320         * @param annotationType the type of annotation represented by this
321         * {@code AnnotationAttributes} instance; never {@code null}
322         * @param annotationSource the source of the annotation represented by
323         * this {@code AnnotationAttributes} (e.g., the {@link AnnotatedElement});
324         * or {@code null} if unknown
325         * @return the array of classes
326         * @throws IllegalArgumentException if the attribute and its alias do
327         * not exist or are not of type {@code Class[]}
328         * @throws AnnotationConfigurationException if the attribute and its
329         * alias are both present with different non-empty values
330         * @since 4.2
331         * @deprecated as of Spring 4.3.2, in favor of built-in alias resolution
332         * in {@link #getClassArray} itself
333         */
334        @Deprecated
335        public Class<?>[] getAliasedClassArray(String attributeName, Class<? extends Annotation> annotationType,
336                        Object annotationSource) {
337
338                return getRequiredAttributeWithAlias(attributeName, annotationType, annotationSource, Class[].class);
339        }
340
341        /**
342         * Get the {@link AnnotationAttributes} stored under the specified
343         * {@code attributeName}.
344         * <p>Note: if you expect an actual annotation, invoke
345         * {@link #getAnnotation(String, Class)} instead.
346         * @param attributeName the name of the attribute to get; never
347         * {@code null} or empty
348         * @return the {@code AnnotationAttributes}
349         * @throws IllegalArgumentException if the attribute does not exist or
350         * if it is not of the expected type
351         */
352        public AnnotationAttributes getAnnotation(String attributeName) {
353                return getRequiredAttribute(attributeName, AnnotationAttributes.class);
354        }
355
356        /**
357         * Get the annotation of type {@code annotationType} stored under the
358         * specified {@code attributeName}.
359         * @param attributeName the name of the attribute to get;
360         * never {@code null} or empty
361         * @param annotationType the expected annotation type; never {@code null}
362         * @return the annotation
363         * @throws IllegalArgumentException if the attribute does not exist or
364         * if it is not of the expected type
365         * @since 4.2
366         */
367        public <A extends Annotation> A getAnnotation(String attributeName, Class<A> annotationType) {
368                return getRequiredAttribute(attributeName, annotationType);
369        }
370
371        /**
372         * Get the array of {@link AnnotationAttributes} stored under the specified
373         * {@code attributeName}.
374         * <p>If the value stored under the specified {@code attributeName} is
375         * an instance of {@code AnnotationAttributes}, it will be wrapped in
376         * a single-element array before returning it.
377         * <p>Note: if you expect an actual array of annotations, invoke
378         * {@link #getAnnotationArray(String, Class)} instead.
379         * @param attributeName the name of the attribute to get;
380         * never {@code null} or empty
381         * @return the array of {@code AnnotationAttributes}
382         * @throws IllegalArgumentException if the attribute does not exist or
383         * if it is not of the expected type
384         */
385        public AnnotationAttributes[] getAnnotationArray(String attributeName) {
386                return getRequiredAttribute(attributeName, AnnotationAttributes[].class);
387        }
388
389        /**
390         * Get the array of type {@code annotationType} stored under the specified
391         * {@code attributeName}.
392         * <p>If the value stored under the specified {@code attributeName} is
393         * an {@code Annotation}, it will be wrapped in a single-element array
394         * before returning it.
395         * @param attributeName the name of the attribute to get;
396         * never {@code null} or empty
397         * @param annotationType the expected annotation type; never {@code null}
398         * @return the annotation array
399         * @throws IllegalArgumentException if the attribute does not exist or
400         * if it is not of the expected type
401         * @since 4.2
402         */
403        @SuppressWarnings("unchecked")
404        public <A extends Annotation> A[] getAnnotationArray(String attributeName, Class<A> annotationType) {
405                Object array = Array.newInstance(annotationType, 0);
406                return (A[]) getRequiredAttribute(attributeName, array.getClass());
407        }
408
409        /**
410         * Get the value stored under the specified {@code attributeName},
411         * ensuring that the value is of the {@code expectedType}.
412         * <p>If the {@code expectedType} is an array and the value stored
413         * under the specified {@code attributeName} is a single element of the
414         * component type of the expected array type, the single element will be
415         * wrapped in a single-element array of the appropriate type before
416         * returning it.
417         * @param attributeName the name of the attribute to get;
418         * never {@code null} or empty
419         * @param expectedType the expected type; never {@code null}
420         * @return the value
421         * @throws IllegalArgumentException if the attribute does not exist or
422         * if it is not of the expected type
423         */
424        @SuppressWarnings("unchecked")
425        private <T> T getRequiredAttribute(String attributeName, Class<T> expectedType) {
426                Assert.hasText(attributeName, "'attributeName' must not be null or empty");
427                Object value = get(attributeName);
428                assertAttributePresence(attributeName, value);
429                assertNotException(attributeName, value);
430                if (!expectedType.isInstance(value) && expectedType.isArray() &&
431                                expectedType.getComponentType().isInstance(value)) {
432                        Object array = Array.newInstance(expectedType.getComponentType(), 1);
433                        Array.set(array, 0, value);
434                        value = array;
435                }
436                assertAttributeType(attributeName, value, expectedType);
437                return (T) value;
438        }
439
440        /**
441         * Get the value stored under the specified {@code attributeName} as an
442         * object of the {@code expectedType}, taking into account alias semantics
443         * defined via {@link AliasFor @AliasFor}.
444         * <p>If there is no value stored under the specified {@code attributeName}
445         * but the attribute has an alias declared via {@code @AliasFor}, the
446         * value of the alias will be returned.
447         * @param attributeName the name of the attribute to get; never
448         * {@code null} or empty
449         * @param annotationType the type of annotation represented by this
450         * {@code AnnotationAttributes} instance; never {@code null}
451         * @param annotationSource the source of the annotation represented by
452         * this {@code AnnotationAttributes} (e.g., the {@link AnnotatedElement});
453         * or {@code null} if unknown
454         * @param expectedType the expected type; never {@code null}
455         * @return the value
456         * @throws IllegalArgumentException if the attribute and its alias do
457         * not exist or are not of the {@code expectedType}
458         * @throws AnnotationConfigurationException if the attribute and its
459         * alias are both present with different non-empty values
460         * @since 4.2
461         * @see ObjectUtils#isEmpty(Object)
462         */
463        private <T> T getRequiredAttributeWithAlias(String attributeName, Class<? extends Annotation> annotationType,
464                        Object annotationSource, Class<T> expectedType) {
465
466                Assert.hasText(attributeName, "'attributeName' must not be null or empty");
467                Assert.notNull(annotationType, "'annotationType' must not be null");
468                Assert.notNull(expectedType, "'expectedType' must not be null");
469
470                T attributeValue = getAttribute(attributeName, expectedType);
471
472                List<String> aliasNames = AnnotationUtils.getAttributeAliasMap(annotationType).get(attributeName);
473                if (aliasNames != null) {
474                        for (String aliasName : aliasNames) {
475                                T aliasValue = getAttribute(aliasName, expectedType);
476                                boolean attributeEmpty = ObjectUtils.isEmpty(attributeValue);
477                                boolean aliasEmpty = ObjectUtils.isEmpty(aliasValue);
478
479                                if (!attributeEmpty && !aliasEmpty && !ObjectUtils.nullSafeEquals(attributeValue, aliasValue)) {
480                                        String elementName = (annotationSource == null ? "unknown element" : annotationSource.toString());
481                                        String msg = String.format("In annotation [%s] declared on [%s], attribute [%s] and its " +
482                                                        "alias [%s] are present with values of [%s] and [%s], but only one is permitted.",
483                                                        annotationType.getName(), elementName, attributeName, aliasName,
484                                                        ObjectUtils.nullSafeToString(attributeValue), ObjectUtils.nullSafeToString(aliasValue));
485                                        throw new AnnotationConfigurationException(msg);
486                                }
487
488                                // If we expect an array and the current tracked value is null but the
489                                // current alias value is non-null, then replace the current null value
490                                // with the non-null value (which may be an empty array).
491                                if (expectedType.isArray() && attributeValue == null && aliasValue != null) {
492                                        attributeValue = aliasValue;
493                                }
494                                // Else: if we're not expecting an array, we can rely on the behavior of
495                                // ObjectUtils.isEmpty().
496                                else if (attributeEmpty && !aliasEmpty) {
497                                        attributeValue = aliasValue;
498                                }
499                        }
500                        assertAttributePresence(attributeName, aliasNames, attributeValue);
501                }
502
503                return attributeValue;
504        }
505
506        /**
507         * Get the value stored under the specified {@code attributeName},
508         * ensuring that the value is of the {@code expectedType}.
509         * @param attributeName the name of the attribute to get; never
510         * {@code null} or empty
511         * @param expectedType the expected type; never {@code null}
512         * @return the value
513         * @throws IllegalArgumentException if the attribute is not of the
514         * expected type
515         * @see #getRequiredAttribute(String, Class)
516         */
517        @SuppressWarnings("unchecked")
518        private <T> T getAttribute(String attributeName, Class<T> expectedType) {
519                Object value = get(attributeName);
520                if (value != null) {
521                        assertNotException(attributeName, value);
522                        assertAttributeType(attributeName, value, expectedType);
523                }
524                return (T) value;
525        }
526
527        private void assertAttributePresence(String attributeName, Object attributeValue) {
528                if (attributeValue == null) {
529                        throw new IllegalArgumentException(String.format(
530                                        "Attribute '%s' not found in attributes for annotation [%s]", attributeName, this.displayName));
531                }
532        }
533
534        private void assertAttributePresence(String attributeName, List<String> aliases, Object attributeValue) {
535                if (attributeValue == null) {
536                        throw new IllegalArgumentException(String.format(
537                                        "Neither attribute '%s' nor one of its aliases %s was found in attributes for annotation [%s]",
538                                        attributeName, aliases, this.displayName));
539                }
540        }
541
542        private void assertNotException(String attributeName, Object attributeValue) {
543                if (attributeValue instanceof Exception) {
544                        throw new IllegalArgumentException(String.format(
545                                        "Attribute '%s' for annotation [%s] was not resolvable due to exception [%s]",
546                                        attributeName, this.displayName, attributeValue), (Exception) attributeValue);
547                }
548        }
549
550        private void assertAttributeType(String attributeName, Object attributeValue, Class<?> expectedType) {
551                if (!expectedType.isInstance(attributeValue)) {
552                        throw new IllegalArgumentException(String.format(
553                                        "Attribute '%s' is of type [%s], but [%s] was expected in attributes for annotation [%s]",
554                                        attributeName, attributeValue.getClass().getSimpleName(), expectedType.getSimpleName(),
555                                        this.displayName));
556                }
557        }
558
559        /**
560         * Store the supplied {@code value} in this map under the specified
561         * {@code key}, unless a value is already stored under the key.
562         * @param key the key under which to store the value
563         * @param value the value to store
564         * @return the current value stored in this map, or {@code null} if no
565         * value was previously stored in this map
566         * @see #get
567         * @see #put
568         * @since 4.2
569         */
570        @Override
571        public Object putIfAbsent(String key, Object value) {
572                Object obj = get(key);
573                if (obj == null) {
574                        obj = put(key, value);
575                }
576                return obj;
577        }
578
579        @Override
580        public String toString() {
581                Iterator<Map.Entry<String, Object>> entries = entrySet().iterator();
582                StringBuilder sb = new StringBuilder("{");
583                while (entries.hasNext()) {
584                        Map.Entry<String, Object> entry = entries.next();
585                        sb.append(entry.getKey());
586                        sb.append('=');
587                        sb.append(valueToString(entry.getValue()));
588                        sb.append(entries.hasNext() ? ", " : "");
589                }
590                sb.append("}");
591                return sb.toString();
592        }
593
594        private String valueToString(Object value) {
595                if (value == this) {
596                        return "(this Map)";
597                }
598                if (value instanceof Object[]) {
599                        return "[" + StringUtils.arrayToDelimitedString((Object[]) value, ", ") + "]";
600                }
601                return String.valueOf(value);
602        }
603
604
605        /**
606         * Return an {@link AnnotationAttributes} instance based on the given map.
607         * <p>If the map is already an {@code AnnotationAttributes} instance, it
608         * will be cast and returned immediately without creating a new instance.
609         * Otherwise a new instance will be created by passing the supplied map
610         * to the {@link #AnnotationAttributes(Map)} constructor.
611         * @param map original source of annotation attribute <em>key-value</em> pairs
612         */
613        public static AnnotationAttributes fromMap(Map<String, Object> map) {
614                if (map == null) {
615                        return null;
616                }
617                if (map instanceof AnnotationAttributes) {
618                        return (AnnotationAttributes) map;
619                }
620                return new AnnotationAttributes(map);
621        }
622
623}