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}