Merge from sbrannen/SPR-13067
* SPR-13067: Synthesize annotation from a map of attributes
This commit is contained in:
		
						commit
						a31d1bdf60
					
				|  | @ -32,7 +32,6 @@ import org.springframework.context.expression.AnnotatedElementKey; | |||
| import org.springframework.core.BridgeMethodResolver; | ||||
| import org.springframework.core.ResolvableType; | ||||
| import org.springframework.core.annotation.AnnotatedElementUtils; | ||||
| import org.springframework.core.annotation.AnnotationAttributes; | ||||
| import org.springframework.core.annotation.AnnotationUtils; | ||||
| import org.springframework.core.annotation.Order; | ||||
| import org.springframework.expression.EvaluationContext; | ||||
|  | @ -248,11 +247,9 @@ public class ApplicationListenerMethodAdapter implements GenericApplicationListe | |||
| 	 */ | ||||
| 	protected String getCondition() { | ||||
| 		if (this.condition == null) { | ||||
| 			AnnotationAttributes annotationAttributes = AnnotatedElementUtils | ||||
| 					.findAnnotationAttributes(this.method, EventListener.class); | ||||
| 			if (annotationAttributes != null) { | ||||
| 				String value = annotationAttributes.getString("condition"); | ||||
| 				this.condition = (value != null ? value : ""); | ||||
| 			EventListener eventListener = AnnotatedElementUtils.findAnnotation(this.method, EventListener.class); | ||||
| 			if (eventListener != null) { | ||||
| 				this.condition = eventListener.condition(); | ||||
| 			} | ||||
| 		} | ||||
| 		return this.condition; | ||||
|  |  | |||
|  | @ -0,0 +1,135 @@ | |||
| /* | ||||
|  * Copyright 2002-2015 the original author or authors. | ||||
|  * | ||||
|  * 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; | ||||
| import java.lang.reflect.AnnotatedElement; | ||||
| import java.lang.reflect.Method; | ||||
| import java.util.Map; | ||||
| 
 | ||||
| import org.springframework.util.Assert; | ||||
| import org.springframework.util.ObjectUtils; | ||||
| 
 | ||||
| /** | ||||
|  * Abstract base class for {@link AnnotationAttributeExtractor} implementations | ||||
|  * that transparently enforce attribute alias semantics for annotation | ||||
|  * attributes that are annotated with {@link AliasFor @AliasFor}. | ||||
|  * | ||||
|  * @author Sam Brannen | ||||
|  * @since 4.2 | ||||
|  * @see Annotation | ||||
|  * @see AliasFor | ||||
|  * @see AnnotationUtils#synthesizeAnnotation(Annotation, AnnotatedElement) | ||||
|  */ | ||||
| abstract class AbstractAliasAwareAnnotationAttributeExtractor implements AnnotationAttributeExtractor { | ||||
| 
 | ||||
| 	private final Class<? extends Annotation> annotationType; | ||||
| 
 | ||||
| 	private final AnnotatedElement annotatedElement; | ||||
| 
 | ||||
| 	private final Object source; | ||||
| 
 | ||||
| 	private final Map<String, String> attributeAliasMap; | ||||
| 
 | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Construct a new {@code AbstractAliasAwareAnnotationAttributeExtractor}. | ||||
| 	 * @param annotationType the annotation type to synthesize; never {@code null} | ||||
| 	 * @param annotatedElement the element that is annotated with the annotation | ||||
| 	 * of the supplied type; may be {@code null} if unknown | ||||
| 	 * @param source the underlying source of annotation attributes; never {@code null} | ||||
| 	 */ | ||||
| 	AbstractAliasAwareAnnotationAttributeExtractor(Class<? extends Annotation> annotationType, | ||||
| 			AnnotatedElement annotatedElement, Object source) { | ||||
| 		Assert.notNull(annotationType, "annotationType must not be null"); | ||||
| 		Assert.notNull(source, "source must not be null"); | ||||
| 		this.annotationType = annotationType; | ||||
| 		this.annotatedElement = annotatedElement; | ||||
| 		this.source = source; | ||||
| 		this.attributeAliasMap = AnnotationUtils.getAttributeAliasMap(annotationType); | ||||
| 	} | ||||
| 
 | ||||
| 	@Override | ||||
| 	public final Class<? extends Annotation> getAnnotationType() { | ||||
| 		return this.annotationType; | ||||
| 	} | ||||
| 
 | ||||
| 	@Override | ||||
| 	public final AnnotatedElement getAnnotatedElement() { | ||||
| 		return this.annotatedElement; | ||||
| 	} | ||||
| 
 | ||||
| 	@Override | ||||
| 	public Object getSource() { | ||||
| 		return this.source; | ||||
| 	} | ||||
| 
 | ||||
| 	@Override | ||||
| 	public final Object getAttributeValue(Method attributeMethod) { | ||||
| 		String attributeName = attributeMethod.getName(); | ||||
| 		Object attributeValue = getRawAttributeValue(attributeMethod); | ||||
| 
 | ||||
| 		String aliasName = this.attributeAliasMap.get(attributeName); | ||||
| 		if ((aliasName != null)) { | ||||
| 
 | ||||
| 			Object aliasValue = getRawAttributeValue(aliasName); | ||||
| 			Object defaultValue = AnnotationUtils.getDefaultValue(getAnnotationType(), attributeName); | ||||
| 
 | ||||
| 			if (!nullSafeEquals(attributeValue, aliasValue) && !nullSafeEquals(attributeValue, defaultValue) | ||||
| 					&& !nullSafeEquals(aliasValue, defaultValue)) { | ||||
| 				String elementName = (getAnnotatedElement() == null ? "unknown element" | ||||
| 						: getAnnotatedElement().toString()); | ||||
| 				String msg = String.format("In annotation [%s] declared on [%s] and synthesized from [%s], " | ||||
| 						+ "attribute [%s] and its alias [%s] are present with values of [%s] and [%s], " | ||||
| 						+ "but only one is permitted.", getAnnotationType().getName(), elementName, getSource(), | ||||
| 					attributeName, aliasName, nullSafeToString(attributeValue), nullSafeToString(aliasValue)); | ||||
| 				throw new AnnotationConfigurationException(msg); | ||||
| 			} | ||||
| 
 | ||||
| 			// If the user didn't declare the annotation with an explicit value, | ||||
| 			// return the value of the alias. | ||||
| 			if (nullSafeEquals(attributeValue, defaultValue)) { | ||||
| 				attributeValue = aliasValue; | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		return attributeValue; | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Get the raw, unmodified attribute value from the underlying | ||||
| 	 * {@linkplain #getSource source} that corresponds to the supplied | ||||
| 	 * attribute method. | ||||
| 	 */ | ||||
| 	protected abstract Object getRawAttributeValue(Method attributeMethod); | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Get the raw, unmodified attribute value from the underlying | ||||
| 	 * {@linkplain #getSource source} that corresponds to the supplied | ||||
| 	 * attribute name. | ||||
| 	 */ | ||||
| 	protected abstract Object getRawAttributeValue(String attributeName); | ||||
| 
 | ||||
| 	private static boolean nullSafeEquals(Object o1, Object o2) { | ||||
| 		return ObjectUtils.nullSafeEquals(o1, o2); | ||||
| 	} | ||||
| 
 | ||||
| 	private static String nullSafeToString(Object obj) { | ||||
| 		return ObjectUtils.nullSafeToString(obj); | ||||
| 	} | ||||
| 
 | ||||
| } | ||||
|  | @ -48,8 +48,8 @@ import org.springframework.util.StringUtils; | |||
|  * <h3>Annotation Attribute Overrides</h3> | ||||
|  * <p>Support for meta-annotations with <em>attribute overrides</em> in | ||||
|  * <em>composed annotations</em> is provided by all variants of the | ||||
|  * {@code getAnnotationAttributes()} and {@code findAnnotationAttributes()} | ||||
|  * methods. | ||||
|  * {@code getAnnotationAttributes()}, {@code findAnnotation()}, and | ||||
|  * {@code findAnnotationAttributes()} methods. | ||||
|  * | ||||
|  * <h3>Find vs. Get Semantics</h3> | ||||
|  * <p>The search algorithms used by methods in this class follow either | ||||
|  | @ -224,6 +224,9 @@ public class AnnotatedElementUtils { | |||
| 	 * merge that annotation's attributes with <em>matching</em> attributes from | ||||
| 	 * annotations in lower levels of the annotation hierarchy. | ||||
| 	 * | ||||
| 	 * <p>{@link AliasFor @AliasFor} semantics are fully supported, both | ||||
| 	 * within a single annotation and within the annotation hierarchy. | ||||
| 	 * | ||||
| 	 * <p>This method delegates to {@link #getAnnotationAttributes(AnnotatedElement, String, boolean, boolean)}, | ||||
| 	 * supplying {@code false} for {@code classValuesAsString} and {@code nestedAnnotationsAsMap}. | ||||
| 	 * | ||||
|  | @ -233,8 +236,8 @@ public class AnnotatedElementUtils { | |||
| 	 * @return the merged {@code AnnotationAttributes}, or {@code null} if | ||||
| 	 * not found | ||||
| 	 * @see #getAnnotationAttributes(AnnotatedElement, String, boolean, boolean) | ||||
| 	 * @see #findAnnotationAttributes(AnnotatedElement, Class) | ||||
| 	 * @see #findAnnotationAttributes(AnnotatedElement, String) | ||||
| 	 * @see #findAnnotationAttributes(AnnotatedElement, String, boolean, boolean) | ||||
| 	 * @see #findAnnotation(AnnotatedElement, Class) | ||||
| 	 * @see #getAllAnnotationAttributes(AnnotatedElement, String) | ||||
| 	 */ | ||||
| 	public static AnnotationAttributes getAnnotationAttributes(AnnotatedElement element, String annotationType) { | ||||
|  | @ -248,7 +251,9 @@ public class AnnotatedElementUtils { | |||
| 	 * annotations in lower levels of the annotation hierarchy. | ||||
| 	 * | ||||
| 	 * <p>Attributes from lower levels in the annotation hierarchy override | ||||
| 	 * attributes of the same name from higher levels. | ||||
| 	 * attributes of the same name from higher levels, and | ||||
| 	 * {@link AliasFor @AliasFor} semantics are fully supported, both | ||||
| 	 * within a single annotation and within the annotation hierarchy. | ||||
| 	 * | ||||
| 	 * <p>In contrast to {@link #getAllAnnotationAttributes}, the search | ||||
| 	 * algorithm used by this method will stop searching the annotation | ||||
|  | @ -269,8 +274,7 @@ public class AnnotatedElementUtils { | |||
| 	 * as Annotation instances | ||||
| 	 * @return the merged {@code AnnotationAttributes}, or {@code null} if | ||||
| 	 * not found | ||||
| 	 * @see #findAnnotationAttributes(AnnotatedElement, Class) | ||||
| 	 * @see #findAnnotationAttributes(AnnotatedElement, String) | ||||
| 	 * @see #findAnnotation(AnnotatedElement, Class) | ||||
| 	 * @see #findAnnotationAttributes(AnnotatedElement, String, boolean, boolean) | ||||
| 	 * @see #getAllAnnotationAttributes(AnnotatedElement, String, boolean, boolean) | ||||
| 	 */ | ||||
|  | @ -286,47 +290,59 @@ public class AnnotatedElementUtils { | |||
| 
 | ||||
| 	/** | ||||
| 	 * Find the first annotation of the specified {@code annotationType} within | ||||
| 	 * the annotation hierarchy <em>above</em> the supplied {@code element} and | ||||
| 	 * the annotation hierarchy <em>above</em> the supplied {@code element}, | ||||
| 	 * merge that annotation's attributes with <em>matching</em> attributes from | ||||
| 	 * annotations in lower levels of the annotation hierarchy. | ||||
| 	 * annotations in lower levels of the annotation hierarchy, and synthesize | ||||
| 	 * the result back into an annotation of the specified {@code annotationType}. | ||||
| 	 * | ||||
| 	 * <p>{@link AliasFor @AliasFor} semantics are fully supported, both | ||||
| 	 * within a single annotation and within the annotation hierarchy. | ||||
| 	 * | ||||
| 	 * <p>This method delegates to {@link #findAnnotationAttributes(AnnotatedElement, String, boolean, boolean)} | ||||
| 	 * supplying {@code false} for {@code classValuesAsString} and {@code nestedAnnotationsAsMap}. | ||||
| 	 * (supplying {@code false} for {@code classValuesAsString} and {@code nestedAnnotationsAsMap}) | ||||
| 	 * and {@link AnnotationUtils#synthesizeAnnotation(Map, Class, AnnotatedElement)}. | ||||
| 	 * | ||||
| 	 * @param element the annotated element; never {@code null} | ||||
| 	 * @param annotationType the annotation type to find; never {@code null} | ||||
| 	 * @return the merged {@code AnnotationAttributes}, or {@code null} if | ||||
| 	 * not found | ||||
| 	 * @return the merged, synthesized {@code Annotation}, or {@code null} if not found | ||||
| 	 * @since 4.2 | ||||
| 	 * @see #findAnnotationAttributes(AnnotatedElement, String) | ||||
| 	 * @see #findAnnotation(AnnotatedElement, String) | ||||
| 	 * @see #findAnnotationAttributes(AnnotatedElement, String, boolean, boolean) | ||||
| 	 * @see AnnotationUtils#synthesizeAnnotation(Map, Class, AnnotatedElement) | ||||
| 	 */ | ||||
| 	public static AnnotationAttributes findAnnotationAttributes(AnnotatedElement element, | ||||
| 			Class<? extends Annotation> annotationType) { | ||||
| 	public static <A extends Annotation> A findAnnotation(AnnotatedElement element, Class<A> annotationType) { | ||||
| 		Assert.notNull(annotationType, "annotationType must not be null"); | ||||
| 		return findAnnotationAttributes(element, annotationType.getName()); | ||||
| 		return findAnnotation(element, annotationType.getName()); | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Find the first annotation of the specified {@code annotationType} within | ||||
| 	 * the annotation hierarchy <em>above</em> the supplied {@code element} and | ||||
| 	 * the annotation hierarchy <em>above</em> the supplied {@code element}, | ||||
| 	 * merge that annotation's attributes with <em>matching</em> attributes from | ||||
| 	 * annotations in lower levels of the annotation hierarchy. | ||||
| 	 * annotations in lower levels of the annotation hierarchy, and synthesize | ||||
| 	 * the result back into an annotation of the specified {@code annotationType}. | ||||
| 	 * | ||||
| 	 * <p>{@link AliasFor @AliasFor} semantics are fully supported, both | ||||
| 	 * within a single annotation and within the annotation hierarchy. | ||||
| 	 * | ||||
| 	 * <p>This method delegates to {@link #findAnnotationAttributes(AnnotatedElement, String, boolean, boolean)} | ||||
| 	 * supplying {@code false} for {@code classValuesAsString} and {@code nestedAnnotationsAsMap}. | ||||
| 	 * (supplying {@code false} for {@code classValuesAsString} and {@code nestedAnnotationsAsMap}) | ||||
| 	 * and {@link AnnotationUtils#synthesizeAnnotation(Map, Class, AnnotatedElement)}. | ||||
| 	 * | ||||
| 	 * @param element the annotated element; never {@code null} | ||||
| 	 * @param annotationType the fully qualified class name of the annotation | ||||
| 	 * type to find; never {@code null} or empty | ||||
| 	 * @return the merged {@code AnnotationAttributes}, or {@code null} if | ||||
| 	 * not found | ||||
| 	 * @return the merged, synthesized {@code Annotation}, or {@code null} if not found | ||||
| 	 * @since 4.2 | ||||
| 	 * @see #findAnnotationAttributes(AnnotatedElement, Class) | ||||
| 	 * @see #findAnnotation(AnnotatedElement, Class) | ||||
| 	 * @see #findAnnotationAttributes(AnnotatedElement, String, boolean, boolean) | ||||
| 	 * @see AnnotationUtils#synthesizeAnnotation(Map, Class, AnnotatedElement) | ||||
| 	 */ | ||||
| 	public static AnnotationAttributes findAnnotationAttributes(AnnotatedElement element, String annotationType) { | ||||
| 		return findAnnotationAttributes(element, annotationType, false, false); | ||||
| 	@SuppressWarnings("unchecked") | ||||
| 	public static <A extends Annotation> A findAnnotation(AnnotatedElement element, String annotationType) { | ||||
| 		AnnotationAttributes attributes = findAnnotationAttributes(element, annotationType, false, false); | ||||
| 		return ((attributes != null) ? AnnotationUtils.synthesizeAnnotation(attributes, | ||||
| 			(Class<A>) attributes.annotationType(), element) : null); | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
|  | @ -336,7 +352,9 @@ public class AnnotatedElementUtils { | |||
| 	 * annotations in lower levels of the annotation hierarchy. | ||||
| 	 * | ||||
| 	 * <p>Attributes from lower levels in the annotation hierarchy override | ||||
| 	 * attributes of the same name from higher levels. | ||||
| 	 * attributes of the same name from higher levels, and | ||||
| 	 * {@link AliasFor @AliasFor} semantics are fully supported, both | ||||
| 	 * within a single annotation and within the annotation hierarchy. | ||||
| 	 * | ||||
| 	 * <p>In contrast to {@link #getAllAnnotationAttributes}, the search | ||||
| 	 * algorithm used by this method will stop searching the annotation | ||||
|  | @ -358,8 +376,7 @@ public class AnnotatedElementUtils { | |||
| 	 * @return the merged {@code AnnotationAttributes}, or {@code null} if | ||||
| 	 * not found | ||||
| 	 * @since 4.2 | ||||
| 	 * @see #findAnnotationAttributes(AnnotatedElement, Class) | ||||
| 	 * @see #findAnnotationAttributes(AnnotatedElement, String) | ||||
| 	 * @see #findAnnotation(AnnotatedElement, Class) | ||||
| 	 * @see #getAnnotationAttributes(AnnotatedElement, String, boolean, boolean) | ||||
| 	 */ | ||||
| 	public static AnnotationAttributes findAnnotationAttributes(AnnotatedElement element, String annotationType, | ||||
|  |  | |||
|  | @ -0,0 +1,62 @@ | |||
| /* | ||||
|  * Copyright 2002-2015 the original author or authors. | ||||
|  * | ||||
|  * 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; | ||||
| import java.lang.reflect.AnnotatedElement; | ||||
| import java.lang.reflect.Method; | ||||
| 
 | ||||
| /** | ||||
|  * An {@code AnnotationAttributeExtractor} is responsible for | ||||
|  * {@linkplain #getAttributeValue extracting} annotation attribute values | ||||
|  * from an underlying {@linkplain #getSource source} such as an | ||||
|  * {@code Annotation} or a {@code Map}. | ||||
|  * | ||||
|  * @author Sam Brannen | ||||
|  * @since 4.2 | ||||
|  * @see SynthesizedAnnotationInvocationHandler | ||||
|  */ | ||||
| interface AnnotationAttributeExtractor { | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Get the type of annotation that this extractor extracts attribute | ||||
| 	 * values for. | ||||
| 	 */ | ||||
| 	Class<? extends Annotation> getAnnotationType(); | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Get the element that is annotated with an annotation of the annotation | ||||
| 	 * type supported by this extractor. | ||||
| 	 * @return the annotated element, or {@code null} if unknown | ||||
| 	 */ | ||||
| 	AnnotatedElement getAnnotatedElement(); | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Get the underlying source of annotation attributes. | ||||
| 	 */ | ||||
| 	Object getSource(); | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Get the attribute value from the underlying {@linkplain #getSource source} | ||||
| 	 * that corresponds to the supplied attribute method. | ||||
| 	 * @param attributeMethod an attribute method from the annotation type | ||||
| 	 * supported by this extractor | ||||
| 	 * @return the value of the annotation attribute | ||||
| 	 */ | ||||
| 	Object getAttributeValue(Method attributeMethod); | ||||
| 
 | ||||
| } | ||||
|  | @ -809,8 +809,10 @@ public abstract class AnnotationUtils { | |||
| 	 * merging attributes within an annotation hierarchy. When running in <em>merge mode</em>, | ||||
| 	 * the following special rules apply: | ||||
| 	 * <ol> | ||||
| 	 * <li>The supplied annotation will <strong>not</strong> be | ||||
| 	 * {@linkplain #synthesizeAnnotation synthesized} before retrieving its attributes.</li> | ||||
| 	 * <li>The supplied annotation will <em>not</em> be | ||||
| 	 * {@linkplain #synthesizeAnnotation synthesized} before retrieving its attributes; | ||||
| 	 * however, nested annotations and arrays of nested annotations <em>will</em> be | ||||
| 	 * synthesized.</li> | ||||
| 	 * <li>Default values will be replaced with {@link #DEFAULT_VALUE_PLACEHOLDER}.</li> | ||||
| 	 * <li>The resulting, merged annotation attributes should eventually be | ||||
| 	 * {@linkplain #postProcessAnnotationAttributes post-processed} in order to | ||||
|  | @ -1051,12 +1053,13 @@ public abstract class AnnotationUtils { | |||
| 	 * @param annotation the annotation to synthesize | ||||
| 	 * @param annotatedElement the element that is annotated with the supplied | ||||
| 	 * annotation; may be {@code null} if unknown | ||||
| 	 * @return the synthesized annotation, if the supplied annotation is | ||||
| 	 * @return the synthesized annotation if the supplied annotation is | ||||
| 	 * <em>synthesizable</em>; {@code null} if the supplied annotation is | ||||
| 	 * {@code null}; otherwise, the supplied annotation unmodified | ||||
| 	 * {@code null}; otherwise the supplied annotation unmodified | ||||
| 	 * @throws AnnotationConfigurationException if invalid configuration of | ||||
| 	 * {@code @AliasFor} is detected | ||||
| 	 * @since 4.2 | ||||
| 	 * @see #synthesizeAnnotation(Map, Class, AnnotatedElement) | ||||
| 	 */ | ||||
| 	@SuppressWarnings("unchecked") | ||||
| 	public static <A extends Annotation> A synthesizeAnnotation(A annotation, AnnotatedElement annotatedElement) { | ||||
|  | @ -1068,20 +1071,58 @@ public abstract class AnnotationUtils { | |||
| 		} | ||||
| 
 | ||||
| 		Class<? extends Annotation> annotationType = annotation.annotationType(); | ||||
| 
 | ||||
| 		// No need to synthesize? | ||||
| 		if (!isSynthesizable(annotationType)) { | ||||
| 			return annotation; | ||||
| 		} | ||||
| 
 | ||||
| 		InvocationHandler handler = new SynthesizedAnnotationInvocationHandler(annotation, annotatedElement, | ||||
| 			getAttributeAliasMap(annotationType)); | ||||
| 		AnnotationAttributeExtractor attributeExtractor = new DefaultAnnotationAttributeExtractor(annotation, | ||||
| 			annotatedElement); | ||||
| 		InvocationHandler handler = new SynthesizedAnnotationInvocationHandler(attributeExtractor); | ||||
| 		A synthesizedAnnotation = (A) Proxy.newProxyInstance(ClassUtils.getDefaultClassLoader(), new Class<?>[] { | ||||
| 			(Class<A>) annotationType, SynthesizedAnnotation.class }, handler); | ||||
| 
 | ||||
| 		return synthesizedAnnotation; | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
| 	 * <em>Synthesize</em> the supplied map of annotation attributes by | ||||
| 	 * wrapping it in a dynamic proxy that implements an annotation of type | ||||
| 	 * {@code annotationType} and transparently enforces <em>attribute alias</em> | ||||
| 	 * semantics for annotation attributes that are annotated with | ||||
| 	 * {@link AliasFor @AliasFor}. | ||||
| 	 * <p>The supplied map must contain key-value pairs for every attribute | ||||
| 	 * defined by the supplied {@code annotationType}. | ||||
| 	 * <p>Note that {@link AnnotationAttributes} is a specialized type of | ||||
| 	 * {@link Map} that is a suitable candidate for this method's | ||||
| 	 * {@code attributes} argument. | ||||
| 	 * | ||||
| 	 * @param attributes the map of annotation attributes to synthesize | ||||
| 	 * @param annotationType the type of annotation to synthesize; never {@code null} | ||||
| 	 * @param annotatedElement the element that is annotated with the annotation | ||||
| 	 * corresponding to the supplied attributes; may be {@code null} if unknown | ||||
| 	 * @return the synthesized annotation, or {@code null} if the supplied attributes | ||||
| 	 * map is {@code null} | ||||
| 	 * @throws AnnotationConfigurationException if invalid configuration is detected | ||||
| 	 * @since 4.2 | ||||
| 	 * @see #synthesizeAnnotation(Annotation, AnnotatedElement) | ||||
| 	 */ | ||||
| 	@SuppressWarnings("unchecked") | ||||
| 	public static <A extends Annotation> A synthesizeAnnotation(Map<String, Object> attributes, | ||||
| 			Class<A> annotationType, AnnotatedElement annotatedElement) { | ||||
| 		Assert.notNull(annotationType, "annotationType must not be null"); | ||||
| 
 | ||||
| 		if (attributes == null) { | ||||
| 			return null; | ||||
| 		} | ||||
| 
 | ||||
| 		AnnotationAttributeExtractor attributeExtractor = new MapAnnotationAttributeExtractor(attributes, | ||||
| 			annotationType, annotatedElement); | ||||
| 		InvocationHandler handler = new SynthesizedAnnotationInvocationHandler(attributeExtractor); | ||||
| 		A synthesizedAnnotation = (A) Proxy.newProxyInstance(ClassUtils.getDefaultClassLoader(), new Class<?>[] { | ||||
| 			annotationType, SynthesizedAnnotation.class }, handler); | ||||
| 
 | ||||
| 		return synthesizedAnnotation; | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Get a map of all attribute alias pairs, declared via {@code @AliasFor} | ||||
|  | @ -1098,7 +1139,7 @@ public abstract class AnnotationUtils { | |||
| 	 * @return a map containing attribute alias pairs; never {@code null} | ||||
| 	 * @since 4.2 | ||||
| 	 */ | ||||
| 	private static Map<String, String> getAttributeAliasMap(Class<? extends Annotation> annotationType) { | ||||
| 	static Map<String, String> getAttributeAliasMap(Class<? extends Annotation> annotationType) { | ||||
| 		if (annotationType == null) { | ||||
| 			return Collections.emptyMap(); | ||||
| 		} | ||||
|  | @ -1334,7 +1375,7 @@ public abstract class AnnotationUtils { | |||
| 
 | ||||
| 		methods = new ArrayList<Method>(); | ||||
| 		for (Method method : annotationType.getDeclaredMethods()) { | ||||
| 			if ((method.getParameterTypes().length == 0) && (method.getReturnType() != void.class)) { | ||||
| 			if (isAttributeMethod(method)) { | ||||
| 				ReflectionUtils.makeAccessible(method); | ||||
| 				methods.add(method); | ||||
| 			} | ||||
|  | @ -1345,6 +1386,24 @@ public abstract class AnnotationUtils { | |||
| 		return methods; | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Determine if the supplied {@code method} is an annotation attribute method. | ||||
| 	 * @param method the method to check | ||||
| 	 * @return {@code true} if the method is an attribute method | ||||
| 	 */ | ||||
| 	static boolean isAttributeMethod(Method method) { | ||||
| 		return ((method != null) && (method.getParameterTypes().length == 0) && (method.getReturnType() != void.class)); | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Determine if the supplied method is an "annotationType" method. | ||||
| 	 * @return {@code true} if the method is an "annotationType" method | ||||
| 	 * @see Annotation#annotationType() | ||||
| 	 */ | ||||
| 	static boolean isAnnotationTypeMethod(Method method) { | ||||
| 		return ((method != null) && method.getName().equals("annotationType") && (method.getParameterTypes().length == 0)); | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Post-process the supplied {@link AnnotationAttributes}. | ||||
| 	 * | ||||
|  |  | |||
|  | @ -0,0 +1,65 @@ | |||
| /* | ||||
|  * Copyright 2002-2015 the original author or authors. | ||||
|  * | ||||
|  * 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; | ||||
| import java.lang.reflect.AnnotatedElement; | ||||
| import java.lang.reflect.Method; | ||||
| 
 | ||||
| import org.springframework.util.ReflectionUtils; | ||||
| 
 | ||||
| /** | ||||
|  * Default implementation of the {@link AnnotationAttributeExtractor} strategy | ||||
|  * that is backed by an {@link Annotation}. | ||||
|  * | ||||
|  * @author Sam Brannen | ||||
|  * @since 4.2 | ||||
|  * @see Annotation | ||||
|  * @see AliasFor | ||||
|  * @see AbstractAliasAwareAnnotationAttributeExtractor | ||||
|  * @see MapAnnotationAttributeExtractor | ||||
|  * @see AnnotationUtils#synthesizeAnnotation(Annotation, AnnotatedElement) | ||||
|  */ | ||||
| class DefaultAnnotationAttributeExtractor extends AbstractAliasAwareAnnotationAttributeExtractor { | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Construct a new {@code DefaultAnnotationAttributeExtractor}. | ||||
| 	 * @param annotation the annotation to synthesize; never {@code null} | ||||
| 	 * @param annotatedElement the element that is annotated with the supplied | ||||
| 	 * annotation; may be {@code null} if unknown | ||||
| 	 */ | ||||
| 	DefaultAnnotationAttributeExtractor(Annotation annotation, AnnotatedElement annotatedElement) { | ||||
| 		super(annotation.annotationType(), annotatedElement, annotation); | ||||
| 	} | ||||
| 
 | ||||
| 	@Override | ||||
| 	protected Object getRawAttributeValue(Method attributeMethod) { | ||||
| 		ReflectionUtils.makeAccessible(attributeMethod); | ||||
| 		return ReflectionUtils.invokeMethod(attributeMethod, getAnnotation()); | ||||
| 	} | ||||
| 
 | ||||
| 	@Override | ||||
| 	protected Object getRawAttributeValue(String attributeName) { | ||||
| 		Method attributeMethod = ReflectionUtils.findMethod(getAnnotation().annotationType(), attributeName); | ||||
| 		return getRawAttributeValue(attributeMethod); | ||||
| 	} | ||||
| 
 | ||||
| 	private Annotation getAnnotation() { | ||||
| 		return (Annotation) getSource(); | ||||
| 	} | ||||
| 
 | ||||
| } | ||||
|  | @ -0,0 +1,100 @@ | |||
| /* | ||||
|  * Copyright 2002-2015 the original author or authors. | ||||
|  * | ||||
|  * 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; | ||||
| import java.lang.reflect.AnnotatedElement; | ||||
| import java.lang.reflect.Method; | ||||
| import java.util.HashMap; | ||||
| import java.util.Map; | ||||
| 
 | ||||
| import org.springframework.util.ClassUtils; | ||||
| 
 | ||||
| import static org.springframework.core.annotation.AnnotationUtils.*; | ||||
| 
 | ||||
| /** | ||||
|  * Implementation of the {@link AnnotationAttributeExtractor} strategy that | ||||
|  * is backed by a {@link Map}. | ||||
|  * | ||||
|  * @author Sam Brannen | ||||
|  * @since 4.2 | ||||
|  * @see Annotation | ||||
|  * @see AliasFor | ||||
|  * @see AbstractAliasAwareAnnotationAttributeExtractor | ||||
|  * @see DefaultAnnotationAttributeExtractor | ||||
|  * @see AnnotationUtils#synthesizeAnnotation(Map, Class, AnnotatedElement) | ||||
|  */ | ||||
| class MapAnnotationAttributeExtractor extends AbstractAliasAwareAnnotationAttributeExtractor { | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Construct a new {@code MapAnnotationAttributeExtractor}. | ||||
| 	 * <p>The supplied map must contain key-value pairs for every attribute | ||||
| 	 * defined in the supplied {@code annotationType}. | ||||
| 	 * @param attributes the map of annotation attributes; never {@code null} | ||||
| 	 * @param annotationType the type of annotation to synthesize; never {@code null} | ||||
| 	 * @param annotatedElement the element that is annotated with the annotation | ||||
| 	 * of the supplied type; may be {@code null} if unknown | ||||
| 	 */ | ||||
| 	MapAnnotationAttributeExtractor(Map<String, Object> attributes, Class<? extends Annotation> annotationType, | ||||
| 			AnnotatedElement annotatedElement) { | ||||
| 		super(annotationType, annotatedElement, new HashMap<String, Object>(attributes)); | ||||
| 		validateAttributes(attributes, annotationType); | ||||
| 	} | ||||
| 
 | ||||
| 	@Override | ||||
| 	protected Object getRawAttributeValue(Method attributeMethod) { | ||||
| 		return getMap().get(attributeMethod.getName()); | ||||
| 	} | ||||
| 
 | ||||
| 	@Override | ||||
| 	protected Object getRawAttributeValue(String attributeName) { | ||||
| 		return getMap().get(attributeName); | ||||
| 	} | ||||
| 
 | ||||
| 	@SuppressWarnings("unchecked") | ||||
| 	private Map<String, Object> getMap() { | ||||
| 		return (Map<String, Object>) getSource(); | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Validate the supplied {@code attributes} map by verifying that it | ||||
| 	 * contains a non-null entry for each annotation attribute in the specified | ||||
| 	 * {@code annotationType} and that the type of the entry matches the | ||||
| 	 * return type for the corresponding annotation attribute. | ||||
| 	 */ | ||||
| 	private static void validateAttributes(Map<String, Object> attributes, Class<? extends Annotation> annotationType) { | ||||
| 		for (Method attributeMethod : getAttributeMethods(annotationType)) { | ||||
| 			String attributeName = attributeMethod.getName(); | ||||
| 
 | ||||
| 			Object attributeValue = attributes.get(attributeName); | ||||
| 			if (attributeValue == null) { | ||||
| 				throw new IllegalArgumentException(String.format( | ||||
| 					"Attributes map [%s] returned null for required attribute [%s] defined by annotation type [%s].", | ||||
| 					attributes, attributeName, annotationType.getName())); | ||||
| 			} | ||||
| 
 | ||||
| 			Class<?> returnType = attributeMethod.getReturnType(); | ||||
| 			if (!ClassUtils.isAssignable(returnType, attributeValue.getClass())) { | ||||
| 				throw new IllegalArgumentException(String.format( | ||||
| 					"Attributes map [%s] returned a value of type [%s] for attribute [%s], " | ||||
| 							+ "but a value of type [%s] is required as defined by annotation type [%s].", attributes, | ||||
| 					attributeValue.getClass().getName(), attributeName, returnType.getName(), annotationType.getName())); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| } | ||||
|  | @ -18,6 +18,7 @@ package org.springframework.core.annotation; | |||
| 
 | ||||
| import java.lang.annotation.Annotation; | ||||
| import java.lang.reflect.AnnotatedElement; | ||||
| import java.lang.reflect.Array; | ||||
| import java.lang.reflect.InvocationHandler; | ||||
| import java.lang.reflect.Method; | ||||
| import java.util.Arrays; | ||||
|  | @ -25,6 +26,7 @@ import java.util.Iterator; | |||
| import java.util.Map; | ||||
| import java.util.concurrent.ConcurrentHashMap; | ||||
| 
 | ||||
| import org.springframework.util.Assert; | ||||
| import org.springframework.util.ObjectUtils; | ||||
| import org.springframework.util.StringUtils; | ||||
| 
 | ||||
|  | @ -36,150 +38,141 @@ import static org.springframework.util.ReflectionUtils.*; | |||
|  * <em>synthesized</em> (i.e., wrapped in a dynamic proxy) with additional | ||||
|  * functionality. | ||||
|  * | ||||
|  * <p>{@code SynthesizedAnnotationInvocationHandler} transparently enforces | ||||
|  * attribute alias semantics for annotation attributes that are annotated | ||||
|  * with {@link AliasFor @AliasFor}. In addition, nested annotations and | ||||
|  * arrays of nested annotations will be synthesized upon first access (i.e., | ||||
|  * <em>lazily</em>). | ||||
|  * | ||||
|  * @author Sam Brannen | ||||
|  * @since 4.2 | ||||
|  * @see Annotation | ||||
|  * @see AliasFor | ||||
|  * @see AnnotationAttributeExtractor | ||||
|  * @see AnnotationUtils#synthesizeAnnotation(Annotation, AnnotatedElement) | ||||
|  */ | ||||
| class SynthesizedAnnotationInvocationHandler implements InvocationHandler { | ||||
| 
 | ||||
| 	private final AnnotatedElement annotatedElement; | ||||
| 	private final AnnotationAttributeExtractor attributeExtractor; | ||||
| 
 | ||||
| 	private final Annotation annotation; | ||||
| 
 | ||||
| 	private final Class<? extends Annotation> annotationType; | ||||
| 
 | ||||
| 	private final Map<String, String> aliasMap; | ||||
| 
 | ||||
| 	private final Map<String, Object> computedValueCache; | ||||
| 	private final Map<String, Object> valueCache = new ConcurrentHashMap<String, Object>(8); | ||||
| 
 | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Construct a new {@code SynthesizedAnnotationInvocationHandler}. | ||||
| 	 * | ||||
| 	 * @param annotation the annotation to synthesize | ||||
| 	 * @param annotatedElement the element that is annotated with the supplied | ||||
| 	 * annotation; may be {@code null} if unknown | ||||
| 	 * @param aliasMap the map of attribute alias pairs, declared via | ||||
| 	 * {@code @AliasFor} in the supplied annotation | ||||
| 	 * Construct a new {@code SynthesizedAnnotationInvocationHandler} for | ||||
| 	 * the supplied {@link AnnotationAttributeExtractor}. | ||||
| 	 * @param attributeExtractor the extractor to delegate to | ||||
| 	 */ | ||||
| 	SynthesizedAnnotationInvocationHandler(Annotation annotation, AnnotatedElement annotatedElement, | ||||
| 			Map<String, String> aliasMap) { | ||||
| 		this.annotatedElement = annotatedElement; | ||||
| 		this.annotation = annotation; | ||||
| 		this.annotationType = annotation.annotationType(); | ||||
| 		this.aliasMap = aliasMap; | ||||
| 		this.computedValueCache = new ConcurrentHashMap<String, Object>(aliasMap.size()); | ||||
| 	SynthesizedAnnotationInvocationHandler(AnnotationAttributeExtractor attributeExtractor) { | ||||
| 		Assert.notNull(attributeExtractor, "AnnotationAttributeExtractor must not be null"); | ||||
| 		this.attributeExtractor = attributeExtractor; | ||||
| 	} | ||||
| 
 | ||||
| 	@Override | ||||
| 	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { | ||||
| 		if (isEqualsMethod(method)) { | ||||
| 			return equals(proxy, args[0]); | ||||
| 			return annotationEquals(args[0]); | ||||
| 		} | ||||
| 		if (isHashCodeMethod(method)) { | ||||
| 			return hashCode(proxy); | ||||
| 			return annotationHashCode(); | ||||
| 		} | ||||
| 		if (isToStringMethod(method)) { | ||||
| 			return toString(proxy); | ||||
| 			return annotationToString(); | ||||
| 		} | ||||
| 
 | ||||
| 		String methodName = method.getName(); | ||||
| 		Class<?> returnType = method.getReturnType(); | ||||
| 		boolean nestedAnnotation = (Annotation[].class.isAssignableFrom(returnType) || Annotation.class.isAssignableFrom(returnType)); | ||||
| 		String aliasedAttributeName = aliasMap.get(methodName); | ||||
| 		boolean aliasPresent = (aliasedAttributeName != null); | ||||
| 
 | ||||
| 		makeAccessible(method); | ||||
| 
 | ||||
| 		// No custom processing necessary? | ||||
| 		if (!aliasPresent && !nestedAnnotation) { | ||||
| 			return invokeMethod(method, this.annotation, args); | ||||
| 		if (isAnnotationTypeMethod(method)) { | ||||
| 			return annotationType(); | ||||
| 		} | ||||
| 
 | ||||
| 		Object cachedValue = this.computedValueCache.get(methodName); | ||||
| 		if (cachedValue != null) { | ||||
| 			return cachedValue; | ||||
| 		if (!isAttributeMethod(method)) { | ||||
| 			String msg = String.format("Method [%s] is unsupported for synthesized annotation type [%s]", method, | ||||
| 				annotationType()); | ||||
| 			throw new AnnotationConfigurationException(msg); | ||||
| 		} | ||||
| 		return getAttributeValue(method); | ||||
| 	} | ||||
| 
 | ||||
| 		Object value = invokeMethod(method, this.annotation, args); | ||||
| 	private Class<? extends Annotation> annotationType() { | ||||
| 		return this.attributeExtractor.getAnnotationType(); | ||||
| 	} | ||||
| 
 | ||||
| 		if (aliasPresent) { | ||||
| 			Method aliasedMethod = null; | ||||
| 			try { | ||||
| 				aliasedMethod = this.annotationType.getDeclaredMethod(aliasedAttributeName); | ||||
| 			} | ||||
| 			catch (NoSuchMethodException e) { | ||||
| 				String msg = String.format("In annotation [%s], attribute [%s] is declared as an @AliasFor [%s], " | ||||
| 						+ "but attribute [%s] does not exist.", this.annotationType.getName(), methodName, | ||||
| 					aliasedAttributeName, aliasedAttributeName); | ||||
| 				throw new AnnotationConfigurationException(msg); | ||||
| 	private Object getAttributeValue(Method attributeMethod) { | ||||
| 		String attributeName = attributeMethod.getName(); | ||||
| 		Object value = this.valueCache.get(attributeName); | ||||
| 		if (value == null) { | ||||
| 			value = this.attributeExtractor.getAttributeValue(attributeMethod); | ||||
| 			if (value == null) { | ||||
| 				throw new IllegalStateException(String.format( | ||||
| 					"%s returned null for attribute name [%s] from attribute source [%s]", | ||||
| 					this.attributeExtractor.getClass().getName(), attributeName, this.attributeExtractor.getSource())); | ||||
| 			} | ||||
| 
 | ||||
| 			makeAccessible(aliasedMethod); | ||||
| 			Object aliasedValue = invokeMethod(aliasedMethod, this.annotation); | ||||
| 			Object defaultValue = getDefaultValue(this.annotation, methodName); | ||||
| 
 | ||||
| 			if (!nullSafeEquals(value, aliasedValue) && !nullSafeEquals(value, defaultValue) | ||||
| 					&& !nullSafeEquals(aliasedValue, defaultValue)) { | ||||
| 				String elementName = (this.annotatedElement == null ? "unknown element" | ||||
| 						: this.annotatedElement.toString()); | ||||
| 				String msg = String.format( | ||||
| 					"In annotation [%s] declared on [%s], attribute [%s] and its alias [%s] are " | ||||
| 							+ "declared with values of [%s] and [%s], but only one declaration is permitted.", | ||||
| 					this.annotationType.getName(), elementName, methodName, aliasedAttributeName, | ||||
| 					nullSafeToString(value), nullSafeToString(aliasedValue)); | ||||
| 				throw new AnnotationConfigurationException(msg); | ||||
| 			// Synthesize nested annotations before returning them. | ||||
| 			if (value instanceof Annotation) { | ||||
| 				value = synthesizeAnnotation((Annotation) value, this.attributeExtractor.getAnnotatedElement()); | ||||
| 			} | ||||
| 			else if (value instanceof Annotation[]) { | ||||
| 				Annotation[] orig = (Annotation[]) value; | ||||
| 				Annotation[] clone = (Annotation[]) Array.newInstance(orig.getClass().getComponentType(), orig.length); | ||||
| 				for (int i = 0; i < orig.length; i++) { | ||||
| 					clone[i] = synthesizeAnnotation(orig[i], this.attributeExtractor.getAnnotatedElement()); | ||||
| 				} | ||||
| 				value = clone; | ||||
| 			} | ||||
| 
 | ||||
| 			// If the user didn't declare the annotation with an explicit value, return | ||||
| 			// the value of the alias. | ||||
| 			if (nullSafeEquals(value, defaultValue)) { | ||||
| 				value = aliasedValue; | ||||
| 			} | ||||
| 			this.valueCache.put(attributeName, value); | ||||
| 		} | ||||
| 
 | ||||
| 		// Synthesize nested annotations before returning them. | ||||
| 		if (value instanceof Annotation) { | ||||
| 			value = synthesizeAnnotation((Annotation) value, this.annotatedElement); | ||||
| 		// Clone arrays so that users cannot alter the contents of values in our cache. | ||||
| 		if (value.getClass().isArray()) { | ||||
| 			value = cloneArray(value); | ||||
| 		} | ||||
| 		else if (value instanceof Annotation[]) { | ||||
| 			Annotation[] annotations = (Annotation[]) value; | ||||
| 			for (int i = 0; i < annotations.length; i++) { | ||||
| 				annotations[i] = synthesizeAnnotation(annotations[i], this.annotatedElement); | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		this.computedValueCache.put(methodName, value); | ||||
| 
 | ||||
| 		return value; | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Clone the provided array, ensuring that original component type is | ||||
| 	 * retained. | ||||
| 	 * @param array the array to clone | ||||
| 	 */ | ||||
| 	private Object cloneArray(Object array) { | ||||
| 		if (array instanceof boolean[]) { | ||||
| 			return ((boolean[]) array).clone(); | ||||
| 		} | ||||
| 		if (array instanceof byte[]) { | ||||
| 			return ((byte[]) array).clone(); | ||||
| 		} | ||||
| 		if (array instanceof char[]) { | ||||
| 			return ((char[]) array).clone(); | ||||
| 		} | ||||
| 		if (array instanceof double[]) { | ||||
| 			return ((double[]) array).clone(); | ||||
| 		} | ||||
| 		if (array instanceof float[]) { | ||||
| 			return ((float[]) array).clone(); | ||||
| 		} | ||||
| 		if (array instanceof int[]) { | ||||
| 			return ((int[]) array).clone(); | ||||
| 		} | ||||
| 		if (array instanceof long[]) { | ||||
| 			return ((long[]) array).clone(); | ||||
| 		} | ||||
| 		if (array instanceof short[]) { | ||||
| 			return ((short[]) array).clone(); | ||||
| 		} | ||||
| 
 | ||||
| 		// else | ||||
| 		return ((Object[]) array).clone(); | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
| 	 * See {@link Annotation#equals(Object)} for a definition of the required algorithm. | ||||
| 	 * | ||||
| 	 * @param proxy the synthesized annotation | ||||
| 	 * @param other the other object to compare against | ||||
| 	 */ | ||||
| 	private boolean equals(Object proxy, Object other) { | ||||
| 	private boolean annotationEquals(Object other) { | ||||
| 		if (this == other) { | ||||
| 			return true; | ||||
| 		} | ||||
| 		if (!this.annotationType.isInstance(other)) { | ||||
| 		if (!annotationType().isInstance(other)) { | ||||
| 			return false; | ||||
| 		} | ||||
| 
 | ||||
| 		for (Method attributeMethod : getAttributeMethods(this.annotationType)) { | ||||
| 			Object thisValue = invokeMethod(attributeMethod, proxy); | ||||
| 		for (Method attributeMethod : getAttributeMethods(annotationType())) { | ||||
| 			Object thisValue = getAttributeValue(attributeMethod); | ||||
| 			Object otherValue = invokeMethod(attributeMethod, other); | ||||
| 			if (!nullSafeEquals(thisValue, otherValue)) { | ||||
| 			if (!ObjectUtils.nullSafeEquals(thisValue, otherValue)) { | ||||
| 				return false; | ||||
| 			} | ||||
| 		} | ||||
|  | @ -189,14 +182,12 @@ class SynthesizedAnnotationInvocationHandler implements InvocationHandler { | |||
| 
 | ||||
| 	/** | ||||
| 	 * See {@link Annotation#hashCode()} for a definition of the required algorithm. | ||||
| 	 * | ||||
| 	 * @param proxy the synthesized annotation | ||||
| 	 */ | ||||
| 	private int hashCode(Object proxy) { | ||||
| 	private int annotationHashCode() { | ||||
| 		int result = 0; | ||||
| 
 | ||||
| 		for (Method attributeMethod : getAttributeMethods(this.annotationType)) { | ||||
| 			Object value = invokeMethod(attributeMethod, proxy); | ||||
| 		for (Method attributeMethod : getAttributeMethods(annotationType())) { | ||||
| 			Object value = getAttributeValue(attributeMethod); | ||||
| 			int hashCode; | ||||
| 			if (value.getClass().isArray()) { | ||||
| 				hashCode = hashCodeForArray(value); | ||||
|  | @ -250,39 +241,27 @@ class SynthesizedAnnotationInvocationHandler implements InvocationHandler { | |||
| 
 | ||||
| 	/** | ||||
| 	 * See {@link Annotation#toString()} for guidelines on the recommended format. | ||||
| 	 * | ||||
| 	 * @param proxy the synthesized annotation | ||||
| 	 */ | ||||
| 	private String toString(Object proxy) { | ||||
| 		StringBuilder sb = new StringBuilder("@").append(annotationType.getName()).append("("); | ||||
| 	private String annotationToString() { | ||||
| 		StringBuilder sb = new StringBuilder("@").append(annotationType().getName()).append("("); | ||||
| 
 | ||||
| 		Iterator<Method> iterator = getAttributeMethods(this.annotationType).iterator(); | ||||
| 		Iterator<Method> iterator = getAttributeMethods(annotationType()).iterator(); | ||||
| 		while (iterator.hasNext()) { | ||||
| 			Method attributeMethod = iterator.next(); | ||||
| 			sb.append(attributeMethod.getName()); | ||||
| 			sb.append('='); | ||||
| 			sb.append(valueToString(invokeMethod(attributeMethod, proxy))); | ||||
| 			sb.append(attributeValueToString(getAttributeValue(attributeMethod))); | ||||
| 			sb.append(iterator.hasNext() ? ", " : ""); | ||||
| 		} | ||||
| 
 | ||||
| 		return sb.append(")").toString(); | ||||
| 	} | ||||
| 
 | ||||
| 	private String valueToString(Object value) { | ||||
| 	private String attributeValueToString(Object value) { | ||||
| 		if (value instanceof Object[]) { | ||||
| 			return "[" + StringUtils.arrayToDelimitedString((Object[]) value, ", ") + "]"; | ||||
| 		} | ||||
| 
 | ||||
| 		// else | ||||
| 		return String.valueOf(value); | ||||
| 	} | ||||
| 
 | ||||
| 	private static boolean nullSafeEquals(Object o1, Object o2) { | ||||
| 		return ObjectUtils.nullSafeEquals(o1, o2); | ||||
| 	} | ||||
| 
 | ||||
| 	private static String nullSafeToString(Object obj) { | ||||
| 		return ObjectUtils.nullSafeToString(obj); | ||||
| 	} | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -16,11 +16,13 @@ | |||
| 
 | ||||
| package org.springframework.core.annotation; | ||||
| 
 | ||||
| import java.lang.annotation.Annotation; | ||||
| import java.lang.annotation.ElementType; | ||||
| import java.lang.annotation.Inherited; | ||||
| import java.lang.annotation.Retention; | ||||
| import java.lang.annotation.RetentionPolicy; | ||||
| import java.lang.annotation.Target; | ||||
| import java.lang.reflect.AnnotatedElement; | ||||
| import java.lang.reflect.Method; | ||||
| import java.util.List; | ||||
| import java.util.Set; | ||||
|  | @ -30,6 +32,7 @@ import org.junit.Test; | |||
| import org.junit.rules.ExpectedException; | ||||
| 
 | ||||
| import org.springframework.stereotype.Component; | ||||
| import org.springframework.util.Assert; | ||||
| import org.springframework.util.MultiValueMap; | ||||
| 
 | ||||
| import static java.util.Arrays.*; | ||||
|  | @ -450,14 +453,33 @@ public class AnnotatedElementUtilsTests { | |||
| 	} | ||||
| 
 | ||||
| 	@Test | ||||
| 	public void findAnnotationAttributesOnClassWithAttributeAliasesInTargetAnnotation() { | ||||
| 	public void findAndSynthesizeAnnotationAttributesOnClassWithAttributeAliasesInTargetAnnotation() { | ||||
| 		String qualifier = "aliasForQualifier"; | ||||
| 
 | ||||
| 		// 1) Find and merge AnnotationAttributes from the annotation hierarchy | ||||
| 		AnnotationAttributes attributes = findAnnotationAttributes(AliasedTransactionalComponentClass.class, | ||||
| 			AliasedTransactional.class); | ||||
| 		assertNotNull("Should find @AliasedTransactional on AliasedTransactionalComponentClass", attributes); | ||||
| 		assertEquals("TX value for AliasedTransactionalComponentClass.", "aliasForQualifier", | ||||
| 			attributes.getString("value")); | ||||
| 		assertEquals("TX qualifier for AliasedTransactionalComponentClass.", "aliasForQualifier", | ||||
| 			attributes.getString("qualifier")); | ||||
| 		assertNotNull("@AliasedTransactional on AliasedTransactionalComponentClass.", attributes); | ||||
| 
 | ||||
| 		// 2) Synthesize the AnnotationAttributes back into the target annotation | ||||
| 		AliasedTransactional annotation = AnnotationUtils.synthesizeAnnotation(attributes, | ||||
| 			AliasedTransactional.class, AliasedTransactionalComponentClass.class); | ||||
| 		assertNotNull(annotation); | ||||
| 
 | ||||
| 		// 3) Verify that the AnnotationAttributes and synthesized annotation are equivalent | ||||
| 		assertEquals("TX value via attributes.", qualifier, attributes.getString("value")); | ||||
| 		assertEquals("TX value via synthesized annotation.", qualifier, annotation.value()); | ||||
| 		assertEquals("TX qualifier via attributes.", qualifier, attributes.getString("qualifier")); | ||||
| 		assertEquals("TX qualifier via synthesized annotation.", qualifier, annotation.qualifier()); | ||||
| 	} | ||||
| 
 | ||||
| 	@Test | ||||
| 	public void findAnnotationWithAttributeAliasesInTargetAnnotation() { | ||||
| 		Class<?> element = AliasedTransactionalComponentClass.class; | ||||
| 		AliasedTransactional annotation = findAnnotation(element, AliasedTransactional.class); | ||||
| 		assertNotNull("@AliasedTransactional on " + element, annotation); | ||||
| 		assertEquals("TX value via synthesized annotation.", "aliasForQualifier", annotation.value()); | ||||
| 		assertEquals("TX qualifier via synthesized annotation.", "aliasForQualifier", annotation.qualifier()); | ||||
| 	} | ||||
| 
 | ||||
| 	@Test | ||||
|  | @ -480,6 +502,12 @@ public class AnnotatedElementUtilsTests { | |||
| 	} | ||||
| 
 | ||||
| 
 | ||||
| 	static AnnotationAttributes findAnnotationAttributes(AnnotatedElement element, Class<? extends Annotation> annotationType) { | ||||
| 		Assert.notNull(annotationType, "annotationType must not be null"); | ||||
| 		return AnnotatedElementUtils.findAnnotationAttributes(element, annotationType.getName(), false, false); | ||||
| 	} | ||||
| 
 | ||||
| 
 | ||||
| 	// ------------------------------------------------------------------------- | ||||
| 
 | ||||
| 	@MetaCycle3 | ||||
|  |  | |||
|  | @ -24,7 +24,9 @@ import java.lang.annotation.RetentionPolicy; | |||
| import java.lang.annotation.Target; | ||||
| import java.lang.reflect.Method; | ||||
| import java.util.Arrays; | ||||
| import java.util.HashMap; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| import java.util.Set; | ||||
| 
 | ||||
| import org.junit.Rule; | ||||
|  | @ -436,7 +438,7 @@ public class AnnotationUtilsTests { | |||
| 		exception.expect(AnnotationConfigurationException.class); | ||||
| 		exception.expectMessage(containsString("attribute [value] and its alias [path]")); | ||||
| 		exception.expectMessage(containsString("values of [/enigma] and [/test]")); | ||||
| 		exception.expectMessage(containsString("but only one declaration is permitted")); | ||||
| 		exception.expectMessage(containsString("but only one is permitted")); | ||||
| 		getAnnotationAttributes(webMapping); | ||||
| 	} | ||||
| 
 | ||||
|  | @ -504,7 +506,8 @@ public class AnnotationUtilsTests { | |||
| 
 | ||||
| 	@Test | ||||
| 	public void getRepeatableWithAttributeAliases() throws Exception { | ||||
| 		Set<ContextConfig> annotations = getRepeatableAnnotation(TestCase.class, Hierarchy.class, ContextConfig.class); | ||||
| 		Set<ContextConfig> annotations = getRepeatableAnnotation(ConfigHierarchyTestCase.class, Hierarchy.class, | ||||
| 			ContextConfig.class); | ||||
| 		assertNotNull(annotations); | ||||
| 
 | ||||
| 		List<String> locations = annotations.stream().map(ContextConfig::locations).collect(toList()); | ||||
|  | @ -522,7 +525,7 @@ public class AnnotationUtilsTests { | |||
| 
 | ||||
| 	@Test | ||||
| 	public void synthesizeAnnotationWithoutAttributeAliases() throws Exception { | ||||
| 		Component component = findAnnotation(WebController.class, Component.class); | ||||
| 		Component component = WebController.class.getAnnotation(Component.class); | ||||
| 		assertNotNull(component); | ||||
| 		Component synthesizedComponent = synthesizeAnnotation(component); | ||||
| 		assertNotNull(synthesizedComponent); | ||||
|  | @ -530,6 +533,29 @@ public class AnnotationUtilsTests { | |||
| 		assertEquals("value attribute: ", "webController", synthesizedComponent.value()); | ||||
| 	} | ||||
| 
 | ||||
| 	@Test | ||||
| 	public void synthesizeAnnotationsFromNullSources() throws Exception { | ||||
| 		assertNull("null annotation", synthesizeAnnotation(null, null)); | ||||
| 		assertNull("null map", synthesizeAnnotation(null, WebMapping.class, null)); | ||||
| 	} | ||||
| 
 | ||||
| 	@Test | ||||
| 	public void synthesizeAlreadySynthesizedAnnotation() throws Exception { | ||||
| 		Method method = WebController.class.getMethod("handleMappedWithValueAttribute"); | ||||
| 		WebMapping webMapping = method.getAnnotation(WebMapping.class); | ||||
| 		assertNotNull(webMapping); | ||||
| 		WebMapping synthesizedWebMapping = synthesizeAnnotation(webMapping); | ||||
| 		assertNotSame(webMapping, synthesizedWebMapping); | ||||
| 		WebMapping synthesizedAgainWebMapping = synthesizeAnnotation(synthesizedWebMapping); | ||||
| 		assertSame(synthesizedWebMapping, synthesizedAgainWebMapping); | ||||
| 		assertThat(synthesizedAgainWebMapping, instanceOf(SynthesizedAnnotation.class)); | ||||
| 
 | ||||
| 		assertNotNull(synthesizedAgainWebMapping); | ||||
| 		assertEquals("name attribute: ", "foo", synthesizedAgainWebMapping.name()); | ||||
| 		assertEquals("aliased path attribute: ", "/test", synthesizedAgainWebMapping.path()); | ||||
| 		assertEquals("actual value attribute: ", "/test", synthesizedAgainWebMapping.value()); | ||||
| 	} | ||||
| 
 | ||||
| 	@Test | ||||
| 	public void synthesizeAnnotationWithAttributeAliasForNonexistentAttribute() throws Exception { | ||||
| 		AliasForNonexistentAttribute annotation = AliasForNonexistentAttributeClass.class.getAnnotation(AliasForNonexistentAttribute.class); | ||||
|  | @ -638,6 +664,78 @@ public class AnnotationUtilsTests { | |||
| 		assertEquals("actual value attribute: ", "/test", synthesizedWebMapping2.value()); | ||||
| 	} | ||||
| 
 | ||||
| 	@Test | ||||
| 	public void synthesizeAnnotationFromMapWithoutAttributeAliases() throws Exception { | ||||
| 		Component component = WebController.class.getAnnotation(Component.class); | ||||
| 		assertNotNull(component); | ||||
| 
 | ||||
| 		Map<String, Object> map = new HashMap<String, Object>(); | ||||
| 		map.put(VALUE, "webController"); | ||||
| 		Component synthesizedComponent = synthesizeAnnotation(map, Component.class, WebController.class); | ||||
| 		assertNotNull(synthesizedComponent); | ||||
| 
 | ||||
| 		assertNotSame(component, synthesizedComponent); | ||||
| 		assertEquals("value from component: ", "webController", component.value()); | ||||
| 		assertEquals("value from synthesized component: ", "webController", synthesizedComponent.value()); | ||||
| 	} | ||||
| 
 | ||||
| 	@Test | ||||
| 	public void synthesizeAnnotationFromMapWithMissingAttributeValue() throws Exception { | ||||
| 		exception.expect(IllegalArgumentException.class); | ||||
| 		exception.expectMessage(startsWith("Attributes map")); | ||||
| 		exception.expectMessage(containsString("returned null for required attribute [value]")); | ||||
| 		exception.expectMessage(containsString("defined by annotation type [" + Component.class.getName() + "]")); | ||||
| 		synthesizeAnnotation(new HashMap<String, Object>(), Component.class, null); | ||||
| 	} | ||||
| 
 | ||||
| 	@Test | ||||
| 	public void synthesizeAnnotationFromMapWithNullAttributeValue() throws Exception { | ||||
| 		Map<String, Object> map = new HashMap<String, Object>(); | ||||
| 		map.put(VALUE, null); | ||||
| 
 | ||||
| 		exception.expect(IllegalArgumentException.class); | ||||
| 		exception.expectMessage(startsWith("Attributes map")); | ||||
| 		exception.expectMessage(containsString("returned null for required attribute [value]")); | ||||
| 		exception.expectMessage(containsString("defined by annotation type [" + Component.class.getName() + "]")); | ||||
| 		synthesizeAnnotation(map, Component.class, null); | ||||
| 	} | ||||
| 
 | ||||
| 	@Test | ||||
| 	public void synthesizeAnnotationFromMapWithAttributeOfIncorrectType() throws Exception { | ||||
| 		Map<String, Object> map = new HashMap<String, Object>(); | ||||
| 		map.put(VALUE, 42L); | ||||
| 
 | ||||
| 		exception.expect(IllegalArgumentException.class); | ||||
| 		exception.expectMessage(startsWith("Attributes map")); | ||||
| 		exception.expectMessage(containsString("returned a value of type [java.lang.Long]")); | ||||
| 		exception.expectMessage(containsString("for attribute [value]")); | ||||
| 		exception.expectMessage(containsString("but a value of type [java.lang.String] is required")); | ||||
| 		exception.expectMessage(containsString("as defined by annotation type [" + Component.class.getName() + "]")); | ||||
| 		synthesizeAnnotation(map, Component.class, null); | ||||
| 	} | ||||
| 
 | ||||
| 	@Test | ||||
| 	public void synthesizeAnnotationFromAnnotationAttributesWithoutAttributeAliases() throws Exception { | ||||
| 
 | ||||
| 		// 1) Get an annotation | ||||
| 		Component component = WebController.class.getAnnotation(Component.class); | ||||
| 		assertNotNull(component); | ||||
| 
 | ||||
| 		// 2) Convert the annotation into AnnotationAttributes | ||||
| 		AnnotationAttributes attributes = getAnnotationAttributes(WebController.class, component); | ||||
| 		assertNotNull(attributes); | ||||
| 
 | ||||
| 		// 3) Synthesize the AnnotationAttributes back into an annotation | ||||
| 		Component synthesizedComponent = synthesizeAnnotation(attributes, Component.class, WebController.class); | ||||
| 		assertNotNull(synthesizedComponent); | ||||
| 
 | ||||
| 		// 4) Verify that the original and synthesized annotations are equivalent | ||||
| 		assertNotSame(component, synthesizedComponent); | ||||
| 		assertEquals(component, synthesizedComponent); | ||||
| 		assertEquals("value from component: ", "webController", component.value()); | ||||
| 		assertEquals("value from synthesized component: ", "webController", synthesizedComponent.value()); | ||||
| 	} | ||||
| 
 | ||||
| 	@Test | ||||
| 	public void toStringForSynthesizedAnnotations() throws Exception { | ||||
| 		Method methodWithPath = WebController.class.getMethod("handleMappedWithPathAttribute"); | ||||
|  | @ -670,7 +768,7 @@ public class AnnotationUtilsTests { | |||
| 		assertThat(string, containsString("path=/test")); | ||||
| 		assertThat(string, containsString("name=bar")); | ||||
| 		assertThat(string, containsString("method=")); | ||||
| 		assertThat(string, either(containsString("[GET, POST]")).or(containsString("[POST, GET]"))); | ||||
| 		assertThat(string, containsString("[GET, POST]")); | ||||
| 		assertThat(string, endsWith(")")); | ||||
| 	} | ||||
| 
 | ||||
|  | @ -778,7 +876,7 @@ public class AnnotationUtilsTests { | |||
| 
 | ||||
| 	@Test | ||||
| 	public void synthesizeAnnotationWithAttributeAliasesInNestedAnnotations() throws Exception { | ||||
| 		Hierarchy hierarchy = TestCase.class.getAnnotation(Hierarchy.class); | ||||
| 		Hierarchy hierarchy = ConfigHierarchyTestCase.class.getAnnotation(Hierarchy.class); | ||||
| 		assertNotNull(hierarchy); | ||||
| 		Hierarchy synthesizedHierarchy = synthesizeAnnotation(hierarchy); | ||||
| 		assertNotSame(hierarchy, synthesizedHierarchy); | ||||
|  | @ -797,20 +895,44 @@ public class AnnotationUtilsTests { | |||
| 	} | ||||
| 
 | ||||
| 	@Test | ||||
| 	public void synthesizeAlreadySynthesizedAnnotation() throws Exception { | ||||
| 		Method method = WebController.class.getMethod("handleMappedWithValueAttribute"); | ||||
| 		WebMapping webMapping = method.getAnnotation(WebMapping.class); | ||||
| 		assertNotNull(webMapping); | ||||
| 		WebMapping synthesizedWebMapping = synthesizeAnnotation(webMapping); | ||||
| 		assertNotSame(webMapping, synthesizedWebMapping); | ||||
| 		WebMapping synthesizedAgainWebMapping = synthesizeAnnotation(synthesizedWebMapping); | ||||
| 		assertSame(synthesizedWebMapping, synthesizedAgainWebMapping); | ||||
| 		assertThat(synthesizedAgainWebMapping, instanceOf(SynthesizedAnnotation.class)); | ||||
| 	public void synthesizeAnnotationWithArrayOfAnnotations() throws Exception { | ||||
| 		Hierarchy hierarchy = ConfigHierarchyTestCase.class.getAnnotation(Hierarchy.class); | ||||
| 		assertNotNull(hierarchy); | ||||
| 		Hierarchy synthesizedHierarchy = synthesizeAnnotation(hierarchy); | ||||
| 		assertThat(synthesizedHierarchy, instanceOf(SynthesizedAnnotation.class)); | ||||
| 
 | ||||
| 		assertNotNull(synthesizedAgainWebMapping); | ||||
| 		assertEquals("name attribute: ", "foo", synthesizedAgainWebMapping.name()); | ||||
| 		assertEquals("aliased path attribute: ", "/test", synthesizedAgainWebMapping.path()); | ||||
| 		assertEquals("actual value attribute: ", "/test", synthesizedAgainWebMapping.value()); | ||||
| 		ContextConfig contextConfig = SimpleConfigTestCase.class.getAnnotation(ContextConfig.class); | ||||
| 		assertNotNull(contextConfig); | ||||
| 
 | ||||
| 		ContextConfig[] configs = synthesizedHierarchy.value(); | ||||
| 		List<String> locations = Arrays.stream(configs).map(ContextConfig::locations).collect(toList()); | ||||
| 		assertThat(locations, equalTo(Arrays.asList("A", "B"))); | ||||
| 
 | ||||
| 		// Alter array returned from synthesized annotation | ||||
| 		configs[0] = contextConfig; | ||||
| 
 | ||||
| 		// Re-retrieve the array from the synthesized annotation | ||||
| 		configs = synthesizedHierarchy.value(); | ||||
| 		List<String> values = Arrays.stream(configs).map(ContextConfig::value).collect(toList()); | ||||
| 		assertThat(values, equalTo(Arrays.asList("A", "B"))); | ||||
| 	} | ||||
| 
 | ||||
| 	@Test | ||||
| 	public void synthesizeAnnotationWithArrayOfChars() throws Exception { | ||||
| 		CharsContainer charsContainer = GroupOfCharsClass.class.getAnnotation(CharsContainer.class); | ||||
| 		assertNotNull(charsContainer); | ||||
| 		CharsContainer synthesizedCharsContainer = synthesizeAnnotation(charsContainer); | ||||
| 		assertThat(synthesizedCharsContainer, instanceOf(SynthesizedAnnotation.class)); | ||||
| 
 | ||||
| 		char[] chars = synthesizedCharsContainer.chars(); | ||||
| 		assertArrayEquals(new char[] { 'x', 'y', 'z' }, chars); | ||||
| 
 | ||||
| 		// Alter array returned from synthesized annotation | ||||
| 		chars[0] = '?'; | ||||
| 
 | ||||
| 		// Re-retrieve the array from the synthesized annotation | ||||
| 		chars = synthesizedCharsContainer.chars(); | ||||
| 		assertArrayEquals(new char[] { 'x', 'y', 'z' }, chars); | ||||
| 	} | ||||
| 
 | ||||
| 
 | ||||
|  | @ -1149,9 +1271,28 @@ public class AnnotationUtilsTests { | |||
| 	} | ||||
| 
 | ||||
| 	@Hierarchy({ @ContextConfig("A"), @ContextConfig(locations = "B") }) | ||||
| 	static class TestCase { | ||||
| 	static class ConfigHierarchyTestCase { | ||||
| 	} | ||||
| 
 | ||||
| 	@ContextConfig("simple.xml") | ||||
| 	static class SimpleConfigTestCase { | ||||
| 	} | ||||
| 
 | ||||
| 	@Retention(RetentionPolicy.RUNTIME) | ||||
| 	@interface CharsContainer { | ||||
| 
 | ||||
| 		@AliasFor(attribute = "chars") | ||||
| 		char[] value() default {}; | ||||
| 
 | ||||
| 		@AliasFor(attribute = "value") | ||||
| 		char[] chars() default {}; | ||||
| 	} | ||||
| 
 | ||||
| 	@CharsContainer(chars = { 'x', 'y', 'z' }) | ||||
| 	static class GroupOfCharsClass { | ||||
| 	} | ||||
| 
 | ||||
| 
 | ||||
| 	@Retention(RetentionPolicy.RUNTIME) | ||||
| 	@interface AliasForNonexistentAttribute { | ||||
| 
 | ||||
|  |  | |||
|  | @ -19,7 +19,6 @@ package org.springframework.test.annotation; | |||
| import java.lang.reflect.Method; | ||||
| 
 | ||||
| import org.springframework.core.annotation.AnnotatedElementUtils; | ||||
| import org.springframework.core.annotation.AnnotationAttributes; | ||||
| import org.springframework.core.annotation.AnnotationUtils; | ||||
| 
 | ||||
| /** | ||||
|  | @ -38,13 +37,12 @@ public class TestAnnotationUtils { | |||
| 	 * annotated with {@code @Timed} | ||||
| 	 */ | ||||
| 	public static long getTimeout(Method method) { | ||||
| 		AnnotationAttributes attributes = AnnotatedElementUtils.findAnnotationAttributes(method, Timed.class.getName()); | ||||
| 		if (attributes == null) { | ||||
| 		Timed timed = AnnotatedElementUtils.findAnnotation(method, Timed.class); | ||||
| 		if (timed == null) { | ||||
| 			return 0; | ||||
| 		} | ||||
| 		else { | ||||
| 			long millis = attributes.<Long> getNumber("millis").longValue(); | ||||
| 			return Math.max(0, millis); | ||||
| 			return Math.max(0, timed.millis()); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
|  |  | |||
|  | @ -87,7 +87,7 @@ class MergedSqlConfig { | |||
| 
 | ||||
| 		// Get global attributes, if any. | ||||
| 		AnnotationAttributes attributes = AnnotatedElementUtils.findAnnotationAttributes(testClass, | ||||
| 			SqlConfig.class.getName()); | ||||
| 			SqlConfig.class.getName(), false, false); | ||||
| 
 | ||||
| 		// Override global attributes with local attributes. | ||||
| 		if (attributes != null) { | ||||
|  |  | |||
|  | @ -32,7 +32,6 @@ import org.springframework.beans.BeanInstantiationException; | |||
| import org.springframework.beans.BeanUtils; | ||||
| import org.springframework.context.ApplicationContextInitializer; | ||||
| import org.springframework.context.ConfigurableApplicationContext; | ||||
| import org.springframework.core.annotation.AnnotationAttributes; | ||||
| import org.springframework.core.annotation.AnnotationAwareOrderComparator; | ||||
| import org.springframework.core.annotation.AnnotationUtils; | ||||
| import org.springframework.core.io.support.SpringFactoriesLoader; | ||||
|  | @ -115,7 +114,6 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot | |||
| 	/** | ||||
| 	 * {@inheritDoc} | ||||
| 	 */ | ||||
| 	@SuppressWarnings("unchecked") | ||||
| 	@Override | ||||
| 	public final List<TestExecutionListener> getTestExecutionListeners() { | ||||
| 		Class<?> clazz = getBootstrapContext().getTestClass(); | ||||
|  | @ -139,23 +137,20 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot | |||
| 			// Traverse the class hierarchy... | ||||
| 			while (descriptor != null) { | ||||
| 				Class<?> declaringClass = descriptor.getDeclaringClass(); | ||||
| 				AnnotationAttributes annAttrs = descriptor.getAnnotationAttributes(); | ||||
| 				TestExecutionListeners testExecutionListeners = descriptor.getMergedAnnotation(); | ||||
| 				if (logger.isTraceEnabled()) { | ||||
| 					logger.trace(String.format( | ||||
| 						"Retrieved @TestExecutionListeners attributes [%s] for declaring class [%s].", annAttrs, | ||||
| 						declaringClass.getName())); | ||||
| 					logger.trace(String.format("Retrieved @TestExecutionListeners [%s] for declaring class [%s].", | ||||
| 						testExecutionListeners, declaringClass.getName())); | ||||
| 				} | ||||
| 
 | ||||
| 				Class<? extends TestExecutionListener>[] listenerClasses = (Class<? extends TestExecutionListener>[]) annAttrs.getClassArray("listeners"); | ||||
| 
 | ||||
| 				boolean inheritListeners = annAttrs.getBoolean("inheritListeners"); | ||||
| 				boolean inheritListeners = testExecutionListeners.inheritListeners(); | ||||
| 				AnnotationDescriptor<TestExecutionListeners> superDescriptor = MetaAnnotationUtils.findAnnotationDescriptor( | ||||
| 					descriptor.getRootDeclaringClass().getSuperclass(), annotationType); | ||||
| 
 | ||||
| 				// If there are no listeners to inherit, we might need to merge the | ||||
| 				// locally declared listeners with the defaults. | ||||
| 				if ((!inheritListeners || superDescriptor == null) | ||||
| 						&& (annAttrs.getEnum("mergeMode") == MergeMode.MERGE_WITH_DEFAULTS)) { | ||||
| 						&& (testExecutionListeners.mergeMode() == MergeMode.MERGE_WITH_DEFAULTS)) { | ||||
| 					if (logger.isDebugEnabled()) { | ||||
| 						logger.debug(String.format( | ||||
| 							"Merging default listeners with listeners configured via @TestExecutionListeners for class [%s].", | ||||
|  | @ -165,7 +160,7 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot | |||
| 					classesList.addAll(getDefaultTestExecutionListenerClasses()); | ||||
| 				} | ||||
| 
 | ||||
| 				classesList.addAll(0, Arrays.<Class<? extends TestExecutionListener>> asList(listenerClasses)); | ||||
| 				classesList.addAll(0, Arrays.asList(testExecutionListeners.listeners())); | ||||
| 
 | ||||
| 				descriptor = (inheritListeners ? superDescriptor : null); | ||||
| 			} | ||||
|  |  | |||
|  | @ -23,7 +23,6 @@ import org.apache.commons.logging.Log; | |||
| import org.apache.commons.logging.LogFactory; | ||||
| 
 | ||||
| import org.springframework.beans.BeanUtils; | ||||
| import org.springframework.core.annotation.AnnotationAttributes; | ||||
| import org.springframework.test.context.ActiveProfiles; | ||||
| import org.springframework.test.context.ActiveProfilesResolver; | ||||
| import org.springframework.test.util.MetaAnnotationUtils; | ||||
|  | @ -87,14 +86,14 @@ abstract class ActiveProfilesUtils { | |||
| 		while (descriptor != null) { | ||||
| 			Class<?> rootDeclaringClass = descriptor.getRootDeclaringClass(); | ||||
| 			Class<?> declaringClass = descriptor.getDeclaringClass(); | ||||
| 			ActiveProfiles annotation = descriptor.getMergedAnnotation(); | ||||
| 
 | ||||
| 			AnnotationAttributes annAttrs = descriptor.getAnnotationAttributes(); | ||||
| 			if (logger.isTraceEnabled()) { | ||||
| 				logger.trace(String.format("Retrieved @ActiveProfiles attributes [%s] for declaring class [%s].", | ||||
| 					annAttrs, declaringClass.getName())); | ||||
| 				logger.trace(String.format("Retrieved @ActiveProfiles [%s] for declaring class [%s].", annotation, | ||||
| 					declaringClass.getName())); | ||||
| 			} | ||||
| 
 | ||||
| 			Class<? extends ActiveProfilesResolver> resolverClass = annAttrs.getClass("resolver"); | ||||
| 			Class<? extends ActiveProfilesResolver> resolverClass = annotation.resolver(); | ||||
| 			if (ActiveProfilesResolver.class == resolverClass) { | ||||
| 				resolverClass = DefaultActiveProfilesResolver.class; | ||||
| 			} | ||||
|  | @ -125,8 +124,8 @@ abstract class ActiveProfilesUtils { | |||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			descriptor = annAttrs.getBoolean("inheritProfiles") ? MetaAnnotationUtils.findAnnotationDescriptor( | ||||
| 				rootDeclaringClass.getSuperclass(), annotationType) : null; | ||||
| 			descriptor = (annotation.inheritProfiles() ? MetaAnnotationUtils.findAnnotationDescriptor( | ||||
| 				rootDeclaringClass.getSuperclass(), annotationType) : null); | ||||
| 		} | ||||
| 
 | ||||
| 		return StringUtils.toStringArray(activeProfiles); | ||||
|  |  | |||
|  | @ -1,5 +1,5 @@ | |||
| /* | ||||
|  * Copyright 2002-2014 the original author or authors. | ||||
|  * Copyright 2002-2015 the original author or authors. | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  | @ -26,7 +26,6 @@ import java.util.Set; | |||
| import org.apache.commons.logging.Log; | ||||
| import org.apache.commons.logging.LogFactory; | ||||
| 
 | ||||
| import org.springframework.core.annotation.AnnotationAttributes; | ||||
| import org.springframework.test.context.ContextConfiguration; | ||||
| import org.springframework.test.context.ContextConfigurationAttributes; | ||||
| import org.springframework.test.context.ContextHierarchy; | ||||
|  | @ -133,8 +132,9 @@ abstract class ContextLoaderUtils { | |||
| 			final List<ContextConfigurationAttributes> configAttributesList = new ArrayList<ContextConfigurationAttributes>(); | ||||
| 
 | ||||
| 			if (contextConfigDeclaredLocally) { | ||||
| 				convertAnnotationAttributesToConfigAttributesAndAddToList(descriptor.getAnnotationAttributes(), | ||||
| 					rootDeclaringClass, configAttributesList); | ||||
| 				convertContextConfigToConfigAttributesAndAddToList( | ||||
| 					(ContextConfiguration) descriptor.getMergedAnnotation(), rootDeclaringClass, | ||||
| 					configAttributesList); | ||||
| 			} | ||||
| 			else if (contextHierarchyDeclaredLocally) { | ||||
| 				ContextHierarchy contextHierarchy = getAnnotation(declaringClass, contextHierarchyType); | ||||
|  | @ -256,7 +256,7 @@ abstract class ContextLoaderUtils { | |||
| 			annotationType.getName(), testClass.getName())); | ||||
| 
 | ||||
| 		while (descriptor != null) { | ||||
| 			convertAnnotationAttributesToConfigAttributesAndAddToList(descriptor.getAnnotationAttributes(), | ||||
| 			convertContextConfigToConfigAttributesAndAddToList(descriptor.getMergedAnnotation(), | ||||
| 				descriptor.getRootDeclaringClass(), attributesList); | ||||
| 			descriptor = findAnnotationDescriptor(descriptor.getRootDeclaringClass().getSuperclass(), annotationType); | ||||
| 		} | ||||
|  | @ -284,24 +284,4 @@ abstract class ContextLoaderUtils { | |||
| 		attributesList.add(attributes); | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Convenience method for creating a {@link ContextConfigurationAttributes} | ||||
| 	 * instance from the supplied {@link AnnotationAttributes} and declaring | ||||
| 	 * class and then adding the attributes to the supplied list. | ||||
| 	 * @since 4.0 | ||||
| 	 */ | ||||
| 	private static void convertAnnotationAttributesToConfigAttributesAndAddToList(AnnotationAttributes annAttrs, | ||||
| 			Class<?> declaringClass, final List<ContextConfigurationAttributes> attributesList) { | ||||
| 		if (logger.isTraceEnabled()) { | ||||
| 			logger.trace(String.format("Retrieved @ContextConfiguration attributes [%s] for declaring class [%s].", | ||||
| 				annAttrs, declaringClass.getName())); | ||||
| 		} | ||||
| 
 | ||||
| 		ContextConfigurationAttributes attributes = new ContextConfigurationAttributes(declaringClass, annAttrs); | ||||
| 		if (logger.isTraceEnabled()) { | ||||
| 			logger.trace("Resolved context configuration attributes: " + attributes); | ||||
| 		} | ||||
| 		attributesList.add(attributes); | ||||
| 	} | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -22,9 +22,9 @@ import java.util.Set; | |||
| import org.apache.commons.logging.Log; | ||||
| import org.apache.commons.logging.LogFactory; | ||||
| 
 | ||||
| import org.springframework.core.annotation.AnnotationAttributes; | ||||
| import org.springframework.test.context.ActiveProfiles; | ||||
| import org.springframework.test.context.ActiveProfilesResolver; | ||||
| import org.springframework.test.util.MetaAnnotationUtils.AnnotationDescriptor; | ||||
| import org.springframework.util.Assert; | ||||
| import org.springframework.util.StringUtils; | ||||
| 
 | ||||
|  | @ -73,14 +73,14 @@ public class DefaultActiveProfilesResolver implements ActiveProfilesResolver { | |||
| 		} | ||||
| 		else { | ||||
| 			Class<?> declaringClass = descriptor.getDeclaringClass(); | ||||
| 			ActiveProfiles annotation = descriptor.getMergedAnnotation(); | ||||
| 
 | ||||
| 			AnnotationAttributes annAttrs = descriptor.getAnnotationAttributes(); | ||||
| 			if (logger.isTraceEnabled()) { | ||||
| 				logger.trace(String.format("Retrieved @ActiveProfiles attributes [%s] for declaring class [%s].", | ||||
| 					annAttrs, declaringClass.getName())); | ||||
| 				logger.trace(String.format("Retrieved @ActiveProfiles [%s] for declaring class [%s].", annotation, | ||||
| 					declaringClass.getName())); | ||||
| 			} | ||||
| 
 | ||||
| 			for (String profile : annAttrs.getStringArray("profiles")) { | ||||
| 			for (String profile : annotation.profiles()) { | ||||
| 				if (StringUtils.hasText(profile)) { | ||||
| 					activeProfiles.add(profile.trim()); | ||||
| 				} | ||||
|  |  | |||
|  | @ -23,7 +23,6 @@ import org.apache.commons.logging.LogFactory; | |||
| 
 | ||||
| import org.springframework.context.ApplicationContext; | ||||
| import org.springframework.core.annotation.AnnotatedElementUtils; | ||||
| import org.springframework.core.annotation.AnnotationAttributes; | ||||
| import org.springframework.test.annotation.DirtiesContext; | ||||
| import org.springframework.test.annotation.DirtiesContext.ClassMode; | ||||
| import org.springframework.test.annotation.DirtiesContext.HierarchyMode; | ||||
|  | @ -150,28 +149,27 @@ public class DirtiesContextTestExecutionListener extends AbstractTestExecutionLi | |||
| 	 */ | ||||
| 	private void beforeOrAfterTestMethod(TestContext testContext, String phase, MethodMode requiredMethodMode, | ||||
| 			ClassMode requiredClassMode) throws Exception { | ||||
| 
 | ||||
| 		Class<?> testClass = testContext.getTestClass(); | ||||
| 		Assert.notNull(testClass, "The test class of the supplied TestContext must not be null"); | ||||
| 		Method testMethod = testContext.getTestMethod(); | ||||
| 		Assert.notNull(testClass, "The test class of the supplied TestContext must not be null"); | ||||
| 		Assert.notNull(testMethod, "The test method of the supplied TestContext must not be null"); | ||||
| 
 | ||||
| 		final String annotationType = DirtiesContext.class.getName(); | ||||
| 		AnnotationAttributes methodAnnAttrs = AnnotatedElementUtils.findAnnotationAttributes(testMethod, annotationType); | ||||
| 		AnnotationAttributes classAnnAttrs = AnnotatedElementUtils.findAnnotationAttributes(testClass, annotationType); | ||||
| 		boolean methodAnnotated = methodAnnAttrs != null; | ||||
| 		boolean classAnnotated = classAnnAttrs != null; | ||||
| 		MethodMode methodMode = methodAnnotated ? methodAnnAttrs.<MethodMode> getEnum("methodMode") : null; | ||||
| 		ClassMode classMode = classAnnotated ? classAnnAttrs.<ClassMode> getEnum("classMode") : null; | ||||
| 		DirtiesContext methodAnn = AnnotatedElementUtils.findAnnotation(testMethod, DirtiesContext.class); | ||||
| 		DirtiesContext classAnn = AnnotatedElementUtils.findAnnotation(testClass, DirtiesContext.class); | ||||
| 		boolean methodAnnotated = (methodAnn != null); | ||||
| 		boolean classAnnotated = (classAnn != null); | ||||
| 		MethodMode methodMode = (methodAnnotated ? methodAnn.methodMode() : null); | ||||
| 		ClassMode classMode = (classAnnotated ? classAnn.classMode() : null); | ||||
| 
 | ||||
| 		if (logger.isDebugEnabled()) { | ||||
| 			logger.debug(String.format( | ||||
| 				"%s test method: context %s, class annotated with @DirtiesContext [%s] with mode [%s], method annotated with @DirtiesContext [%s] with mode [%s].", | ||||
| 				phase, testContext, classAnnotated, classMode, methodAnnotated, methodMode)); | ||||
| 			logger.debug(String.format("%s test method: context %s, class annotated with @DirtiesContext [%s] " | ||||
| 					+ "with mode [%s], method annotated with @DirtiesContext [%s] with mode [%s].", phase, testContext, | ||||
| 				classAnnotated, classMode, methodAnnotated, methodMode)); | ||||
| 		} | ||||
| 
 | ||||
| 		if ((methodMode == requiredMethodMode) || (classMode == requiredClassMode)) { | ||||
| 			HierarchyMode hierarchyMode = methodAnnotated ? methodAnnAttrs.<HierarchyMode> getEnum("hierarchyMode") | ||||
| 					: classAnnAttrs.<HierarchyMode> getEnum("hierarchyMode"); | ||||
| 			HierarchyMode hierarchyMode = (methodAnnotated ? methodAnn.hierarchyMode() : classAnn.hierarchyMode()); | ||||
| 			dirtyContext(testContext, hierarchyMode); | ||||
| 		} | ||||
| 	} | ||||
|  | @ -185,10 +183,9 @@ public class DirtiesContextTestExecutionListener extends AbstractTestExecutionLi | |||
| 		Class<?> testClass = testContext.getTestClass(); | ||||
| 		Assert.notNull(testClass, "The test class of the supplied TestContext must not be null"); | ||||
| 
 | ||||
| 		final String annotationType = DirtiesContext.class.getName(); | ||||
| 		AnnotationAttributes classAnnAttrs = AnnotatedElementUtils.findAnnotationAttributes(testClass, annotationType); | ||||
| 		boolean classAnnotated = classAnnAttrs != null; | ||||
| 		ClassMode classMode = classAnnotated ? classAnnAttrs.<ClassMode> getEnum("classMode") : null; | ||||
| 		DirtiesContext dirtiesContext = AnnotatedElementUtils.findAnnotation(testClass, DirtiesContext.class); | ||||
| 		boolean classAnnotated = (dirtiesContext != null); | ||||
| 		ClassMode classMode = (classAnnotated ? dirtiesContext.classMode() : null); | ||||
| 
 | ||||
| 		if (logger.isDebugEnabled()) { | ||||
| 			logger.debug(String.format( | ||||
|  | @ -197,8 +194,7 @@ public class DirtiesContextTestExecutionListener extends AbstractTestExecutionLi | |||
| 		} | ||||
| 
 | ||||
| 		if (classMode == requiredClassMode) { | ||||
| 			HierarchyMode hierarchyMode = classAnnAttrs.<HierarchyMode> getEnum("hierarchyMode"); | ||||
| 			dirtyContext(testContext, hierarchyMode); | ||||
| 			dirtyContext(testContext, dirtiesContext.hierarchyMode()); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
|  |  | |||
|  | @ -19,7 +19,6 @@ package org.springframework.test.context.support; | |||
| import org.apache.commons.logging.Log; | ||||
| import org.apache.commons.logging.LogFactory; | ||||
| 
 | ||||
| import org.springframework.core.annotation.AnnotationAttributes; | ||||
| import org.springframework.core.io.ClassPathResource; | ||||
| import org.springframework.core.style.ToStringCreator; | ||||
| import org.springframework.test.context.TestPropertySource; | ||||
|  | @ -58,17 +57,17 @@ class TestPropertySourceAttributes { | |||
| 
 | ||||
| 	/** | ||||
| 	 * Create a new {@code TestPropertySourceAttributes} instance for the | ||||
| 	 * supplied {@link AnnotationAttributes} (parsed from a | ||||
| 	 * {@link TestPropertySource @TestPropertySource} annotation) and | ||||
| 	 * the {@linkplain Class test class} that declared them, enforcing | ||||
| 	 * supplied {@link TestPropertySource @TestPropertySource} annotation and | ||||
| 	 * the {@linkplain Class test class} that declared it, enforcing | ||||
| 	 * configuration rules and detecting a default properties file if | ||||
| 	 * necessary. | ||||
| 	 * @param declaringClass the class that declared {@code @TestPropertySource} | ||||
| 	 * @param annAttrs the annotation attributes from which to retrieve the attributes | ||||
| 	 * @param testPropertySource the annotation from which to retrieve the attributes | ||||
| 	 * @since 4.2 | ||||
| 	 */ | ||||
| 	TestPropertySourceAttributes(Class<?> declaringClass, AnnotationAttributes annAttrs) { | ||||
| 		this(declaringClass, annAttrs.getStringArray("locations"), annAttrs.getBoolean("inheritLocations"), | ||||
| 			annAttrs.getStringArray("properties"), annAttrs.getBoolean("inheritProperties")); | ||||
| 	TestPropertySourceAttributes(Class<?> declaringClass, TestPropertySource testPropertySource) { | ||||
| 		this(declaringClass, testPropertySource.locations(), testPropertySource.inheritLocations(), | ||||
| 			testPropertySource.properties(), testPropertySource.inheritProperties()); | ||||
| 	} | ||||
| 
 | ||||
| 	private TestPropertySourceAttributes(Class<?> declaringClass, String[] locations, boolean inheritLocations, | ||||
|  |  | |||
|  | @ -29,7 +29,6 @@ import org.apache.commons.logging.Log; | |||
| import org.apache.commons.logging.LogFactory; | ||||
| 
 | ||||
| import org.springframework.context.ConfigurableApplicationContext; | ||||
| import org.springframework.core.annotation.AnnotationAttributes; | ||||
| import org.springframework.core.env.ConfigurableEnvironment; | ||||
| import org.springframework.core.env.Environment; | ||||
| import org.springframework.core.env.MapPropertySource; | ||||
|  | @ -39,6 +38,7 @@ import org.springframework.core.io.Resource; | |||
| import org.springframework.core.io.support.ResourcePropertySource; | ||||
| import org.springframework.test.context.TestPropertySource; | ||||
| import org.springframework.test.context.util.TestContextResourceUtils; | ||||
| import org.springframework.test.util.MetaAnnotationUtils.AnnotationDescriptor; | ||||
| import org.springframework.util.Assert; | ||||
| import org.springframework.util.ObjectUtils; | ||||
| import org.springframework.util.StringUtils; | ||||
|  | @ -96,15 +96,16 @@ public abstract class TestPropertySourceUtils { | |||
| 			annotationType.getName(), testClass.getName())); | ||||
| 
 | ||||
| 		while (descriptor != null) { | ||||
| 			AnnotationAttributes annAttrs = descriptor.getAnnotationAttributes(); | ||||
| 			TestPropertySource testPropertySource = descriptor.getMergedAnnotation(); | ||||
| 			Class<?> rootDeclaringClass = descriptor.getRootDeclaringClass(); | ||||
| 
 | ||||
| 			if (logger.isTraceEnabled()) { | ||||
| 				logger.trace(String.format("Retrieved @TestPropertySource attributes [%s] for declaring class [%s].", | ||||
| 					annAttrs, rootDeclaringClass.getName())); | ||||
| 				logger.trace(String.format("Retrieved @TestPropertySource [%s] for declaring class [%s].", | ||||
| 					testPropertySource, rootDeclaringClass.getName())); | ||||
| 			} | ||||
| 
 | ||||
| 			TestPropertySourceAttributes attributes = new TestPropertySourceAttributes(rootDeclaringClass, annAttrs); | ||||
| 			TestPropertySourceAttributes attributes = new TestPropertySourceAttributes(rootDeclaringClass, | ||||
| 				testPropertySource); | ||||
| 			if (logger.isTraceEnabled()) { | ||||
| 				logger.trace("Resolved TestPropertySource attributes: " + attributes); | ||||
| 			} | ||||
|  |  | |||
|  | @ -30,7 +30,6 @@ import org.springframework.beans.BeansException; | |||
| import org.springframework.beans.factory.BeanFactory; | ||||
| import org.springframework.beans.factory.annotation.BeanFactoryAnnotationUtils; | ||||
| import org.springframework.core.annotation.AnnotatedElementUtils; | ||||
| import org.springframework.core.annotation.AnnotationAttributes; | ||||
| import org.springframework.test.annotation.Rollback; | ||||
| import org.springframework.test.context.TestContext; | ||||
| import org.springframework.test.context.support.AbstractTestExecutionListener; | ||||
|  | @ -500,18 +499,18 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis | |||
| 		if (this.configurationAttributes == null) { | ||||
| 			Class<?> clazz = testContext.getTestClass(); | ||||
| 
 | ||||
| 			AnnotationAttributes annAttrs = AnnotatedElementUtils.findAnnotationAttributes(clazz, | ||||
| 				TransactionConfiguration.class.getName()); | ||||
| 			TransactionConfiguration txConfig = AnnotatedElementUtils.findAnnotation(clazz, | ||||
| 				TransactionConfiguration.class); | ||||
| 			if (logger.isDebugEnabled()) { | ||||
| 				logger.debug(String.format("Retrieved @TransactionConfiguration attributes [%s] for test class [%s].", | ||||
| 					annAttrs, clazz)); | ||||
| 				logger.debug(String.format("Retrieved @TransactionConfiguration [%s] for test class [%s].", | ||||
| 					txConfig, clazz)); | ||||
| 			} | ||||
| 
 | ||||
| 			String transactionManagerName; | ||||
| 			boolean defaultRollback; | ||||
| 			if (annAttrs != null) { | ||||
| 				transactionManagerName = annAttrs.getString("transactionManager"); | ||||
| 				defaultRollback = annAttrs.getBoolean("defaultRollback"); | ||||
| 			if (txConfig != null) { | ||||
| 				transactionManagerName = txConfig.transactionManager(); | ||||
| 				defaultRollback = txConfig.defaultRollback(); | ||||
| 			} | ||||
| 			else { | ||||
| 				transactionManagerName = DEFAULT_TRANSACTION_MANAGER_NAME; | ||||
|  |  | |||
|  | @ -272,12 +272,16 @@ public abstract class MetaAnnotationUtils { | |||
| 
 | ||||
| 		private final T annotation; | ||||
| 
 | ||||
| 		private final T mergedAnnotation; | ||||
| 
 | ||||
| 		private final AnnotationAttributes annotationAttributes; | ||||
| 
 | ||||
| 
 | ||||
| 		public AnnotationDescriptor(Class<?> rootDeclaringClass, T annotation) { | ||||
| 			this(rootDeclaringClass, rootDeclaringClass, null, annotation); | ||||
| 		} | ||||
| 
 | ||||
| 		@SuppressWarnings("unchecked") | ||||
| 		public AnnotationDescriptor(Class<?> rootDeclaringClass, Class<?> declaringClass, | ||||
| 				Annotation composedAnnotation, T annotation) { | ||||
| 			Assert.notNull(rootDeclaringClass, "rootDeclaringClass must not be null"); | ||||
|  | @ -286,8 +290,10 @@ public abstract class MetaAnnotationUtils { | |||
| 			this.declaringClass = declaringClass; | ||||
| 			this.composedAnnotation = composedAnnotation; | ||||
| 			this.annotation = annotation; | ||||
| 			this.annotationAttributes = AnnotatedElementUtils.findAnnotationAttributes( | ||||
| 					rootDeclaringClass, annotation.annotationType()); | ||||
| 			this.annotationAttributes = AnnotatedElementUtils.findAnnotationAttributes(rootDeclaringClass, | ||||
| 				annotation.annotationType().getName(), false, false); | ||||
| 			this.mergedAnnotation = AnnotationUtils.synthesizeAnnotation(annotationAttributes, | ||||
| 				(Class<T>) annotation.annotationType(), rootDeclaringClass); | ||||
| 		} | ||||
| 
 | ||||
| 		public Class<?> getRootDeclaringClass() { | ||||
|  | @ -302,6 +308,16 @@ public abstract class MetaAnnotationUtils { | |||
| 			return this.annotation; | ||||
| 		} | ||||
| 
 | ||||
| 		/** | ||||
| 		 * Get the annotation that was synthesized from the merged | ||||
| 		 * {@link #getAnnotationAttributes AnnotationAttributes}. | ||||
| 		 * @see #getAnnotationAttributes() | ||||
| 		 * @see AnnotationUtils#synthesizeAnnotation(java.util.Map, Class, java.lang.reflect.AnnotatedElement) | ||||
| 		 */ | ||||
| 		public T getMergedAnnotation() { | ||||
| 			return this.mergedAnnotation; | ||||
| 		} | ||||
| 
 | ||||
| 		public Class<? extends Annotation> getAnnotationType() { | ||||
| 			return this.annotation.annotationType(); | ||||
| 		} | ||||
|  |  | |||
|  | @ -79,7 +79,7 @@ public class SqlScriptsTestExecutionListenerTests { | |||
| 				containsString("attribute [scripts] and its alias [value]"))); | ||||
| 		exception.expectMessage(either(containsString("values of [{foo}] and [{bar}]")).or( | ||||
| 				containsString("values of [{bar}] and [{foo}]"))); | ||||
| 		exception.expectMessage(containsString("but only one declaration is permitted")); | ||||
| 		exception.expectMessage(containsString("but only one is permitted")); | ||||
| 		listener.beforeTestMethod(testContext); | ||||
| 	} | ||||
| 
 | ||||
|  |  | |||
|  | @ -401,7 +401,7 @@ public final class RequestMappingInfo implements RequestCondition<RequestMapping | |||
| 		Builder mappingName(String name); | ||||
| 
 | ||||
| 		/** | ||||
| 		 * Set a custom conditions to use. | ||||
| 		 * Set a custom condition to use. | ||||
| 		 */ | ||||
| 		Builder customCondition(RequestCondition<?> condition); | ||||
| 
 | ||||
|  |  | |||
|  | @ -26,6 +26,7 @@ import java.util.Set; | |||
| import javax.servlet.http.HttpServletRequest; | ||||
| 
 | ||||
| import org.aopalliance.intercept.MethodInterceptor; | ||||
| 
 | ||||
| import org.apache.commons.logging.Log; | ||||
| import org.apache.commons.logging.LogFactory; | ||||
| 
 | ||||
|  | @ -42,7 +43,6 @@ import org.springframework.core.DefaultParameterNameDiscoverer; | |||
| import org.springframework.core.MethodParameter; | ||||
| import org.springframework.core.ParameterNameDiscoverer; | ||||
| import org.springframework.core.annotation.AnnotatedElementUtils; | ||||
| import org.springframework.core.annotation.AnnotationAttributes; | ||||
| import org.springframework.objenesis.Objenesis; | ||||
| import org.springframework.objenesis.SpringObjenesis; | ||||
| import org.springframework.util.AntPathMatcher; | ||||
|  | @ -418,13 +418,11 @@ public class MvcUriComponentsBuilder { | |||
| 
 | ||||
| 	private static String getTypeRequestMapping(Class<?> controllerType) { | ||||
| 		Assert.notNull(controllerType, "'controllerType' must not be null"); | ||||
| 		String annotType = RequestMapping.class.getName(); | ||||
| 		AnnotationAttributes attrs = AnnotatedElementUtils.findAnnotationAttributes(controllerType, annotType); | ||||
| 		if (attrs == null) { | ||||
| 		RequestMapping requestMapping = AnnotatedElementUtils.findAnnotation(controllerType, RequestMapping.class); | ||||
| 		if (requestMapping == null) { | ||||
| 			return "/"; | ||||
| 		} | ||||
| 		String[] paths = attrs.getStringArray("path"); | ||||
| 		paths = ObjectUtils.isEmpty(paths) ? attrs.getStringArray("value") : paths; | ||||
| 		String[] paths = requestMapping.path(); | ||||
| 		if (ObjectUtils.isEmpty(paths) || StringUtils.isEmpty(paths[0])) { | ||||
| 			return "/"; | ||||
| 		} | ||||
|  | @ -435,13 +433,11 @@ public class MvcUriComponentsBuilder { | |||
| 	} | ||||
| 
 | ||||
| 	private static String getMethodRequestMapping(Method method) { | ||||
| 		String annotType = RequestMapping.class.getName(); | ||||
| 		AnnotationAttributes attrs = AnnotatedElementUtils.findAnnotationAttributes(method, annotType); | ||||
| 		if (attrs == null) { | ||||
| 		RequestMapping requestMapping = AnnotatedElementUtils.findAnnotation(method, RequestMapping.class); | ||||
| 		if (requestMapping == null) { | ||||
| 			throw new IllegalArgumentException("No @RequestMapping on: " + method.toGenericString()); | ||||
| 		} | ||||
| 		String[] paths = attrs.getStringArray("path"); | ||||
| 		paths = ObjectUtils.isEmpty(paths) ? attrs.getStringArray("value") : paths; | ||||
| 		String[] paths = requestMapping.path(); | ||||
| 		if (ObjectUtils.isEmpty(paths) || StringUtils.isEmpty(paths[0])) { | ||||
| 			return "/"; | ||||
| 		} | ||||
|  | @ -759,7 +755,6 @@ public class MvcUriComponentsBuilder { | |||
| 		 * that accept the controllerType. | ||||
| 		 */ | ||||
| 		@Deprecated | ||||
| 		@SuppressWarnings("unused") | ||||
| 		public MethodArgumentBuilder(Method method) { | ||||
| 			this(method.getDeclaringClass(), method); | ||||
| 		} | ||||
|  |  | |||
|  | @ -22,12 +22,10 @@ import java.util.List; | |||
| 
 | ||||
| import org.springframework.context.EmbeddedValueResolverAware; | ||||
| import org.springframework.core.annotation.AnnotatedElementUtils; | ||||
| import org.springframework.core.annotation.AnnotationAttributes; | ||||
| import org.springframework.core.annotation.AnnotationUtils; | ||||
| import org.springframework.stereotype.Controller; | ||||
| import org.springframework.util.Assert; | ||||
| import org.springframework.util.CollectionUtils; | ||||
| import org.springframework.util.ObjectUtils; | ||||
| import org.springframework.util.StringValueResolver; | ||||
| import org.springframework.web.accept.ContentNegotiationManager; | ||||
| import org.springframework.web.bind.annotation.CrossOrigin; | ||||
|  | @ -49,6 +47,7 @@ import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMappi | |||
|  * | ||||
|  * @author Arjen Poutsma | ||||
|  * @author Rossen Stoyanchev | ||||
|  * @author Sam Brannen | ||||
|  * @since 3.1 | ||||
|  */ | ||||
| public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMapping | ||||
|  | @ -212,7 +211,6 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi | |||
| 	 * @param handlerType the handler type for which to create the condition | ||||
| 	 * @return the condition, or {@code null} | ||||
| 	 */ | ||||
| 	@SuppressWarnings("unused") | ||||
| 	protected RequestCondition<?> getCustomTypeCondition(Class<?> handlerType) { | ||||
| 		return null; | ||||
| 	} | ||||
|  | @ -228,77 +226,41 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi | |||
| 	 * @param method the handler method for which to create the condition | ||||
| 	 * @return the condition, or {@code null} | ||||
| 	 */ | ||||
| 	@SuppressWarnings("unused") | ||||
| 	protected RequestCondition<?> getCustomMethodCondition(Method method) { | ||||
| 		return null; | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Transitional method used to invoke one of two createRequestMappingInfo | ||||
| 	 * variants one of which is deprecated. | ||||
| 	 * Delegates to {@link #createRequestMappingInfo(RequestMapping, RequestCondition)}, | ||||
| 	 * supplying the appropriate custom {@link RequestCondition} depending on whether | ||||
| 	 * the supplied {@code annotatedElement} is a class or method. | ||||
| 	 * | ||||
| 	 * @see #getCustomTypeCondition(Class) | ||||
| 	 * @see #getCustomMethodCondition(Method) | ||||
| 	 */ | ||||
| 	@SuppressWarnings("deprecation") | ||||
| 	private RequestMappingInfo createRequestMappingInfo(AnnotatedElement annotatedElement) { | ||||
| 		RequestMapping annotation; | ||||
| 		AnnotationAttributes attributes; | ||||
| 		RequestCondition<?> customCondition; | ||||
| 		String annotationType = RequestMapping.class.getName(); | ||||
| 		if (annotatedElement instanceof Class<?>) { | ||||
| 			Class<?> type = (Class<?>) annotatedElement; | ||||
| 			annotation = AnnotationUtils.findAnnotation(type, RequestMapping.class); | ||||
| 			attributes = AnnotatedElementUtils.findAnnotationAttributes(type, annotationType); | ||||
| 			customCondition = getCustomTypeCondition(type); | ||||
| 		} | ||||
| 		else { | ||||
| 			Method method = (Method) annotatedElement; | ||||
| 			annotation = AnnotationUtils.findAnnotation(method, RequestMapping.class); | ||||
| 			attributes = AnnotatedElementUtils.findAnnotationAttributes(method, annotationType); | ||||
| 			customCondition = getCustomMethodCondition(method); | ||||
| 		} | ||||
| 		RequestMappingInfo info = null; | ||||
| 		if (annotation != null) { | ||||
| 			info = createRequestMappingInfo(annotation, customCondition); | ||||
| 			if (info == null) { | ||||
| 				info = createRequestMappingInfo(attributes, customCondition); | ||||
| 			} | ||||
| 		} | ||||
| 		return info; | ||||
| 		RequestMapping requestMapping = AnnotatedElementUtils.findAnnotation(annotatedElement, RequestMapping.class); | ||||
| 		RequestCondition<?> customCondition = ((annotatedElement instanceof Class<?>) ? getCustomTypeCondition((Class<?>) annotatedElement) | ||||
| 				: getCustomMethodCondition((Method) annotatedElement)); | ||||
| 		return ((requestMapping != null) ? createRequestMappingInfo(requestMapping, customCondition) : null); | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Create a RequestMappingInfo from a RequestMapping annotation. | ||||
| 	 * @deprecated as of 4.2 after the introduction of support for | ||||
| 	 * {@code @RequestMapping} as meta-annotation. Please use | ||||
| 	 * {@link #createRequestMappingInfo(AnnotationAttributes, RequestCondition)}. | ||||
| 	 * Create a {@link RequestMappingInfo} from the supplied | ||||
| 	 * {@link RequestMapping @RequestMapping} annotation, which is either | ||||
| 	 * a directly declared annotation, a meta-annotation, or the synthesized | ||||
| 	 * result of merging annotation attributes within an annotation hierarchy. | ||||
| 	 */ | ||||
| 	@Deprecated | ||||
| 	@SuppressWarnings("unused") | ||||
| 	protected RequestMappingInfo createRequestMappingInfo(RequestMapping annotation, | ||||
| 	protected RequestMappingInfo createRequestMappingInfo(RequestMapping requestMapping, | ||||
| 			RequestCondition<?> customCondition) { | ||||
| 
 | ||||
| 		return null; | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Create a RequestMappingInfo from the attributes of an | ||||
| 	 * {@code @RequestMapping} annotation or a meta-annotation, i.e. a custom | ||||
| 	 * annotation annotated with {@code @RequestMapping}. | ||||
| 	 * @since 4.2 | ||||
| 	 */ | ||||
| 	protected RequestMappingInfo createRequestMappingInfo(AnnotationAttributes attributes, | ||||
| 			RequestCondition<?> customCondition) { | ||||
| 
 | ||||
| 		String[] paths = attributes.getStringArray("path"); | ||||
| 		paths = ObjectUtils.isEmpty(paths) ? attributes.getStringArray("value") : paths; | ||||
| 		paths = resolveEmbeddedValuesInPatterns(paths); | ||||
| 
 | ||||
| 		return RequestMappingInfo.paths(paths) | ||||
| 				.methods((RequestMethod[]) attributes.get("method")) | ||||
| 				.params(attributes.getStringArray("params")) | ||||
| 				.headers(attributes.getStringArray("headers")) | ||||
| 				.consumes(attributes.getStringArray("consumes")) | ||||
| 				.produces(attributes.getStringArray("produces")) | ||||
| 				.mappingName(attributes.getString("name")) | ||||
| 		return RequestMappingInfo.paths(resolveEmbeddedValuesInPatterns(requestMapping.path())) | ||||
| 				.methods(requestMapping.method()) | ||||
| 				.params(requestMapping.params()) | ||||
| 				.headers(requestMapping.headers()) | ||||
| 				.consumes(requestMapping.consumes()) | ||||
| 				.produces(requestMapping.produces()) | ||||
| 				.mappingName(requestMapping.name()) | ||||
| 				.customCondition(customCondition) | ||||
| 				.options(this.config) | ||||
| 				.build(); | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue