001/* 002 * Copyright 2002-2019 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.Array; 021import java.util.Iterator; 022import java.util.LinkedHashMap; 023import java.util.Map; 024 025import org.springframework.lang.Nullable; 026import org.springframework.util.Assert; 027import org.springframework.util.StringUtils; 028 029/** 030 * {@link LinkedHashMap} subclass representing annotation attribute 031 * <em>key-value</em> pairs as read by {@link AnnotationUtils}, 032 * {@link AnnotatedElementUtils}, and Spring's reflection- and ASM-based 033 * {@link org.springframework.core.type.AnnotationMetadata} implementations. 034 * 035 * <p>Provides 'pseudo-reification' to avoid noisy Map generics in the calling 036 * code as well as convenience methods for looking up annotation attributes 037 * in a type-safe fashion. 038 * 039 * @author Chris Beams 040 * @author Sam Brannen 041 * @author Juergen Hoeller 042 * @since 3.1.1 043 * @see AnnotationUtils#getAnnotationAttributes 044 * @see AnnotatedElementUtils 045 */ 046@SuppressWarnings("serial") 047public class AnnotationAttributes extends LinkedHashMap<String, Object> { 048 049 private static final String UNKNOWN = "unknown"; 050 051 @Nullable 052 private final Class<? extends Annotation> annotationType; 053 054 final String displayName; 055 056 boolean validated = false; 057 058 059 /** 060 * Create a new, empty {@link AnnotationAttributes} instance. 061 */ 062 public AnnotationAttributes() { 063 this.annotationType = null; 064 this.displayName = UNKNOWN; 065 } 066 067 /** 068 * Create a new, empty {@link AnnotationAttributes} instance with the 069 * given initial capacity to optimize performance. 070 * @param initialCapacity initial size of the underlying map 071 */ 072 public AnnotationAttributes(int initialCapacity) { 073 super(initialCapacity); 074 this.annotationType = null; 075 this.displayName = UNKNOWN; 076 } 077 078 /** 079 * Create a new {@link AnnotationAttributes} instance, wrapping the provided 080 * map and all its <em>key-value</em> pairs. 081 * @param map original source of annotation attribute <em>key-value</em> pairs 082 * @see #fromMap(Map) 083 */ 084 public AnnotationAttributes(Map<String, Object> map) { 085 super(map); 086 this.annotationType = null; 087 this.displayName = UNKNOWN; 088 } 089 090 /** 091 * Create a new {@link AnnotationAttributes} instance, wrapping the provided 092 * map and all its <em>key-value</em> pairs. 093 * @param other original source of annotation attribute <em>key-value</em> pairs 094 * @see #fromMap(Map) 095 */ 096 public AnnotationAttributes(AnnotationAttributes other) { 097 super(other); 098 this.annotationType = other.annotationType; 099 this.displayName = other.displayName; 100 this.validated = other.validated; 101 } 102 103 /** 104 * Create a new, empty {@link AnnotationAttributes} instance for the 105 * specified {@code annotationType}. 106 * @param annotationType the type of annotation represented by this 107 * {@code AnnotationAttributes} instance; never {@code null} 108 * @since 4.2 109 */ 110 public AnnotationAttributes(Class<? extends Annotation> annotationType) { 111 Assert.notNull(annotationType, "'annotationType' must not be null"); 112 this.annotationType = annotationType; 113 this.displayName = annotationType.getName(); 114 } 115 116 /** 117 * Create a possibly already validated new, empty 118 * {@link AnnotationAttributes} instance for the specified 119 * {@code annotationType}. 120 * @param annotationType the type of annotation represented by this 121 * {@code AnnotationAttributes} instance; never {@code null} 122 * @param validated if the attributes are considered already validated 123 * @since 5.2 124 */ 125 AnnotationAttributes(Class<? extends Annotation> annotationType, boolean validated) { 126 Assert.notNull(annotationType, "'annotationType' must not be null"); 127 this.annotationType = annotationType; 128 this.displayName = annotationType.getName(); 129 this.validated = validated; 130 } 131 132 /** 133 * Create a new, empty {@link AnnotationAttributes} instance for the 134 * specified {@code annotationType}. 135 * @param annotationType the annotation type name represented by this 136 * {@code AnnotationAttributes} instance; never {@code null} 137 * @param classLoader the ClassLoader to try to load the annotation type on, 138 * or {@code null} to just store the annotation type name 139 * @since 4.3.2 140 */ 141 public AnnotationAttributes(String annotationType, @Nullable ClassLoader classLoader) { 142 Assert.notNull(annotationType, "'annotationType' must not be null"); 143 this.annotationType = getAnnotationType(annotationType, classLoader); 144 this.displayName = annotationType; 145 } 146 147 @SuppressWarnings("unchecked") 148 @Nullable 149 private static Class<? extends Annotation> getAnnotationType(String annotationType, @Nullable ClassLoader classLoader) { 150 if (classLoader != null) { 151 try { 152 return (Class<? extends Annotation>) classLoader.loadClass(annotationType); 153 } 154 catch (ClassNotFoundException ex) { 155 // Annotation Class not resolvable 156 } 157 } 158 return null; 159 } 160 161 162 /** 163 * Get the type of annotation represented by this {@code AnnotationAttributes}. 164 * @return the annotation type, or {@code null} if unknown 165 * @since 4.2 166 */ 167 @Nullable 168 public Class<? extends Annotation> annotationType() { 169 return this.annotationType; 170 } 171 172 /** 173 * Get the value stored under the specified {@code attributeName} as a string. 174 * @param attributeName the name of the attribute to get; 175 * never {@code null} or empty 176 * @return the value 177 * @throws IllegalArgumentException if the attribute does not exist or 178 * if it is not of the expected type 179 */ 180 public String getString(String attributeName) { 181 return getRequiredAttribute(attributeName, String.class); 182 } 183 184 /** 185 * Get the value stored under the specified {@code attributeName} as an 186 * array of strings. 187 * <p>If the value stored under the specified {@code attributeName} is 188 * a string, it will be wrapped in a single-element array before 189 * returning it. 190 * @param attributeName the name of the attribute to get; 191 * never {@code null} or empty 192 * @return the value 193 * @throws IllegalArgumentException if the attribute does not exist or 194 * if it is not of the expected type 195 */ 196 public String[] getStringArray(String attributeName) { 197 return getRequiredAttribute(attributeName, String[].class); 198 } 199 200 /** 201 * Get the value stored under the specified {@code attributeName} as a boolean. 202 * @param attributeName the name of the attribute to get; 203 * never {@code null} or empty 204 * @return the value 205 * @throws IllegalArgumentException if the attribute does not exist or 206 * if it is not of the expected type 207 */ 208 public boolean getBoolean(String attributeName) { 209 return getRequiredAttribute(attributeName, Boolean.class); 210 } 211 212 /** 213 * Get the value stored under the specified {@code attributeName} as a number. 214 * @param attributeName the name of the attribute to get; 215 * never {@code null} or empty 216 * @return the value 217 * @throws IllegalArgumentException if the attribute does not exist or 218 * if it is not of the expected type 219 */ 220 @SuppressWarnings("unchecked") 221 public <N extends Number> N getNumber(String attributeName) { 222 return (N) getRequiredAttribute(attributeName, Number.class); 223 } 224 225 /** 226 * Get the value stored under the specified {@code attributeName} as an enum. 227 * @param attributeName the name of the attribute to get; 228 * never {@code null} or empty 229 * @return the value 230 * @throws IllegalArgumentException if the attribute does not exist or 231 * if it is not of the expected type 232 */ 233 @SuppressWarnings("unchecked") 234 public <E extends Enum<?>> E getEnum(String attributeName) { 235 return (E) getRequiredAttribute(attributeName, Enum.class); 236 } 237 238 /** 239 * Get the value stored under the specified {@code attributeName} as a class. 240 * @param attributeName the name of the attribute to get; 241 * never {@code null} or empty 242 * @return the value 243 * @throws IllegalArgumentException if the attribute does not exist or 244 * if it is not of the expected type 245 */ 246 @SuppressWarnings("unchecked") 247 public <T> Class<? extends T> getClass(String attributeName) { 248 return getRequiredAttribute(attributeName, Class.class); 249 } 250 251 /** 252 * Get the value stored under the specified {@code attributeName} as an 253 * array of classes. 254 * <p>If the value stored under the specified {@code attributeName} is a class, 255 * it will be wrapped in a single-element array before returning it. 256 * @param attributeName the name of the attribute to get; 257 * never {@code null} or empty 258 * @return the value 259 * @throws IllegalArgumentException if the attribute does not exist or 260 * if it is not of the expected type 261 */ 262 public Class<?>[] getClassArray(String attributeName) { 263 return getRequiredAttribute(attributeName, Class[].class); 264 } 265 266 /** 267 * Get the {@link AnnotationAttributes} stored under the specified 268 * {@code attributeName}. 269 * <p>Note: if you expect an actual annotation, invoke 270 * {@link #getAnnotation(String, Class)} instead. 271 * @param attributeName the name of the attribute to get; 272 * never {@code null} or empty 273 * @return the {@code AnnotationAttributes} 274 * @throws IllegalArgumentException if the attribute does not exist or 275 * if it is not of the expected type 276 */ 277 public AnnotationAttributes getAnnotation(String attributeName) { 278 return getRequiredAttribute(attributeName, AnnotationAttributes.class); 279 } 280 281 /** 282 * Get the annotation of type {@code annotationType} stored under the 283 * specified {@code attributeName}. 284 * @param attributeName the name of the attribute to get; 285 * never {@code null} or empty 286 * @param annotationType the expected annotation type; never {@code null} 287 * @return the annotation 288 * @throws IllegalArgumentException if the attribute does not exist or 289 * if it is not of the expected type 290 * @since 4.2 291 */ 292 public <A extends Annotation> A getAnnotation(String attributeName, Class<A> annotationType) { 293 return getRequiredAttribute(attributeName, annotationType); 294 } 295 296 /** 297 * Get the array of {@link AnnotationAttributes} stored under the specified 298 * {@code attributeName}. 299 * <p>If the value stored under the specified {@code attributeName} is 300 * an instance of {@code AnnotationAttributes}, it will be wrapped in 301 * a single-element array before returning it. 302 * <p>Note: if you expect an actual array of annotations, invoke 303 * {@link #getAnnotationArray(String, Class)} instead. 304 * @param attributeName the name of the attribute to get; 305 * never {@code null} or empty 306 * @return the array of {@code AnnotationAttributes} 307 * @throws IllegalArgumentException if the attribute does not exist or 308 * if it is not of the expected type 309 */ 310 public AnnotationAttributes[] getAnnotationArray(String attributeName) { 311 return getRequiredAttribute(attributeName, AnnotationAttributes[].class); 312 } 313 314 /** 315 * Get the array of type {@code annotationType} stored under the specified 316 * {@code attributeName}. 317 * <p>If the value stored under the specified {@code attributeName} is 318 * an {@code Annotation}, it will be wrapped in a single-element array 319 * before returning it. 320 * @param attributeName the name of the attribute to get; 321 * never {@code null} or empty 322 * @param annotationType the expected annotation type; never {@code null} 323 * @return the annotation array 324 * @throws IllegalArgumentException if the attribute does not exist or 325 * if it is not of the expected type 326 * @since 4.2 327 */ 328 @SuppressWarnings("unchecked") 329 public <A extends Annotation> A[] getAnnotationArray(String attributeName, Class<A> annotationType) { 330 Object array = Array.newInstance(annotationType, 0); 331 return (A[]) getRequiredAttribute(attributeName, array.getClass()); 332 } 333 334 /** 335 * Get the value stored under the specified {@code attributeName}, 336 * ensuring that the value is of the {@code expectedType}. 337 * <p>If the {@code expectedType} is an array and the value stored 338 * under the specified {@code attributeName} is a single element of the 339 * component type of the expected array type, the single element will be 340 * wrapped in a single-element array of the appropriate type before 341 * returning it. 342 * @param attributeName the name of the attribute to get; 343 * never {@code null} or empty 344 * @param expectedType the expected type; never {@code null} 345 * @return the value 346 * @throws IllegalArgumentException if the attribute does not exist or 347 * if it is not of the expected type 348 */ 349 @SuppressWarnings("unchecked") 350 private <T> T getRequiredAttribute(String attributeName, Class<T> expectedType) { 351 Assert.hasText(attributeName, "'attributeName' must not be null or empty"); 352 Object value = get(attributeName); 353 assertAttributePresence(attributeName, value); 354 assertNotException(attributeName, value); 355 if (!expectedType.isInstance(value) && expectedType.isArray() && 356 expectedType.getComponentType().isInstance(value)) { 357 Object array = Array.newInstance(expectedType.getComponentType(), 1); 358 Array.set(array, 0, value); 359 value = array; 360 } 361 assertAttributeType(attributeName, value, expectedType); 362 return (T) value; 363 } 364 365 private void assertAttributePresence(String attributeName, Object attributeValue) { 366 Assert.notNull(attributeValue, () -> String.format( 367 "Attribute '%s' not found in attributes for annotation [%s]", 368 attributeName, this.displayName)); 369 } 370 371 private void assertNotException(String attributeName, Object attributeValue) { 372 if (attributeValue instanceof Throwable) { 373 throw new IllegalArgumentException(String.format( 374 "Attribute '%s' for annotation [%s] was not resolvable due to exception [%s]", 375 attributeName, this.displayName, attributeValue), (Throwable) attributeValue); 376 } 377 } 378 379 private void assertAttributeType(String attributeName, Object attributeValue, Class<?> expectedType) { 380 if (!expectedType.isInstance(attributeValue)) { 381 throw new IllegalArgumentException(String.format( 382 "Attribute '%s' is of type %s, but %s was expected in attributes for annotation [%s]", 383 attributeName, attributeValue.getClass().getSimpleName(), expectedType.getSimpleName(), 384 this.displayName)); 385 } 386 } 387 388 @Override 389 public String toString() { 390 Iterator<Map.Entry<String, Object>> entries = entrySet().iterator(); 391 StringBuilder sb = new StringBuilder("{"); 392 while (entries.hasNext()) { 393 Map.Entry<String, Object> entry = entries.next(); 394 sb.append(entry.getKey()); 395 sb.append('='); 396 sb.append(valueToString(entry.getValue())); 397 sb.append(entries.hasNext() ? ", " : ""); 398 } 399 sb.append("}"); 400 return sb.toString(); 401 } 402 403 private String valueToString(Object value) { 404 if (value == this) { 405 return "(this Map)"; 406 } 407 if (value instanceof Object[]) { 408 return "[" + StringUtils.arrayToDelimitedString((Object[]) value, ", ") + "]"; 409 } 410 return String.valueOf(value); 411 } 412 413 414 /** 415 * Return an {@link AnnotationAttributes} instance based on the given map. 416 * <p>If the map is already an {@code AnnotationAttributes} instance, it 417 * will be cast and returned immediately without creating a new instance. 418 * Otherwise a new instance will be created by passing the supplied map 419 * to the {@link #AnnotationAttributes(Map)} constructor. 420 * @param map original source of annotation attribute <em>key-value</em> pairs 421 */ 422 @Nullable 423 public static AnnotationAttributes fromMap(@Nullable Map<String, Object> map) { 424 if (map == null) { 425 return null; 426 } 427 if (map instanceof AnnotationAttributes) { 428 return (AnnotationAttributes) map; 429 } 430 return new AnnotationAttributes(map); 431 } 432 433}