AnnotationUtils.java 40.5 KB
Newer Older
A
Arjen Poutsma 已提交
1
/*
2
 * Copyright 2002-2015 the original author or authors.
A
Arjen Poutsma 已提交
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.core.annotation;

import java.lang.annotation.Annotation;
20
import java.lang.reflect.AnnotatedElement;
A
Arjen Poutsma 已提交
21
import java.lang.reflect.Method;
22 23 24 25
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
26
import java.util.List;
A
Arjen Poutsma 已提交
27
import java.util.Map;
28
import java.util.Set;
A
Arjen Poutsma 已提交
29

30 31 32
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

A
Arjen Poutsma 已提交
33 34
import org.springframework.core.BridgeMethodResolver;
import org.springframework.util.Assert;
35
import org.springframework.util.ConcurrentReferenceHashMap;
36
import org.springframework.util.ObjectUtils;
37
import org.springframework.util.ReflectionUtils;
38
import org.springframework.util.StringUtils;
A
Arjen Poutsma 已提交
39 40

/**
41 42 43 44 45 46
 * General utility methods for working with annotations, handling meta-annotations,
 * bridge methods (which the compiler generates for generic declarations) as well
 * as super methods (for optional <em>annotation inheritance</em>).
 *
 * <p>Note that most of the features of this class are not provided by the
 * JDK's introspection facilities themselves.
A
Arjen Poutsma 已提交
47
 *
S
Sam Brannen 已提交
48 49 50 51 52 53 54 55
 * <p>As a general rule for runtime-retained annotations (e.g. for transaction
 * control, authorization, or service exposure), always use the lookup methods
 * on this class (e.g., {@link #findAnnotation(Method, Class)},
 * {@link #getAnnotation(Method, Class)}, and {@link #getAnnotations(Method)})
 * instead of the plain annotation lookup methods in the JDK. You can still
 * explicitly choose between a <em>get</em> lookup on the given class level only
 * ({@link #getAnnotation(Method, Class)}) and a <em>find</em> lookup in the entire
 * inheritance hierarchy of the given method ({@link #findAnnotation(Method, Class)}).
A
Arjen Poutsma 已提交
56
 *
57 58 59 60 61 62 63
 * <h3>Meta-annotation Support</h3>
 * <p>Most {@code find*()} methods and some {@code get*()} methods in this class
 * provide support for meta-annotations. Consult the Javadoc for each method in
 * this class for details. For support for meta-annotations with
 * <em>attribute overrides</em> in <em>composed annotations</em>, use
 * {@link AnnotatedElementUtils} instead.
 *
64 65 66 67 68 69
 * <h3>Search Scope</h3>
 * <p>The search algorithms used by methods in this class stop searching for
 * an annotation once the first annotation of the specified type has been
 * found. As a consequence, additional annotations of the specified type will
 * be silently ignored.
 *
A
Arjen Poutsma 已提交
70 71 72 73
 * @author Rob Harrop
 * @author Juergen Hoeller
 * @author Sam Brannen
 * @author Mark Fisher
C
Chris Beams 已提交
74
 * @author Chris Beams
75
 * @author Phillip Webb
A
Arjen Poutsma 已提交
76
 * @since 2.0
77 78 79
 * @see java.lang.reflect.AnnotatedElement#getAnnotations()
 * @see java.lang.reflect.AnnotatedElement#getAnnotation(Class)
 * @see java.lang.reflect.AnnotatedElement#getDeclaredAnnotations()
A
Arjen Poutsma 已提交
80 81 82 83
 */
public abstract class AnnotationUtils {

	/** The attribute name for annotations with a single element */
84
	public static final String VALUE = "value";
A
Arjen Poutsma 已提交
85

86

87 88 89 90 91
	private static final Map<AnnotationCacheKey, Annotation> findAnnotationCache =
			new ConcurrentReferenceHashMap<AnnotationCacheKey, Annotation>(256);

	private static final Map<Class<?>, Boolean> annotatedInterfaceCache =
			new ConcurrentReferenceHashMap<Class<?>, Boolean>(256);
92

93 94
	private static transient Log logger;

J
Juergen Hoeller 已提交
95

96 97
	/**
	 * Get a single {@link Annotation} of {@code annotationType} from the supplied
98 99
	 * annotation: either the given annotation itself or a direct meta-annotation
	 * thereof.
100 101 102
	 * <p>Note that this method supports only a single level of meta-annotations.
	 * For support for arbitrary levels of meta-annotations, use one of the
	 * {@code find*()} methods instead.
103
	 * @param ann the Annotation to check
J
Juergen Hoeller 已提交
104
	 * @param annotationType the annotation type to look for, both locally and as a meta-annotation
105
	 * @return the first matching annotation, or {@code null} if not found
106 107 108
	 * @since 4.0
	 */
	@SuppressWarnings("unchecked")
109
	public static <A extends Annotation> A getAnnotation(Annotation ann, Class<A> annotationType) {
110
		if (annotationType.isInstance(ann)) {
111
			return (A) ann;
112
		}
113 114 115 116 117
		try {
			return ann.annotationType().getAnnotation(annotationType);
		}
		catch (Exception ex) {
			// Assuming nested Class values not resolvable within annotation attributes...
118
			logIntrospectionFailure(ann.annotationType(), ex);
119 120
			return null;
		}
121 122
	}

123
	/**
C
Chris Beams 已提交
124
	 * Get a single {@link Annotation} of {@code annotationType} from the supplied
125 126 127 128 129
	 * {@link AnnotatedElement}, where the {@code AnnotatedElement} is either
	 * directly annotated or meta-annotated with the {@code annotationType}.
	 * <p>Note that this method supports only a single level of meta-annotations.
	 * For support for arbitrary levels of meta-annotations, use
	 * {@link #findAnnotation(AnnotatedElement, Class)} instead.
130
	 * @param annotatedElement the {@code AnnotatedElement} from which to get the annotation
J
Juergen Hoeller 已提交
131
	 * @param annotationType the annotation type to look for, both locally and as a meta-annotation
132
	 * @return the first matching annotation, or {@code null} if not found
C
Chris Beams 已提交
133
	 * @since 3.1
134
	 */
135
	public static <A extends Annotation> A getAnnotation(AnnotatedElement annotatedElement, Class<A> annotationType) {
136
		try {
137
			A ann = annotatedElement.getAnnotation(annotationType);
138
			if (ann == null) {
139
				for (Annotation metaAnn : annotatedElement.getAnnotations()) {
140 141 142 143
					ann = metaAnn.annotationType().getAnnotation(annotationType);
					if (ann != null) {
						break;
					}
144 145
				}
			}
146 147 148 149
			return ann;
		}
		catch (Exception ex) {
			// Assuming nested Class values not resolvable within annotation attributes...
150
			logIntrospectionFailure(annotatedElement, ex);
151
			return null;
152 153 154
		}
	}

155
	/**
156 157 158
	 * Get a single {@link Annotation} of {@code annotationType} from the
	 * supplied {@link Method}, where the method is either directly annotated
	 * or meta-annotated with the {@code annotationType}.
159
	 * <p>Correctly handles bridge {@link Method Methods} generated by the compiler.
160 161 162
	 * <p>Note that this method supports only a single level of meta-annotations.
	 * For support for arbitrary levels of meta-annotations, use
	 * {@link #findAnnotation(Method, Class)} instead.
163 164
	 * @param method the method to look for annotations on
	 * @param annotationType the annotation type to look for
165
	 * @return the first matching annotation, or {@code null} if not found
166 167 168 169 170 171 172 173 174 175 176 177
	 * @see org.springframework.core.BridgeMethodResolver#findBridgedMethod(Method)
	 * @see #getAnnotation(AnnotatedElement, Class)
	 */
	public static <A extends Annotation> A getAnnotation(Method method, Class<A> annotationType) {
		Method resolvedMethod = BridgeMethodResolver.findBridgedMethod(method);
		return getAnnotation((AnnotatedElement) resolvedMethod, annotationType);
	}

	/**
	 * Get all {@link Annotation Annotations} that are <em>present</em> on the
	 * supplied {@link AnnotatedElement}.
	 * <p>Meta-annotations will <em>not</em> be searched.
178
	 * @param annotatedElement the Method, Constructor or Field to retrieve annotations from
179 180 181
	 * @return the annotations found, an empty array, or {@code null} if not
	 * resolvable (e.g. because nested Class values in annotation attributes
	 * failed to resolve at runtime)
182 183 184 185 186 187 188 189 190 191
	 * @since 4.0.8
	 */
	public static Annotation[] getAnnotations(AnnotatedElement annotatedElement) {
		try {
			return annotatedElement.getAnnotations();
		}
		catch (Exception ex) {
			// Assuming nested Class values not resolvable within annotation attributes...
			logIntrospectionFailure(annotatedElement, ex);
		}
192
		return null;
193 194
	}

A
Arjen Poutsma 已提交
195
	/**
196 197
	 * Get all {@link Annotation Annotations} that are <em>present</em on the
	 * supplied {@link Method}.
A
Arjen Poutsma 已提交
198
	 * <p>Correctly handles bridge {@link Method Methods} generated by the compiler.
199
	 * <p>Meta-annotations will <em>not</em> be searched.
200
	 * @param method the Method to retrieve annotations from
201 202 203
	 * @return the annotations found, an empty array, or {@code null} if not
	 * resolvable (e.g. because nested Class values in annotation attributes
	 * failed to resolve at runtime)
A
Arjen Poutsma 已提交
204
	 * @see org.springframework.core.BridgeMethodResolver#findBridgedMethod(Method)
205
	 * @see AnnotatedElement#getAnnotations()
A
Arjen Poutsma 已提交
206 207
	 */
	public static Annotation[] getAnnotations(Method method) {
208 209 210 211 212
		try {
			return BridgeMethodResolver.findBridgedMethod(method).getAnnotations();
		}
		catch (Exception ex) {
			// Assuming nested Class values not resolvable within annotation attributes...
213
			logIntrospectionFailure(method, ex);
214
		}
215
		return null;
A
Arjen Poutsma 已提交
216 217 218
	}

	/**
219 220 221 222
	 * Get the possibly repeating {@link Annotation}s of {@code annotationType}
	 * from the supplied {@link Method}.
	 * <p>Deals with both a single direct annotation and repeating annotations
	 * nested within a containing annotation.
223
	 * <p>Correctly handles bridge {@link Method Methods} generated by the compiler.
224 225
	 * <p>Meta-annotations will be searched if the annotation is not
	 * <em>directly present</em> on the supplied method.
226 227
	 * @param method the method to look for annotations on
	 * @param containerAnnotationType the class of the container that holds the annotations
J
Juergen Hoeller 已提交
228
	 * @param annotationType the annotation type to look for
229
	 * @return the annotations found or an empty set; never {@code null}
230
	 * @since 4.0
J
Juergen Hoeller 已提交
231
	 * @see org.springframework.core.BridgeMethodResolver#findBridgedMethod(Method)
232
	 * @see java.lang.annotation.Repeatable
233 234 235
	 */
	public static <A extends Annotation> Set<A> getRepeatableAnnotation(Method method,
			Class<? extends Annotation> containerAnnotationType, Class<A> annotationType) {
J
Juergen Hoeller 已提交
236

237
		Method resolvedMethod = BridgeMethodResolver.findBridgedMethod(method);
J
Juergen Hoeller 已提交
238
		return getRepeatableAnnotation((AnnotatedElement) resolvedMethod, containerAnnotationType, annotationType);
239 240 241 242
	}

	/**
	 * Get the possibly repeating {@link Annotation}s of {@code annotationType} from the
243 244 245 246 247
	 * supplied {@link AnnotatedElement}.
	 * <p>Deals with both a single direct annotation and repeating annotations
	 * nested within a containing annotation.
	 * <p>Meta-annotations will be searched if the annotation is not
	 * <em>directly present</em> on the supplied element.
248 249
	 * @param annotatedElement the element to look for annotations on
	 * @param containerAnnotationType the class of the container that holds the annotations
J
Juergen Hoeller 已提交
250
	 * @param annotationType the annotation type to look for
251
	 * @return the annotations found or an empty set; never {@code null}
252
	 * @since 4.0
253
	 * @see java.lang.annotation.Repeatable
254 255 256
	 */
	public static <A extends Annotation> Set<A> getRepeatableAnnotation(AnnotatedElement annotatedElement,
			Class<? extends Annotation> containerAnnotationType, Class<A> annotationType) {
J
Juergen Hoeller 已提交
257

258 259 260 261
		try {
			if (annotatedElement.getAnnotations().length > 0) {
				return new AnnotationCollector<A>(containerAnnotationType, annotationType).getResult(annotatedElement);
			}
262
		}
263 264
		catch (Exception ex) {
			// Assuming nested Class values not resolvable within annotation attributes...
265
			logIntrospectionFailure(annotatedElement, ex);
266 267
		}
		return Collections.emptySet();
268 269
	}

A
Arjen Poutsma 已提交
270
	/**
271 272 273 274 275 276 277 278 279 280 281 282
	 * Find a single {@link Annotation} of {@code annotationType} on the
	 * supplied {@link AnnotatedElement}.
	 * <p>Meta-annotations will be searched if the annotation is not
	 * <em>directly present</em> on the supplied element.
	 * <p><strong>Warning</strong>: this method operates generically on
	 * annotated elements. In other words, this method does not execute
	 * specialized search algorithms for classes or methods. If you require
	 * the more specific semantics of {@link #findAnnotation(Class, Class)}
	 * or {@link #findAnnotation(Method, Class)}, invoke one of those methods
	 * instead.
	 * @param annotatedElement the {@code AnnotatedElement} on which to find the annotation
	 * @param annotationType the annotation type to look for, both locally and as a meta-annotation
283
	 * @return the first matching annotation, or {@code null} if not found
284 285 286 287 288 289 290 291 292 293 294 295 296 297 298
	 * @since 4.2
	 */
	public static <A extends Annotation> A findAnnotation(AnnotatedElement annotatedElement, Class<A> annotationType) {
		// Do NOT store result in the findAnnotationCache since doing so could break
		// findAnnotation(Class, Class) and findAnnotation(Method, Class).
		return findAnnotation(annotatedElement, annotationType, new HashSet<Annotation>());
	}

	/**
	 * Perform the search algorithm for {@link #findAnnotation(AnnotatedElement, Class)}
	 * avoiding endless recursion by tracking which annotations have already
	 * been <em>visited</em>.
	 * @param annotatedElement the {@code AnnotatedElement} on which to find the annotation
	 * @param annotationType the annotation type to look for, both locally and as a meta-annotation
	 * @param visited the set of annotations that have already been visited
299
	 * @return the first matching annotation, or {@code null} if not found
300 301 302
	 * @since 4.2
	 */
	@SuppressWarnings("unchecked")
303
	private static <A extends Annotation> A findAnnotation(AnnotatedElement annotatedElement, Class<A> annotationType, Set<Annotation> visited) {
304 305 306 307 308
		Assert.notNull(annotatedElement, "AnnotatedElement must not be null");
		try {
			Annotation[] anns = annotatedElement.getDeclaredAnnotations();
			for (Annotation ann : anns) {
				if (ann.annotationType().equals(annotationType)) {
309
					return (A) ann;
310 311 312 313
				}
			}
			for (Annotation ann : anns) {
				if (!isInJavaLangAnnotationPackage(ann) && visited.add(ann)) {
314
					A annotation = findAnnotation((AnnotatedElement) ann.annotationType(), annotationType, visited);
315 316 317 318 319 320 321 322 323 324 325 326 327 328 329
					if (annotation != null) {
						return annotation;
					}
				}
			}
		}
		catch (Exception ex) {
			// Assuming nested Class values not resolvable within annotation attributes...
			logIntrospectionFailure(annotatedElement, ex);
		}
		return null;
	}

	/**
	 * Find a single {@link Annotation} of {@code annotationType} on the supplied
J
Juergen Hoeller 已提交
330
	 * {@link Method}, traversing its super methods (i.e., from superclasses and
S
Sam Brannen 已提交
331
	 * interfaces) if no annotation can be found on the given method itself.
332 333 334
	 * <p>Correctly handles bridge {@link Method Methods} generated by the compiler.
	 * <p>Meta-annotations will be searched if the annotation is not
	 * <em>directly present</em> on the method.
S
Sam Brannen 已提交
335 336
	 * <p>Annotations on methods are not inherited by default, so we need to handle
	 * this explicitly.
A
Arjen Poutsma 已提交
337
	 * @param method the method to look for annotations on
J
Juergen Hoeller 已提交
338
	 * @param annotationType the annotation type to look for
339
	 * @return the first matching annotation, or {@code null} if not found
340
	 * @see #getAnnotation(Method, Class)
A
Arjen Poutsma 已提交
341
	 */
342
	@SuppressWarnings("unchecked")
A
Arjen Poutsma 已提交
343
	public static <A extends Annotation> A findAnnotation(Method method, Class<A> annotationType) {
344 345
		AnnotationCacheKey cacheKey = new AnnotationCacheKey(method, annotationType);
		A result = (A) findAnnotationCache.get(cacheKey);
346

347
		if (result == null) {
348 349 350
			Method resolvedMethod = BridgeMethodResolver.findBridgedMethod(method);
			result = findAnnotation((AnnotatedElement) resolvedMethod, annotationType);

351
			if (result == null) {
352
				result = searchOnInterfaces(method, annotationType, method.getDeclaringClass().getInterfaces());
A
Arjen Poutsma 已提交
353
			}
354 355

			Class<?> clazz = method.getDeclaringClass();
356 357 358 359 360 361 362
			while (result == null) {
				clazz = clazz.getSuperclass();
				if (clazz == null || clazz.equals(Object.class)) {
					break;
				}
				try {
					Method equivalentMethod = clazz.getDeclaredMethod(method.getName(), method.getParameterTypes());
363 364
					Method resolvedEquivalentMethod = BridgeMethodResolver.findBridgedMethod(equivalentMethod);
					result = findAnnotation((AnnotatedElement) resolvedEquivalentMethod, annotationType);
365 366 367 368 369 370 371
				}
				catch (NoSuchMethodException ex) {
					// No equivalent method found
				}
				if (result == null) {
					result = searchOnInterfaces(method, annotationType, clazz.getInterfaces());
				}
372
			}
373

374 375 376
			if (result != null) {
				findAnnotationCache.put(cacheKey, result);
			}
A
Arjen Poutsma 已提交
377
		}
378

379
		return result;
A
Arjen Poutsma 已提交
380 381
	}

382
	private static <A extends Annotation> A searchOnInterfaces(Method method, Class<A> annotationType, Class<?>... ifcs) {
383
		A annotation = null;
J
Juergen Hoeller 已提交
384
		for (Class<?> iface : ifcs) {
385 386 387 388 389 390 391 392 393 394 395
			if (isInterfaceWithAnnotatedMethods(iface)) {
				try {
					Method equivalentMethod = iface.getMethod(method.getName(), method.getParameterTypes());
					annotation = getAnnotation(equivalentMethod, annotationType);
				}
				catch (NoSuchMethodException ex) {
					// Skip this interface - it doesn't have the method...
				}
				if (annotation != null) {
					break;
				}
396
			}
397 398 399 400
		}
		return annotation;
	}

401
	static boolean isInterfaceWithAnnotatedMethods(Class<?> iface) {
402 403
		Boolean flag = annotatedInterfaceCache.get(iface);
		if (flag != null) {
404
			return flag.booleanValue();
405
		}
406
		Boolean found = Boolean.FALSE;
407 408 409
		for (Method ifcMethod : iface.getMethods()) {
			try {
				if (ifcMethod.getAnnotations().length > 0) {
410
					found = Boolean.TRUE;
411
					break;
412
				}
413 414 415
			}
			catch (Exception ex) {
				// Assuming nested Class values not resolvable within annotation attributes...
416
				logIntrospectionFailure(ifcMethod, ex);
417 418
			}
		}
419
		annotatedInterfaceCache.put(iface, found);
420
		return found.booleanValue();
421 422
	}

A
Arjen Poutsma 已提交
423
	/**
424 425 426 427
	 * Find a single {@link Annotation} of {@code annotationType} on the
	 * supplied {@link Class}, traversing its interfaces, annotations, and
	 * superclasses if the annotation is not <em>present</em> on the given class
	 * itself.
S
Sam Brannen 已提交
428
	 * <p>This method explicitly handles class-level annotations which are not
429 430
	 * declared as {@link java.lang.annotation.Inherited inherited} <em>as well
	 * as meta-annotations and annotations on interfaces</em>.
S
Sam Brannen 已提交
431 432
	 * <p>The algorithm operates as follows:
	 * <ol>
433 434
	 * <li>Search for the annotation on the given class and return it if found.
	 * <li>Recursively search through all annotations that the given class declares.
435
	 * <li>Recursively search through all interfaces that the given class declares.
436
	 * <li>Recursively search through the superclass hierarchy of the given class.
S
Sam Brannen 已提交
437
	 * </ol>
438 439 440
	 * <p>Note: in this context, the term <em>recursively</em> means that the search
	 * process continues by returning to step #1 with the current interface,
	 * annotation, or superclass as the class to look for annotations on.
A
Arjen Poutsma 已提交
441
	 * @param clazz the class to look for annotations on
442
	 * @param annotationType the type of annotation to look for
443
	 * @return the first matching annotation, or {@code null} if not found
A
Arjen Poutsma 已提交
444
	 */
445
	@SuppressWarnings("unchecked")
A
Arjen Poutsma 已提交
446
	public static <A extends Annotation> A findAnnotation(Class<?> clazz, Class<A> annotationType) {
447 448 449 450 451 452 453 454 455
		AnnotationCacheKey cacheKey = new AnnotationCacheKey(clazz, annotationType);
		A result = (A) findAnnotationCache.get(cacheKey);
		if (result == null) {
			result = findAnnotation(clazz, annotationType, new HashSet<Annotation>());
			if (result != null) {
				findAnnotationCache.put(cacheKey, result);
			}
		}
		return result;
456 457 458 459 460
	}

	/**
	 * Perform the search algorithm for {@link #findAnnotation(Class, Class)},
	 * avoiding endless recursion by tracking which annotations have already
S
Sam Brannen 已提交
461
	 * been <em>visited</em>.
462 463
	 * @param clazz the class to look for annotations on
	 * @param annotationType the type of annotation to look for
S
Sam Brannen 已提交
464
	 * @param visited the set of annotations that have already been visited
465
	 * @return the first matching annotation, or {@code null} if not found
466
	 */
467
	@SuppressWarnings("unchecked")
J
Juergen Hoeller 已提交
468
	private static <A extends Annotation> A findAnnotation(Class<?> clazz, Class<A> annotationType, Set<Annotation> visited) {
A
Arjen Poutsma 已提交
469
		Assert.notNull(clazz, "Class must not be null");
470 471 472 473 474 475 476

		try {
			Annotation[] anns = clazz.getDeclaredAnnotations();
			for (Annotation ann : anns) {
				if (ann.annotationType().equals(annotationType)) {
					return (A) ann;
				}
477
			}
478 479 480 481 482 483
			for (Annotation ann : anns) {
				if (!isInJavaLangAnnotationPackage(ann) && visited.add(ann)) {
					A annotation = findAnnotation(ann.annotationType(), annotationType, visited);
					if (annotation != null) {
						return annotation;
					}
484
				}
485
			}
A
Arjen Poutsma 已提交
486
		}
487 488
		catch (Exception ex) {
			// Assuming nested Class values not resolvable within annotation attributes...
489
			logIntrospectionFailure(clazz, ex);
490 491 492
			return null;
		}

A
Arjen Poutsma 已提交
493
		for (Class<?> ifc : clazz.getInterfaces()) {
494
			A annotation = findAnnotation(ifc, annotationType, visited);
A
Arjen Poutsma 已提交
495 496 497 498
			if (annotation != null) {
				return annotation;
			}
		}
499

500 501
		Class<?> superclass = clazz.getSuperclass();
		if (superclass == null || superclass.equals(Object.class)) {
A
Arjen Poutsma 已提交
502 503
			return null;
		}
S
Sam Brannen 已提交
504
		return findAnnotation(superclass, annotationType, visited);
A
Arjen Poutsma 已提交
505 506 507
	}

	/**
508 509 510 511 512
	 * Find the first {@link Class} in the inheritance hierarchy of the specified {@code clazz}
	 * (including the specified {@code clazz} itself) which declares an annotation for the
	 * specified {@code annotationType}, or {@code null} if not found. If the supplied
	 * {@code clazz} is {@code null}, {@code null} will be returned.
	 * <p>If the supplied {@code clazz} is an interface, only the interface itself will be checked;
J
Juergen Hoeller 已提交
513
	 * the inheritance hierarchy for interfaces will not be traversed.
514
	 * <p>Meta-annotations will <em>not</em> be searched.
J
Juergen Hoeller 已提交
515 516 517
	 * <p>The standard {@link Class} API does not provide a mechanism for determining which class
	 * in an inheritance hierarchy actually declares an {@link Annotation}, so we need to handle
	 * this explicitly.
J
Juergen Hoeller 已提交
518 519
	 * @param annotationType the annotation type to look for, both locally and as a meta-annotation
	 * @param clazz the class on which to check for the annotation (may be {@code null})
520
	 * @return the first {@link Class} in the inheritance hierarchy of the specified {@code clazz}
521
	 * which declares an annotation of the specified {@code annotationType}, or {@code null}
J
Juergen Hoeller 已提交
522
	 * if not found
A
Arjen Poutsma 已提交
523 524
	 * @see Class#isAnnotationPresent(Class)
	 * @see Class#getDeclaredAnnotations()
525 526
	 * @see #findAnnotationDeclaringClassForTypes(List, Class)
	 * @see #isAnnotationDeclaredLocally(Class, Class)
A
Arjen Poutsma 已提交
527 528 529 530 531 532
	 */
	public static Class<?> findAnnotationDeclaringClass(Class<? extends Annotation> annotationType, Class<?> clazz) {
		Assert.notNull(annotationType, "Annotation type must not be null");
		if (clazz == null || clazz.equals(Object.class)) {
			return null;
		}
533 534 535 536
		if (isAnnotationDeclaredLocally(annotationType, clazz)) {
			return clazz;
		}
		return findAnnotationDeclaringClass(annotationType, clazz.getSuperclass());
537 538 539 540 541 542 543 544 545 546 547
	}

	/**
	 * Find the first {@link Class} in the inheritance hierarchy of the specified
	 * {@code clazz} (including the specified {@code clazz} itself) which declares
	 * at least one of the specified {@code annotationTypes}, or {@code null} if
	 * none of the specified annotation types could be found.
	 * <p>If the supplied {@code clazz} is {@code null}, {@code null} will be
	 * returned.
	 * <p>If the supplied {@code clazz} is an interface, only the interface itself
	 * will be checked; the inheritance hierarchy for interfaces will not be traversed.
548
	 * <p>Meta-annotations will <em>not</em> be searched.
549 550 551 552 553 554 555 556 557 558 559
	 * <p>The standard {@link Class} API does not provide a mechanism for determining
	 * which class in an inheritance hierarchy actually declares one of several
	 * candidate {@linkplain Annotation annotations}, so we need to handle this
	 * explicitly.
	 * @param annotationTypes the list of Class objects corresponding to the
	 * annotation types
	 * @param clazz the Class object corresponding to the class on which to check
	 * for the annotations, or {@code null}
	 * @return the first {@link Class} in the inheritance hierarchy of the specified
	 * {@code clazz} which declares an annotation of at least one of the specified
	 * {@code annotationTypes}, or {@code null} if not found
560
	 * @since 3.2.2
561 562 563 564 565
	 * @see Class#isAnnotationPresent(Class)
	 * @see Class#getDeclaredAnnotations()
	 * @see #findAnnotationDeclaringClass(Class, Class)
	 * @see #isAnnotationDeclaredLocally(Class, Class)
	 */
566
	public static Class<?> findAnnotationDeclaringClassForTypes(List<Class<? extends Annotation>> annotationTypes, Class<?> clazz) {
567 568 569 570 571 572 573 574 575 576
		Assert.notEmpty(annotationTypes, "The list of annotation types must not be empty");
		if (clazz == null || clazz.equals(Object.class)) {
			return null;
		}
		for (Class<? extends Annotation> annotationType : annotationTypes) {
			if (isAnnotationDeclaredLocally(annotationType, clazz)) {
				return clazz;
			}
		}
		return findAnnotationDeclaringClassForTypes(annotationTypes, clazz.getSuperclass());
A
Arjen Poutsma 已提交
577 578 579
	}

	/**
580 581 582 583
	 * Determine whether an annotation of the specified {@code annotationType} is
	 * declared locally (i.e., <em>directly present</em>) on the supplied
	 * {@code clazz}. The supplied {@link Class} may represent any type.
	 * <p>Meta-annotations will <em>not</em> be searched.
J
Juergen Hoeller 已提交
584
	 * <p>Note: This method does <strong>not</strong> determine if the annotation is
S
Sam Brannen 已提交
585 586 587
	 * {@linkplain java.lang.annotation.Inherited inherited}. For greater clarity
	 * regarding inherited annotations, consider using
	 * {@link #isAnnotationInherited(Class, Class)} instead.
A
Arjen Poutsma 已提交
588
	 * @param annotationType the Class object corresponding to the annotation type
J
Juergen Hoeller 已提交
589
	 * @param clazz the Class object corresponding to the class on which to check for the annotation
590 591 592 593
	 * @return {@code true} if an annotation of the specified {@code annotationType}
	 * is <em>directly present</em> on the supplied {@code clazz}
	 * @see java.lang.Class#getDeclaredAnnotations()
	 * @see java.lang.Class#getDeclaredAnnotation(Class)
A
Arjen Poutsma 已提交
594 595 596 597 598 599
	 * @see #isAnnotationInherited(Class, Class)
	 */
	public static boolean isAnnotationDeclaredLocally(Class<? extends Annotation> annotationType, Class<?> clazz) {
		Assert.notNull(annotationType, "Annotation type must not be null");
		Assert.notNull(clazz, "Class must not be null");
		boolean declaredLocally = false;
600
		try {
601 602
			for (Annotation ann : clazz.getDeclaredAnnotations()) {
				if (ann.annotationType().equals(annotationType)) {
603 604 605
					declaredLocally = true;
					break;
				}
A
Arjen Poutsma 已提交
606 607
			}
		}
608 609
		catch (Exception ex) {
			// Assuming nested Class values not resolvable within annotation attributes...
610
			logIntrospectionFailure(clazz, ex);
611
		}
A
Arjen Poutsma 已提交
612 613 614 615
		return declaredLocally;
	}

	/**
616
	 * Determine whether an annotation of the specified {@code annotationType} is <em>present</em>
S
Sam Brannen 已提交
617 618
	 * on the supplied {@code clazz} and is {@linkplain java.lang.annotation.Inherited inherited}
	 * (i.e., not declared locally for the class).
619
	 * <p>Meta-annotations will <em>not</em> be searched.
620
	 * <p>If the supplied {@code clazz} is an interface, only the interface itself will be checked.
J
Juergen Hoeller 已提交
621
	 * In accordance with standard meta-annotation semantics, the inheritance hierarchy for interfaces
S
Sam Brannen 已提交
622 623
	 * will not be traversed. See the {@linkplain java.lang.annotation.Inherited Javadoc} for the
	 * {@code @Inherited} meta-annotation for further details regarding annotation inheritance.
A
Arjen Poutsma 已提交
624
	 * @param annotationType the Class object corresponding to the annotation type
J
Juergen Hoeller 已提交
625
	 * @param clazz the Class object corresponding to the class on which to check for the annotation
626
	 * @return {@code true} if an annotation of the specified {@code annotationType} is present
S
Sam Brannen 已提交
627
	 * on the supplied {@code clazz} and is <em>inherited</em>
A
Arjen Poutsma 已提交
628 629 630 631 632 633 634 635 636
	 * @see Class#isAnnotationPresent(Class)
	 * @see #isAnnotationDeclaredLocally(Class, Class)
	 */
	public static boolean isAnnotationInherited(Class<? extends Annotation> annotationType, Class<?> clazz) {
		Assert.notNull(annotationType, "Annotation type must not be null");
		Assert.notNull(clazz, "Class must not be null");
		return (clazz.isAnnotationPresent(annotationType) && !isAnnotationDeclaredLocally(annotationType, clazz));
	}

637
	/**
J
Juergen Hoeller 已提交
638
	 * Determine if the supplied {@link Annotation} is defined in the core JDK
639
	 * {@code java.lang.annotation} package.
J
Juergen Hoeller 已提交
640
	 * @param annotation the annotation to check (never {@code null})
641 642 643 644
	 * @return {@code true} if the annotation is in the {@code java.lang.annotation} package
	 */
	public static boolean isInJavaLangAnnotationPackage(Annotation annotation) {
		Assert.notNull(annotation, "Annotation must not be null");
645 646 647 648 649 650 651 652 653 654 655 656 657
		return isInJavaLangAnnotationPackage(annotation.annotationType().getName());
	}

	/**
	 * Determine if the {@link Annotation} with the supplied name is defined
	 * in the core JDK {@code java.lang.annotation} package.
	 * @param annotationType the name of the annotation type to check (never {@code null} or empty)
	 * @return {@code true} if the annotation is in the {@code java.lang.annotation} package
	 * @since 4.2
	 */
	public static boolean isInJavaLangAnnotationPackage(String annotationType) {
		Assert.hasText(annotationType, "annotationType must not be null or empty");
		return annotationType.startsWith("java.lang.annotation");
658 659
	}

A
Arjen Poutsma 已提交
660
	/**
J
Juergen Hoeller 已提交
661
	 * Retrieve the given annotation's attributes as a {@link Map}, preserving all
662 663 664 665
	 * attribute types.
	 * <p>Equivalent to calling {@link #getAnnotationAttributes(Annotation, boolean, boolean)}
	 * with the {@code classValuesAsString} and {@code nestedAnnotationsAsMap} parameters
	 * set to {@code false}.
J
Juergen Hoeller 已提交
666 667
	 * <p>Note: This method actually returns an {@link AnnotationAttributes} instance.
	 * However, the {@code Map} signature has been preserved for binary compatibility.
A
Arjen Poutsma 已提交
668
	 * @param annotation the annotation to retrieve the attributes for
J
Juergen Hoeller 已提交
669
	 * @return the Map of annotation attributes, with attribute names as keys and
670 671
	 * corresponding attribute values as values; never {@code null}
	 * @see #getAnnotationAttributes(Annotation, boolean, boolean)
A
Arjen Poutsma 已提交
672 673
	 */
	public static Map<String, Object> getAnnotationAttributes(Annotation annotation) {
674
		return getAnnotationAttributes(annotation, false, false);
675 676 677
	}

	/**
678 679 680
	 * Retrieve the given annotation's attributes as a {@link Map}.
	 * <p>Equivalent to calling {@link #getAnnotationAttributes(Annotation, boolean, boolean)}
	 * with the {@code nestedAnnotationsAsMap} parameter set to {@code false}.
J
Juergen Hoeller 已提交
681 682
	 * <p>Note: This method actually returns an {@link AnnotationAttributes} instance.
	 * However, the {@code Map} signature has been preserved for binary compatibility.
683
	 * @param annotation the annotation to retrieve the attributes for
684
	 * @param classValuesAsString whether to turn Class references into Strings (for
S
Sam Brannen 已提交
685
	 * compatibility with {@link org.springframework.core.type.AnnotationMetadata})
J
Juergen Hoeller 已提交
686
	 * or to preserve them as Class references
J
Juergen Hoeller 已提交
687
	 * @return the Map of annotation attributes, with attribute names as keys and
688 689
	 * corresponding attribute values as values; never {@code null}
	 * @see #getAnnotationAttributes(Annotation, boolean, boolean)
690
	 */
691
	public static Map<String, Object> getAnnotationAttributes(Annotation annotation, boolean classValuesAsString) {
692 693 694 695 696
		return getAnnotationAttributes(annotation, classValuesAsString, false);
	}

	/**
	 * Retrieve the given annotation's attributes as an {@link AnnotationAttributes}
J
Juergen Hoeller 已提交
697 698 699
	 * map structure.
	 * <p>This method provides fully recursive annotation reading capabilities on par with
	 * the reflection-based {@link org.springframework.core.type.StandardAnnotationMetadata}.
700
	 * @param annotation the annotation to retrieve the attributes for
701
	 * @param classValuesAsString whether to convert Class references into Strings (for
S
Sam Brannen 已提交
702
	 * compatibility with {@link org.springframework.core.type.AnnotationMetadata})
J
Juergen Hoeller 已提交
703
	 * or to preserve them as Class references
704 705
	 * @param nestedAnnotationsAsMap whether to turn nested Annotation instances into
	 * {@link AnnotationAttributes} maps (for compatibility with
S
Sam Brannen 已提交
706
	 * {@link org.springframework.core.type.AnnotationMetadata}) or to preserve them as
707 708
	 * Annotation instances
	 * @return the annotation attributes (a specialized Map) with attribute names as keys
709
	 * and corresponding attribute values as values; never {@code null}
710 711
	 * @since 3.1.1
	 */
712 713
	public static AnnotationAttributes getAnnotationAttributes(Annotation annotation, boolean classValuesAsString,
			boolean nestedAnnotationsAsMap) {
714 715

		AnnotationAttributes attrs = new AnnotationAttributes();
A
Arjen Poutsma 已提交
716
		Method[] methods = annotation.annotationType().getDeclaredMethods();
717
		for (Method method : methods) {
A
Arjen Poutsma 已提交
718 719
			if (method.getParameterTypes().length == 0 && method.getReturnType() != void.class) {
				try {
720
					ReflectionUtils.makeAccessible(method);
721
					Object value = method.invoke(annotation);
722
					attrs.put(method.getName(), adaptValue(value, classValuesAsString, nestedAnnotationsAsMap));
A
Arjen Poutsma 已提交
723 724 725 726 727 728 729 730 731
				}
				catch (Exception ex) {
					throw new IllegalStateException("Could not obtain annotation attribute values", ex);
				}
			}
		}
		return attrs;
	}

732 733 734 735
	/**
	 * Adapt the given value according to the given class and nested annotation settings.
	 * @param value the annotation attribute value
	 * @param classValuesAsString whether to turn Class references into Strings (for
S
Sam Brannen 已提交
736
	 * compatibility with {@link org.springframework.core.type.AnnotationMetadata})
737 738 739
	 * or to preserve them as Class references
	 * @param nestedAnnotationsAsMap whether to turn nested Annotation instances into
	 * {@link AnnotationAttributes} maps (for compatibility with
S
Sam Brannen 已提交
740
	 * {@link org.springframework.core.type.AnnotationMetadata}) or to preserve them as
741 742 743 744 745 746 747 748 749
	 * Annotation instances
	 * @return the adapted value, or the original value if no adaptation is needed
	 */
	static Object adaptValue(Object value, boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
		if (classValuesAsString) {
			if (value instanceof Class) {
				value = ((Class<?>) value).getName();
			}
			else if (value instanceof Class[]) {
750
				Class<?>[] clazzArray = (Class<?>[]) value;
751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773
				String[] newValue = new String[clazzArray.length];
				for (int i = 0; i < clazzArray.length; i++) {
					newValue[i] = clazzArray[i].getName();
				}
				value = newValue;
			}
		}
		if (nestedAnnotationsAsMap && value instanceof Annotation) {
			return getAnnotationAttributes((Annotation) value, classValuesAsString, true);
		}
		else if (nestedAnnotationsAsMap && value instanceof Annotation[]) {
			Annotation[] realAnnotations = (Annotation[]) value;
			AnnotationAttributes[] mappedAnnotations = new AnnotationAttributes[realAnnotations.length];
			for (int i = 0; i < realAnnotations.length; i++) {
				mappedAnnotations[i] = getAnnotationAttributes(realAnnotations[i], classValuesAsString, true);
			}
			return mappedAnnotations;
		}
		else {
			return value;
		}
	}

A
Arjen Poutsma 已提交
774
	/**
775
	 * Retrieve the <em>value</em> of the {@code value} attribute of a
J
Juergen Hoeller 已提交
776
	 * single-element Annotation, given an annotation instance.
A
Arjen Poutsma 已提交
777
	 * @param annotation the annotation instance from which to retrieve the value
778
	 * @return the attribute value, or {@code null} if not found
A
Arjen Poutsma 已提交
779 780 781 782 783 784 785
	 * @see #getValue(Annotation, String)
	 */
	public static Object getValue(Annotation annotation) {
		return getValue(annotation, VALUE);
	}

	/**
J
Juergen Hoeller 已提交
786
	 * Retrieve the <em>value</em> of a named attribute, given an annotation instance.
A
Arjen Poutsma 已提交
787 788
	 * @param annotation the annotation instance from which to retrieve the value
	 * @param attributeName the name of the attribute value to retrieve
789
	 * @return the attribute value, or {@code null} if not found
J
Juergen Hoeller 已提交
790
	 * @see #getValue(Annotation)
A
Arjen Poutsma 已提交
791 792
	 */
	public static Object getValue(Annotation annotation, String attributeName) {
793 794 795
		if (annotation == null || !StringUtils.hasLength(attributeName)) {
			return null;
		}
A
Arjen Poutsma 已提交
796
		try {
J
Juergen Hoeller 已提交
797
			Method method = annotation.annotationType().getDeclaredMethod(attributeName);
798
			ReflectionUtils.makeAccessible(method);
A
Arjen Poutsma 已提交
799 800 801 802 803 804 805 806
			return method.invoke(annotation);
		}
		catch (Exception ex) {
			return null;
		}
	}

	/**
807
	 * Retrieve the <em>default value</em> of the {@code value} attribute
J
Juergen Hoeller 已提交
808 809
	 * of a single-element Annotation, given an annotation instance.
	 * @param annotation the annotation instance from which to retrieve the default value
810
	 * @return the default value, or {@code null} if not found
A
Arjen Poutsma 已提交
811 812 813 814 815 816 817
	 * @see #getDefaultValue(Annotation, String)
	 */
	public static Object getDefaultValue(Annotation annotation) {
		return getDefaultValue(annotation, VALUE);
	}

	/**
J
Juergen Hoeller 已提交
818
	 * Retrieve the <em>default value</em> of a named attribute, given an annotation instance.
J
Juergen Hoeller 已提交
819
	 * @param annotation the annotation instance from which to retrieve the default value
A
Arjen Poutsma 已提交
820
	 * @param attributeName the name of the attribute value to retrieve
821
	 * @return the default value of the named attribute, or {@code null} if not found
A
Arjen Poutsma 已提交
822 823 824
	 * @see #getDefaultValue(Class, String)
	 */
	public static Object getDefaultValue(Annotation annotation, String attributeName) {
825 826 827
		if (annotation == null) {
			return null;
		}
A
Arjen Poutsma 已提交
828 829 830 831
		return getDefaultValue(annotation.annotationType(), attributeName);
	}

	/**
832
	 * Retrieve the <em>default value</em> of the {@code value} attribute
J
Juergen Hoeller 已提交
833 834
	 * of a single-element Annotation, given the {@link Class annotation type}.
	 * @param annotationType the <em>annotation type</em> for which the default value should be retrieved
835
	 * @return the default value, or {@code null} if not found
A
Arjen Poutsma 已提交
836 837 838 839 840 841 842
	 * @see #getDefaultValue(Class, String)
	 */
	public static Object getDefaultValue(Class<? extends Annotation> annotationType) {
		return getDefaultValue(annotationType, VALUE);
	}

	/**
J
Juergen Hoeller 已提交
843 844
	 * Retrieve the <em>default value</em> of a named attribute, given the
	 * {@link Class annotation type}.
J
Juergen Hoeller 已提交
845
	 * @param annotationType the <em>annotation type</em> for which the default value should be retrieved
A
Arjen Poutsma 已提交
846
	 * @param attributeName the name of the attribute value to retrieve.
847
	 * @return the default value of the named attribute, or {@code null} if not found
A
Arjen Poutsma 已提交
848 849 850
	 * @see #getDefaultValue(Annotation, String)
	 */
	public static Object getDefaultValue(Class<? extends Annotation> annotationType, String attributeName) {
851 852 853
		if (annotationType == null || !StringUtils.hasLength(attributeName)) {
			return null;
		}
A
Arjen Poutsma 已提交
854
		try {
J
Juergen Hoeller 已提交
855
			return annotationType.getDeclaredMethod(attributeName).getDefaultValue();
A
Arjen Poutsma 已提交
856 857 858 859 860 861
		}
		catch (Exception ex) {
			return null;
		}
	}

862

863 864 865 866 867 868 869
	/**
	 * Log an introspection failure (in particular {@code TypeNotPresentExceptions}) -
	 * before moving on, pretending there were no annotations on this specific element.
	 * @param element the element that we tried to introspect annotations on
	 * @param ex the exception that we encountered
	 */
	static void logIntrospectionFailure(AnnotatedElement element, Exception ex) {
870 871 872 873 874
		Log loggerToUse = logger;
		if (loggerToUse == null) {
			loggerToUse = LogFactory.getLog(AnnotationUtils.class);
			logger = loggerToUse;
		}
875 876 877 878 879 880 881 882 883 884 885 886
		if (element instanceof Class && Annotation.class.isAssignableFrom((Class<?>) element)) {
			// Meta-annotation lookup on an annotation type
			if (logger.isDebugEnabled()) {
				logger.debug("Failed to introspect meta-annotations on [" + element + "]: " + ex);
			}
		}
		else {
			// Direct annotation lookup on regular Class, Method, Field
			if (loggerToUse.isInfoEnabled()) {
				logger.info("Failed to introspect annotations on [" + element + "]: " + ex);
			}

887 888 889 890
		}
	}


891
	/**
892
	 * Cache key for the AnnotatedElement cache.
893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924
	 */
	private static class AnnotationCacheKey {

		private final AnnotatedElement element;

		private final Class<? extends Annotation> annotationType;

		public AnnotationCacheKey(AnnotatedElement element, Class<? extends Annotation> annotationType) {
			this.element = element;
			this.annotationType = annotationType;
		}

		@Override
		public boolean equals(Object other) {
			if (this == other) {
				return true;
			}
			if (!(other instanceof AnnotationCacheKey)) {
				return false;
			}
			AnnotationCacheKey otherKey = (AnnotationCacheKey) other;
			return (this.element.equals(otherKey.element) &&
					ObjectUtils.nullSafeEquals(this.annotationType, otherKey.annotationType));
		}

		@Override
		public int hashCode() {
			return (this.element.hashCode() * 29 + this.annotationType.hashCode());
		}
	}


925 926 927 928 929 930 931 932 933 934
	private static class AnnotationCollector<A extends Annotation> {

		private final Class<? extends Annotation> containerAnnotationType;

		private final Class<A> annotationType;

		private final Set<AnnotatedElement> visited = new HashSet<AnnotatedElement>();

		private final Set<A> result = new LinkedHashSet<A>();

J
Juergen Hoeller 已提交
935
		public AnnotationCollector(Class<? extends Annotation> containerAnnotationType, Class<A> annotationType) {
936 937 938 939 940 941 942 943 944 945
			this.containerAnnotationType = containerAnnotationType;
			this.annotationType = annotationType;
		}

		public Set<A> getResult(AnnotatedElement element) {
			process(element);
			return Collections.unmodifiableSet(this.result);
		}

		@SuppressWarnings("unchecked")
946 947 948 949
		private void process(AnnotatedElement element) {
			if (this.visited.add(element)) {
				try {
					for (Annotation ann : element.getAnnotations()) {
950 951
						Class<? extends Annotation> currentAnnotationType = ann.annotationType();
						if (ObjectUtils.nullSafeEquals(this.annotationType, currentAnnotationType)) {
952 953
							this.result.add((A) ann);
						}
954
						else if (ObjectUtils.nullSafeEquals(this.containerAnnotationType, currentAnnotationType)) {
955 956 957
							this.result.addAll(getValue(ann));
						}
						else if (!isInJavaLangAnnotationPackage(ann)) {
958
							process(currentAnnotationType);
959
						}
960 961
					}
				}
962 963 964
				catch (Exception ex) {
					logIntrospectionFailure(element, ex);
				}
965 966 967 968
			}
		}

		@SuppressWarnings("unchecked")
969
		private List<A> getValue(Annotation annotation) {
970 971
			try {
				Method method = annotation.annotationType().getDeclaredMethod("value");
972
				ReflectionUtils.makeAccessible(method);
973
				return Arrays.asList((A[]) method.invoke(annotation));
974 975
			}
			catch (Exception ex) {
976 977
				// Unable to read value from repeating annotation container -> ignore it.
				return Collections.emptyList();
978 979 980
			}
		}
	}
J
Juergen Hoeller 已提交
981

A
Arjen Poutsma 已提交
982
}