diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AbstractAliasAwareAnnotationAttributeExtractor.java b/spring-core/src/main/java/org/springframework/core/annotation/AbstractAliasAwareAnnotationAttributeExtractor.java index bf4a08dc348..9f73a6f2335 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/AbstractAliasAwareAnnotationAttributeExtractor.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/AbstractAliasAwareAnnotationAttributeExtractor.java @@ -35,7 +35,7 @@ import org.springframework.util.ObjectUtils; * @param the type of source supported by this extractor * @see Annotation * @see AliasFor - * @see AnnotationUtils#synthesizeAnnotation(Annotation, Object) + * @see AnnotationUtils#synthesizeAnnotation(Annotation, java.lang.reflect.AnnotatedElement) */ abstract class AbstractAliasAwareAnnotationAttributeExtractor implements AnnotationAttributeExtractor { diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java index fbd308c9cbf..33ec6472ac1 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java @@ -18,10 +18,16 @@ package org.springframework.core.annotation; import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; +import java.util.Collections; +import java.util.Comparator; +import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; import org.springframework.core.BridgeMethodResolver; +import org.springframework.core.annotation.MergedAnnotation.MapValues; +import org.springframework.core.annotation.MergedAnnotations.SearchStrategy; import org.springframework.lang.Nullable; import org.springframework.util.MultiValueMap; @@ -111,8 +117,15 @@ public abstract class AnnotatedElementUtils { * @see #getMetaAnnotationTypes(AnnotatedElement, String) * @see #hasMetaAnnotationTypes */ - public static Set getMetaAnnotationTypes(AnnotatedElement element, Class annotationType) { - return InternalAnnotatedElementUtils.getMetaAnnotationTypes(element, annotationType); + public static Set getMetaAnnotationTypes(AnnotatedElement element, + Class annotationType) { + + return MigrateMethod.from(() -> + InternalAnnotatedElementUtils.getMetaAnnotationTypes(element, annotationType) + ).withDescription(() -> element + " " + annotationType + ).to(() -> + getMetaAnnotationTypes(element, element.getAnnotation(annotationType)) + ); } /** @@ -129,8 +142,31 @@ public abstract class AnnotatedElementUtils { * @see #getMetaAnnotationTypes(AnnotatedElement, Class) * @see #hasMetaAnnotationTypes */ - public static Set getMetaAnnotationTypes(AnnotatedElement element, String annotationName) { - return InternalAnnotatedElementUtils.getMetaAnnotationTypes(element, annotationName); + public static Set getMetaAnnotationTypes(AnnotatedElement element, + String annotationName) { + + return MigrateMethod.from(() -> + InternalAnnotatedElementUtils.getMetaAnnotationTypes(element, annotationName) + ).withDescription(() -> element + " " + annotationName + ).to(() -> { + for (Annotation annotation : element.getAnnotations()) { + if (annotation.annotationType().getName().equals(annotationName)) { + return getMetaAnnotationTypes(element, annotation); + } + } + return Collections.emptySet(); + }); + } + + private static Set getMetaAnnotationTypes(AnnotatedElement element, + @Nullable Annotation annotation) { + + if (annotation == null) { + return Collections.emptySet(); + } + return getAnnotations(annotation.annotationType()).stream() + .map(MergedAnnotation::getType) + .collect(Collectors.toCollection(LinkedHashSet::new)); } /** @@ -145,8 +181,15 @@ public abstract class AnnotatedElementUtils { * @since 4.2.3 * @see #getMetaAnnotationTypes */ - public static boolean hasMetaAnnotationTypes(AnnotatedElement element, Class annotationType) { - return InternalAnnotatedElementUtils.hasMetaAnnotationTypes(element, annotationType); + public static boolean hasMetaAnnotationTypes(AnnotatedElement element, + Class annotationType) { + + return MigrateMethod.from(() -> + InternalAnnotatedElementUtils.hasMetaAnnotationTypes(element, annotationType) + ).to(() -> + getAnnotations(element).stream(annotationType) + .anyMatch(MergedAnnotation::isMetaPresent) + ); } /** @@ -161,8 +204,15 @@ public abstract class AnnotatedElementUtils { * @return {@code true} if a matching meta-annotation is present * @see #getMetaAnnotationTypes */ - public static boolean hasMetaAnnotationTypes(AnnotatedElement element, String annotationName) { - return InternalAnnotatedElementUtils.hasMetaAnnotationTypes(element, annotationName); + public static boolean hasMetaAnnotationTypes(AnnotatedElement element, + String annotationName) { + + return MigrateMethod.from(() -> + InternalAnnotatedElementUtils.hasMetaAnnotationTypes(element, annotationName) + ).to(() -> + getAnnotations(element).stream(annotationName) + .anyMatch(MergedAnnotation::isMetaPresent) + ); } /** @@ -179,8 +229,14 @@ public abstract class AnnotatedElementUtils { * @since 4.2.3 * @see #hasAnnotation(AnnotatedElement, Class) */ - public static boolean isAnnotated(AnnotatedElement element, Class annotationType) { - return InternalAnnotatedElementUtils.isAnnotated(element, annotationType); + public static boolean isAnnotated(AnnotatedElement element, + Class annotationType) { + + return MigrateMethod.from(() -> + InternalAnnotatedElementUtils.isAnnotated(element, annotationType) + ).to(() -> + getAnnotations(element).isPresent(annotationType) + ); } /** @@ -196,7 +252,11 @@ public abstract class AnnotatedElementUtils { * @return {@code true} if a matching annotation is present */ public static boolean isAnnotated(AnnotatedElement element, String annotationName) { - return InternalAnnotatedElementUtils.isAnnotated(element, annotationName); + return MigrateMethod.from(() -> + InternalAnnotatedElementUtils.isAnnotated(element, annotationName)) + .to(() -> + getAnnotations(element).isPresent(annotationName) + ); } /** @@ -219,8 +279,15 @@ public abstract class AnnotatedElementUtils { @Nullable public static AnnotationAttributes getMergedAnnotationAttributes( AnnotatedElement element, Class annotationType) { - return InternalAnnotatedElementUtils.getMergedAnnotationAttributes(element, - annotationType); + + return MigrateMethod.from(() -> + InternalAnnotatedElementUtils.getMergedAnnotationAttributes(element, + annotationType)) + .toNullable(() -> { + MergedAnnotation mergedAnnotation = getAnnotations(element) + .get(annotationType, null, MergedAnnotationSelectors.firstDirectlyDeclared()); + return getAnnotationAttributes(mergedAnnotation, false, false); + }); } /** @@ -242,9 +309,10 @@ public abstract class AnnotatedElementUtils { * @see #getAllAnnotationAttributes(AnnotatedElement, String) */ @Nullable - public static AnnotationAttributes getMergedAnnotationAttributes(AnnotatedElement element, String annotationName) { - return InternalAnnotatedElementUtils.getMergedAnnotationAttributes(element, - annotationName); + public static AnnotationAttributes getMergedAnnotationAttributes( + AnnotatedElement element, String annotationName) { + + return getMergedAnnotationAttributes(element, annotationName, false, false); } /** @@ -274,10 +342,18 @@ public abstract class AnnotatedElementUtils { * @see #getAllAnnotationAttributes(AnnotatedElement, String, boolean, boolean) */ @Nullable - public static AnnotationAttributes getMergedAnnotationAttributes(AnnotatedElement element, - String annotationName, boolean classValuesAsString, boolean nestedAnnotationsAsMap) { - return InternalAnnotatedElementUtils.getMergedAnnotationAttributes(element, - annotationName, classValuesAsString, nestedAnnotationsAsMap); + public static AnnotationAttributes getMergedAnnotationAttributes( + AnnotatedElement element, String annotationName, boolean classValuesAsString, + boolean nestedAnnotationsAsMap) { + + return MigrateMethod.from(() -> + InternalAnnotatedElementUtils.getMergedAnnotationAttributes(element, + annotationName, classValuesAsString, nestedAnnotationsAsMap) + ).toNullable(() -> { + MergedAnnotation mergedAnnotation = getAnnotations(element) + .get(annotationName, null, MergedAnnotationSelectors.firstDirectlyDeclared()); + return getAnnotationAttributes(mergedAnnotation, classValuesAsString, nestedAnnotationsAsMap); + }); } /** @@ -299,8 +375,15 @@ public abstract class AnnotatedElementUtils { * @see AnnotationUtils#synthesizeAnnotation(Map, Class, AnnotatedElement) */ @Nullable - public static A getMergedAnnotation(AnnotatedElement element, Class annotationType) { - return InternalAnnotatedElementUtils.getMergedAnnotation(element, annotationType); + public static A getMergedAnnotation(AnnotatedElement element, + Class annotationType) { + return MigrateMethod.from(() -> + InternalAnnotatedElementUtils.getMergedAnnotation(element, annotationType) + ).toNullable(() -> + getAnnotations(element) + .get(annotationType, null, MergedAnnotationSelectors.firstDirectlyDeclared()) + .synthesize(MergedAnnotation::isPresent).orElse(null) + ); } /** @@ -323,9 +406,15 @@ public abstract class AnnotatedElementUtils { * @see #getAllAnnotationAttributes(AnnotatedElement, String) * @see #findAllMergedAnnotations(AnnotatedElement, Class) */ - public static Set getAllMergedAnnotations(AnnotatedElement element, Class annotationType) { - return InternalAnnotatedElementUtils.getAllMergedAnnotations(element, - annotationType); + public static Set getAllMergedAnnotations( + AnnotatedElement element, Class annotationType) { + + return MigrateMethod.from(() -> + InternalAnnotatedElementUtils.getAllMergedAnnotations(element, annotationType) + ).to(() -> + getAnnotations(element).stream(annotationType) + .collect(MergedAnnotationCollectors.toAnnotationSet()) + ); } /** @@ -346,9 +435,17 @@ public abstract class AnnotatedElementUtils { * @since 5.1 * @see #getAllMergedAnnotations(AnnotatedElement, Class) */ - public static Set getAllMergedAnnotations(AnnotatedElement element, Set> annotationTypes) { - return InternalAnnotatedElementUtils.getAllMergedAnnotations(element, - annotationTypes); + public static Set getAllMergedAnnotations(AnnotatedElement element, + Set> annotationTypes) { + + return MigrateMethod.from(() -> + InternalAnnotatedElementUtils.getAllMergedAnnotations(element, + annotationTypes) + ).to(() -> + getAnnotations(element).stream() + .filter(MergedAnnotationPredicates.typeIn(annotationTypes)) + .collect(MergedAnnotationCollectors.toAnnotationSet()) + ); } /** @@ -375,10 +472,10 @@ public abstract class AnnotatedElementUtils { * @see #getAllMergedAnnotations(AnnotatedElement, Class) * @see #getMergedRepeatableAnnotations(AnnotatedElement, Class, Class) */ - public static Set getMergedRepeatableAnnotations(AnnotatedElement element, - Class annotationType) { - return InternalAnnotatedElementUtils.getMergedRepeatableAnnotations(element, - annotationType); + public static Set getMergedRepeatableAnnotations( + AnnotatedElement element, Class annotationType) { + + return getMergedRepeatableAnnotations(element, annotationType, null); } /** @@ -407,10 +504,18 @@ public abstract class AnnotatedElementUtils { * @see #getMergedAnnotation(AnnotatedElement, Class) * @see #getAllMergedAnnotations(AnnotatedElement, Class) */ - public static Set getMergedRepeatableAnnotations(AnnotatedElement element, - Class annotationType, @Nullable Class containerType) { - return InternalAnnotatedElementUtils.getMergedRepeatableAnnotations(element, - annotationType, containerType); + public static Set getMergedRepeatableAnnotations( + AnnotatedElement element, Class annotationType, + @Nullable Class containerType) { + + return MigrateMethod.from(() -> + InternalAnnotatedElementUtils.getMergedRepeatableAnnotations(element, + annotationType, containerType) + ).to(() -> + getRepeatableAnnotations(element, containerType, annotationType) + .stream(annotationType) + .collect(MergedAnnotationCollectors.toAnnotationSet()) + ); } /** @@ -428,9 +533,10 @@ public abstract class AnnotatedElementUtils { * @see #getAllAnnotationAttributes(AnnotatedElement, String, boolean, boolean) */ @Nullable - public static MultiValueMap getAllAnnotationAttributes(AnnotatedElement element, String annotationName) { - return InternalAnnotatedElementUtils.getAllAnnotationAttributes(element, - annotationName); + public static MultiValueMap getAllAnnotationAttributes( + AnnotatedElement element, String annotationName) { + + return getAllAnnotationAttributes(element, annotationName, false, false); } /** @@ -452,10 +558,20 @@ public abstract class AnnotatedElementUtils { * attributes from all annotations found, or {@code null} if not found */ @Nullable - public static MultiValueMap getAllAnnotationAttributes(AnnotatedElement element, - String annotationName, final boolean classValuesAsString, final boolean nestedAnnotationsAsMap) { - return InternalAnnotatedElementUtils.getAllAnnotationAttributes(element, - annotationName, classValuesAsString, nestedAnnotationsAsMap); + public static MultiValueMap getAllAnnotationAttributes( + AnnotatedElement element, String annotationName, + final boolean classValuesAsString, final boolean nestedAnnotationsAsMap) { + + return MigrateMethod.from(() -> + InternalAnnotatedElementUtils.getAllAnnotationAttributes(element, + annotationName, classValuesAsString, nestedAnnotationsAsMap) + ).toNullable(() ->{ + MapValues[] mapValues = MapValues.of(classValuesAsString, nestedAnnotationsAsMap); + return getAnnotations(element).stream(annotationName) + .filter(MergedAnnotationPredicates.unique(AnnotatedElementUtils::parentAndType)) + .map(MergedAnnotation::withNonMergedAttributes) + .collect(MergedAnnotationCollectors.toMultiValueMap(AnnotatedElementUtils::nullIfEmpty, mapValues)); + }); } /** @@ -472,8 +588,18 @@ public abstract class AnnotatedElementUtils { * @since 4.3 * @see #isAnnotated(AnnotatedElement, Class) */ - public static boolean hasAnnotation(AnnotatedElement element, Class annotationType) { - return InternalAnnotatedElementUtils.hasAnnotation(element, annotationType); + public static boolean hasAnnotation(AnnotatedElement element, + Class annotationType) { + + return MigrateMethod.from(() -> + InternalAnnotatedElementUtils.hasAnnotation(element, annotationType) + ).withSkippedEquivalentCheck(() -> + InternalAnnotatedElementUtils.hasAnnotation(element, annotationType) && + InternalAnnotatedElementUtils.findMergedAnnotationAttributes(element, + annotationType.getName(), false, false) == null + ).to(() -> + findAnnotations(element).isPresent(annotationType) + ); } /** @@ -504,10 +630,18 @@ public abstract class AnnotatedElementUtils { * @see #getMergedAnnotationAttributes(AnnotatedElement, String, boolean, boolean) */ @Nullable - public static AnnotationAttributes findMergedAnnotationAttributes(AnnotatedElement element, - Class annotationType, boolean classValuesAsString, boolean nestedAnnotationsAsMap) { - return InternalAnnotatedElementUtils.findMergedAnnotationAttributes(element, - annotationType, classValuesAsString, nestedAnnotationsAsMap); + public static AnnotationAttributes findMergedAnnotationAttributes( + AnnotatedElement element, Class annotationType, + boolean classValuesAsString, boolean nestedAnnotationsAsMap) { + + return MigrateMethod.from(() -> + InternalAnnotatedElementUtils.findMergedAnnotationAttributes(element, + annotationType, classValuesAsString, nestedAnnotationsAsMap) + ).toNullable(() -> { + MergedAnnotation mergedAnnotation = findAnnotations(element) + .get(annotationType, null, MergedAnnotationSelectors.firstDirectlyDeclared()); + return getAnnotationAttributes(mergedAnnotation, classValuesAsString, nestedAnnotationsAsMap); + }); } /** @@ -538,10 +672,18 @@ public abstract class AnnotatedElementUtils { * @see #getMergedAnnotationAttributes(AnnotatedElement, String, boolean, boolean) */ @Nullable - public static AnnotationAttributes findMergedAnnotationAttributes(AnnotatedElement element, - String annotationName, boolean classValuesAsString, boolean nestedAnnotationsAsMap) { - return InternalAnnotatedElementUtils.findMergedAnnotationAttributes(element, - annotationName, classValuesAsString, nestedAnnotationsAsMap); + public static AnnotationAttributes findMergedAnnotationAttributes( + AnnotatedElement element, String annotationName, boolean classValuesAsString, + boolean nestedAnnotationsAsMap) { + + return MigrateMethod.from(() -> + InternalAnnotatedElementUtils.findMergedAnnotationAttributes(element, + annotationName, classValuesAsString, nestedAnnotationsAsMap) + ).toNullable(() -> { + MergedAnnotation mergedAnnotation = findAnnotations(element) + .get(annotationName, null, MergedAnnotationSelectors.firstDirectlyDeclared()); + return getAnnotationAttributes(mergedAnnotation, classValuesAsString, nestedAnnotationsAsMap); + }); } /** @@ -563,9 +705,16 @@ public abstract class AnnotatedElementUtils { * @see #getMergedAnnotationAttributes(AnnotatedElement, Class) */ @Nullable - public static A findMergedAnnotation(AnnotatedElement element, Class annotationType) { - return InternalAnnotatedElementUtils.findMergedAnnotation(element, - annotationType); + public static A findMergedAnnotation(AnnotatedElement element, + Class annotationType) { + + return MigrateMethod.from(() -> + InternalAnnotatedElementUtils.findMergedAnnotation(element, annotationType) + ).toNullable(() -> + findAnnotations(element) + .get(annotationType, null, MergedAnnotationSelectors.firstDirectlyDeclared()) + .synthesize(MergedAnnotation::isPresent).orElse(null) + ); } /** @@ -587,9 +736,17 @@ public abstract class AnnotatedElementUtils { * @see #findMergedAnnotation(AnnotatedElement, Class) * @see #getAllMergedAnnotations(AnnotatedElement, Class) */ - public static Set findAllMergedAnnotations(AnnotatedElement element, Class annotationType) { - return InternalAnnotatedElementUtils.findAllMergedAnnotations(element, - annotationType); + public static Set findAllMergedAnnotations( + AnnotatedElement element, Class annotationType) { + + return MigrateMethod.from(() -> + InternalAnnotatedElementUtils.findAllMergedAnnotations(element, annotationType) + ).withSkippedOriginalExceptionCheck().to(() -> + findAnnotations(element) + .stream(annotationType) + .sorted(highAggregateIndexesFirst()) + .collect(MergedAnnotationCollectors.toAnnotationSet()) + ); } /** @@ -610,9 +767,17 @@ public abstract class AnnotatedElementUtils { * @since 5.1 * @see #findAllMergedAnnotations(AnnotatedElement, Class) */ - public static Set findAllMergedAnnotations(AnnotatedElement element, Set> annotationTypes) { - return InternalAnnotatedElementUtils.findAllMergedAnnotations(element, - annotationTypes); + public static Set findAllMergedAnnotations(AnnotatedElement element, + Set> annotationTypes) { + + return MigrateMethod.from(() -> + InternalAnnotatedElementUtils.findAllMergedAnnotations(element, annotationTypes) + ).to(()-> + findAnnotations(element).stream() + .filter(MergedAnnotationPredicates.typeIn(annotationTypes)) + .sorted(highAggregateIndexesFirst()) + .collect(MergedAnnotationCollectors.toAnnotationSet()) + ); } /** @@ -639,10 +804,10 @@ public abstract class AnnotatedElementUtils { * @see #findAllMergedAnnotations(AnnotatedElement, Class) * @see #findMergedRepeatableAnnotations(AnnotatedElement, Class, Class) */ - public static Set findMergedRepeatableAnnotations(AnnotatedElement element, - Class annotationType) { - return InternalAnnotatedElementUtils.findMergedRepeatableAnnotations(element, - annotationType); + public static Set findMergedRepeatableAnnotations( + AnnotatedElement element, Class annotationType) { + + return findMergedRepeatableAnnotations(element, annotationType, null); } /** @@ -671,10 +836,75 @@ public abstract class AnnotatedElementUtils { * @see #findMergedAnnotation(AnnotatedElement, Class) * @see #findAllMergedAnnotations(AnnotatedElement, Class) */ - public static Set findMergedRepeatableAnnotations(AnnotatedElement element, - Class annotationType, @Nullable Class containerType) { - return InternalAnnotatedElementUtils.findMergedRepeatableAnnotations(element, - annotationType, containerType); + public static Set findMergedRepeatableAnnotations( + AnnotatedElement element, Class annotationType, + @Nullable Class containerType) { + + return MigrateMethod.from(() -> + InternalAnnotatedElementUtils.findMergedRepeatableAnnotations(element, + annotationType, containerType) + ).to(() -> + findRepeatableAnnotations(element, containerType, annotationType) + .stream(annotationType) + .sorted(highAggregateIndexesFirst()) + .collect(MergedAnnotationCollectors.toAnnotationSet()) + ); + } + + private static MergedAnnotations getAnnotations(AnnotatedElement element) { + return MergedAnnotations.from(element, SearchStrategy.INHERITED_ANNOTATIONS, + RepeatableContainers.none(), AnnotationUtils.JAVA_LANG_ANNOTATION_FILTER); + } + + private static MergedAnnotations getRepeatableAnnotations(AnnotatedElement element, + @Nullable Class containerType, + Class annotationType) { + + RepeatableContainers repeatableContainers = RepeatableContainers.of(annotationType, containerType); + return MergedAnnotations.from(element, SearchStrategy.INHERITED_ANNOTATIONS, + repeatableContainers, AnnotationUtils.JAVA_LANG_ANNOTATION_FILTER); + } + + private static MergedAnnotations findAnnotations(AnnotatedElement element) { + return MergedAnnotations.from(element, SearchStrategy.EXHAUSTIVE, + RepeatableContainers.none(), AnnotationUtils.JAVA_LANG_ANNOTATION_FILTER); + } + + private static MergedAnnotations findRepeatableAnnotations(AnnotatedElement element, + @Nullable Class containerType, + Class annotationType) { + + RepeatableContainers repeatableContainers = RepeatableContainers.of(annotationType, containerType); + return MergedAnnotations.from(element, SearchStrategy.EXHAUSTIVE, + repeatableContainers, AnnotationUtils.JAVA_LANG_ANNOTATION_FILTER); + } + + private static Object parentAndType(MergedAnnotation annotation) { + if (annotation.getParent() == null) { + return annotation.getType(); + } + return annotation.getParent().getType() + ":" + annotation.getParent().getType(); + } + + @Nullable + private static MultiValueMap nullIfEmpty( + MultiValueMap map) { + return map.isEmpty() ? null : map; + } + + private static Comparator> highAggregateIndexesFirst() { + return Comparator.>comparingInt(MergedAnnotation::getAggregateIndex).reversed(); + } + + @Nullable + private static AnnotationAttributes getAnnotationAttributes( + MergedAnnotation annotation, boolean classValuesAsString, + boolean nestedAnnotationsAsMap) { + if (!annotation.isPresent()) { + return null; + } + return annotation.asMap((mergedAnnotation) -> new AnnotationAttributes(), + MapValues.of(classValuesAsString, nestedAnnotationsAsMap)); } } diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAttributes.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAttributes.java index 577a17e3491..c7ade76a481 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAttributes.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAttributes.java @@ -113,6 +113,22 @@ public class AnnotationAttributes extends LinkedHashMap { this.displayName = annotationType.getName(); } + /** + * Create a possibly already validated new, empty + * {@link AnnotationAttributes} instance for the specified + * {@code annotationType}. + * @param annotationType the type of annotation represented by this + * {@code AnnotationAttributes} instance; never {@code null} + * @param validated if the attributes are considered already validated + * @since 5.2 + */ + AnnotationAttributes(Class annotationType, boolean validated) { + Assert.notNull(annotationType, "'annotationType' must not be null"); + this.annotationType = annotationType; + this.displayName = annotationType.getName(); + this.validated = validated; + } + /** * Create a new, empty {@link AnnotationAttributes} instance for the * specified {@code annotationType}. diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java index b6ca487ad50..aed9f430480 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java @@ -18,14 +18,28 @@ package org.springframework.core.annotation; import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Array; import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Arrays; import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.NoSuchElementException; import java.util.Set; +import java.util.function.Function; import org.springframework.core.BridgeMethodResolver; +import org.springframework.core.annotation.AnnotationTypeMapping.MirrorSets.MirrorSet; +import org.springframework.core.annotation.InternalAnnotationUtils.DefaultValueHolder; +import org.springframework.core.annotation.MergedAnnotation.MapValues; +import org.springframework.core.annotation.MergedAnnotations.SearchStrategy; import org.springframework.lang.Nullable; +import org.springframework.util.ClassUtils; +import org.springframework.util.ConcurrentReferenceHashMap; +import org.springframework.util.StringUtils; /** * General utility methods for working with annotations, handling meta-annotations, @@ -95,9 +109,16 @@ public abstract class AnnotationUtils { /** * The attribute name for annotations with a single element. */ - public static final String VALUE = "value"; + public static final String VALUE = MergedAnnotation.VALUE; + static final AnnotationFilter JAVA_LANG_ANNOTATION_FILTER = + AnnotationFilter.packages("java.lang.annotation"); + + + private static Map, Map> defaultValuesCache = + new ConcurrentReferenceHashMap<>(); + /** * Determine whether the given class is a candidate for carrying one of the specified * annotations (at type, method or field level). @@ -160,8 +181,16 @@ public abstract class AnnotationUtils { * @since 4.0 */ @Nullable - public static A getAnnotation(Annotation annotation, Class annotationType) { - return InternalAnnotationUtils.getAnnotation(annotation, annotationType); + public static A getAnnotation(Annotation annotation, + Class annotationType) { + + return MigrateMethod.from(() -> + InternalAnnotationUtils.getAnnotation(annotation, annotationType) + ).toNullable(() -> + MergedAnnotations.from(annotation) + .get(annotationType).withNonMergedAttributes() + .synthesize(AnnotationUtils::isSingleLevelPresent).orElse(null) + ); } /** @@ -177,8 +206,17 @@ public abstract class AnnotationUtils { * @since 3.1 */ @Nullable - public static A getAnnotation(AnnotatedElement annotatedElement, Class annotationType) { - return InternalAnnotationUtils.getAnnotation(annotatedElement, annotationType); + public static A getAnnotation( + AnnotatedElement annotatedElement, Class annotationType) { + + return MigrateMethod.from(() -> + InternalAnnotationUtils.getAnnotation(annotatedElement, annotationType) + ).toNullable(() -> + MergedAnnotations.from(annotatedElement, SearchStrategy.INHERITED_ANNOTATIONS, + RepeatableContainers.none(), AnnotationFilter.PLAIN) + .get(annotationType).withNonMergedAttributes() + .synthesize(AnnotationUtils::isSingleLevelPresent).orElse(null) + ); } /** @@ -196,8 +234,17 @@ public abstract class AnnotationUtils { * @see #getAnnotation(AnnotatedElement, Class) */ @Nullable - public static A getAnnotation(Method method, Class annotationType) { - return InternalAnnotationUtils.getAnnotation(method, annotationType); + public static A getAnnotation(Method method, + Class annotationType) { + + return MigrateMethod.from(() -> + InternalAnnotationUtils.getAnnotation(method, annotationType) + ).toNullable(() -> + MergedAnnotations.from(method, SearchStrategy.INHERITED_ANNOTATIONS, + RepeatableContainers.none(), AnnotationFilter.PLAIN) + .get(annotationType).withNonMergedAttributes() + .synthesize(AnnotationUtils::isSingleLevelPresent).orElse(null) + ); } /** @@ -213,7 +260,15 @@ public abstract class AnnotationUtils { */ @Nullable public static Annotation[] getAnnotations(AnnotatedElement annotatedElement) { - return InternalAnnotationUtils.getAnnotations(annotatedElement); + return MigrateMethod.from(() -> + InternalAnnotationUtils.getAnnotations(annotatedElement) + ).toNullable(() -> + MergedAnnotations.from(annotatedElement, SearchStrategy.INHERITED_ANNOTATIONS, + RepeatableContainers.none(), AnnotationFilter.NONE).stream() + .filter(MergedAnnotation::isDirectlyPresent) + .map(MergedAnnotation::withNonMergedAttributes) + .collect(MergedAnnotationCollectors.toAnnotationArray()) + ); } /** @@ -230,7 +285,15 @@ public abstract class AnnotationUtils { */ @Nullable public static Annotation[] getAnnotations(Method method) { - return InternalAnnotationUtils.getAnnotations(method); + return MigrateMethod.from(() -> + InternalAnnotationUtils.getAnnotations(method) + ).toNullable(()-> + MergedAnnotations.from(method, SearchStrategy.INHERITED_ANNOTATIONS, + RepeatableContainers.none(), AnnotationFilter.NONE).stream() + .filter(MergedAnnotation::isDirectlyPresent) + .map(MergedAnnotation::withNonMergedAttributes) + .collect(MergedAnnotationCollectors.toAnnotationArray()) + ); } /** @@ -260,10 +323,10 @@ public abstract class AnnotationUtils { * @see java.lang.annotation.Repeatable * @see java.lang.reflect.AnnotatedElement#getAnnotationsByType */ - public static Set getRepeatableAnnotations(AnnotatedElement annotatedElement, - Class annotationType) { - return InternalAnnotationUtils.getRepeatableAnnotations(annotatedElement, - annotationType); + public static Set getRepeatableAnnotations( + AnnotatedElement annotatedElement, Class annotationType) { + + return getRepeatableAnnotations(annotatedElement, annotationType, null); } /** @@ -296,10 +359,25 @@ public abstract class AnnotationUtils { * @see java.lang.annotation.Repeatable * @see java.lang.reflect.AnnotatedElement#getAnnotationsByType */ - public static Set getRepeatableAnnotations(AnnotatedElement annotatedElement, - Class annotationType, @Nullable Class containerAnnotationType) { - return InternalAnnotationUtils.getRepeatableAnnotations(annotatedElement, - annotationType, containerAnnotationType); + public static Set getRepeatableAnnotations( + AnnotatedElement annotatedElement, Class annotationType, + @Nullable Class containerAnnotationType) { + + return MigrateMethod.from(() -> + InternalAnnotationUtils.getRepeatableAnnotations(annotatedElement, + annotationType, containerAnnotationType) + ).to(() -> { + RepeatableContainers repeatableContainers = containerAnnotationType != null ? + RepeatableContainers.of(annotationType, containerAnnotationType) : + RepeatableContainers.standardRepeatables(); + AnnotationFilter annotationFilter = AnnotationFilter.mostAppropriateFor(annotationType); + return MergedAnnotations.from(annotatedElement, SearchStrategy.SUPER_CLASS, + repeatableContainers, annotationFilter) + .stream(annotationType) + .filter(MergedAnnotationPredicates.firstRunOf(MergedAnnotation::getAggregateIndex)) + .map(MergedAnnotation::withNonMergedAttributes) + .collect(MergedAnnotationCollectors.toAnnotationSet()); + }); } /** @@ -330,10 +408,10 @@ public abstract class AnnotationUtils { * @see java.lang.annotation.Repeatable * @see java.lang.reflect.AnnotatedElement#getDeclaredAnnotationsByType */ - public static Set getDeclaredRepeatableAnnotations(AnnotatedElement annotatedElement, - Class annotationType) { - return InternalAnnotationUtils.getDeclaredRepeatableAnnotations(annotatedElement, - annotationType); + public static Set getDeclaredRepeatableAnnotations( + AnnotatedElement annotatedElement, Class annotationType) { + + return getDeclaredRepeatableAnnotations(annotatedElement, annotationType, null); } /** @@ -366,10 +444,25 @@ public abstract class AnnotationUtils { * @see java.lang.annotation.Repeatable * @see java.lang.reflect.AnnotatedElement#getDeclaredAnnotationsByType */ - public static Set getDeclaredRepeatableAnnotations(AnnotatedElement annotatedElement, - Class annotationType, @Nullable Class containerAnnotationType) { - return InternalAnnotationUtils.getDeclaredRepeatableAnnotations(annotatedElement, - annotationType, containerAnnotationType); + public static Set getDeclaredRepeatableAnnotations( + AnnotatedElement annotatedElement, Class annotationType, + @Nullable Class containerAnnotationType) { + + return MigrateMethod.from(() -> + InternalAnnotationUtils.getDeclaredRepeatableAnnotations(annotatedElement, + annotationType, containerAnnotationType) + ).to(() -> { + RepeatableContainers repeatableContainers = containerAnnotationType != null ? + RepeatableContainers.of(annotationType, containerAnnotationType) : + RepeatableContainers.standardRepeatables(); + AnnotationFilter annotationFilter = AnnotationFilter.mostAppropriateFor( + annotationType, containerAnnotationType); + return MergedAnnotations.from(annotatedElement, SearchStrategy.DIRECT, + repeatableContainers, annotationFilter).stream(annotationType) + .map(MergedAnnotation::withNonMergedAttributes) + .collect(MergedAnnotationCollectors.toAnnotationSet()); + } + ); } /** @@ -389,8 +482,18 @@ public abstract class AnnotationUtils { * @since 4.2 */ @Nullable - public static A findAnnotation(AnnotatedElement annotatedElement, Class annotationType) { - return InternalAnnotationUtils.findAnnotation(annotatedElement, annotationType); + public static A findAnnotation( + AnnotatedElement annotatedElement, Class annotationType) { + + return MigrateMethod.from(() -> + InternalAnnotationUtils.findAnnotation(annotatedElement, annotationType) + ).toNullable(() ->{ + AnnotationFilter annotationFilter = AnnotationFilter.mostAppropriateFor(annotationType); + return MergedAnnotations.from(annotatedElement, SearchStrategy.DIRECT, + RepeatableContainers.none(), annotationFilter) + .get(annotationType).withNonMergedAttributes() + .synthesize(MergedAnnotation::isPresent).orElse(null); + }); } /** @@ -409,8 +512,19 @@ public abstract class AnnotationUtils { * @see #getAnnotation(Method, Class) */ @Nullable - public static A findAnnotation(Method method, @Nullable Class annotationType) { - return InternalAnnotationUtils.findAnnotation(method, annotationType); + public static A findAnnotation(Method method, + @Nullable Class annotationType) { + + return MigrateMethod.from(() -> + InternalAnnotationUtils.findAnnotation(method, annotationType) + ).toNullable(() -> { + AnnotationFilter annotationFilter = AnnotationFilter.mostAppropriateFor(annotationType); + return MergedAnnotations.from(method, SearchStrategy.EXHAUSTIVE, + RepeatableContainers.none(), annotationFilter) + .get(annotationType).withNonMergedAttributes() + .synthesize(MergedAnnotation::isPresent).orElse(null); + + }); } /** @@ -436,8 +550,19 @@ public abstract class AnnotationUtils { * @return the first matching annotation, or {@code null} if not found */ @Nullable - public static A findAnnotation(Class clazz, Class annotationType) { - return InternalAnnotationUtils.findAnnotation(clazz, annotationType); + public static A findAnnotation(Class clazz, + Class annotationType) { + + return MigrateMethod.from(() -> + InternalAnnotationUtils.findAnnotation(clazz, annotationType) + ).toNullable(() -> { + AnnotationFilter annotationFilter = AnnotationFilter.mostAppropriateFor(annotationType); + return MergedAnnotations.from(clazz, SearchStrategy.EXHAUSTIVE, + RepeatableContainers.none(), annotationFilter) + .get(annotationType).withNonMergedAttributes() + .synthesize(MergedAnnotation::isPresent).orElse(null); + } + ); } /** @@ -463,9 +588,20 @@ public abstract class AnnotationUtils { * @see #isAnnotationDeclaredLocally(Class, Class) */ @Nullable - public static Class findAnnotationDeclaringClass(Class annotationType, @Nullable Class clazz) { - return InternalAnnotationUtils.findAnnotationDeclaringClass(annotationType, - clazz); + public static Class findAnnotationDeclaringClass( + Class annotationType, @Nullable Class clazz) { + + return MigrateMethod.> from(() -> + InternalAnnotationUtils.findAnnotationDeclaringClass(annotationType, + clazz) + ).toNullable(() -> { + AnnotationFilter annotationFilter = AnnotationFilter.mostAppropriateFor(annotationType); + return (Class) MergedAnnotations.from(clazz, SearchStrategy.SUPER_CLASS, + RepeatableContainers.none(), annotationFilter) + .get(annotationType, MergedAnnotation::isDirectlyPresent) + .getSource(); + } + ); } /** @@ -495,8 +631,20 @@ public abstract class AnnotationUtils { @Nullable public static Class findAnnotationDeclaringClassForTypes( List> annotationTypes, @Nullable Class clazz) { - return InternalAnnotationUtils.findAnnotationDeclaringClassForTypes( - annotationTypes, clazz); + + return MigrateMethod.> from(() -> + InternalAnnotationUtils.findAnnotationDeclaringClassForTypes( + annotationTypes, clazz) + ).toNullable(() -> { + AnnotationFilter annotationFilter = AnnotationFilter.mostAppropriateFor(annotationTypes); + return (Class) MergedAnnotations.from(clazz, SearchStrategy.SUPER_CLASS, + RepeatableContainers.none(), annotationFilter) + .stream() + .filter(MergedAnnotationPredicates.typeIn(annotationTypes).and(MergedAnnotation::isDirectlyPresent)) + .map(MergedAnnotation::getSource) + .findFirst().orElse(null); + } + ); } /** @@ -517,8 +665,14 @@ public abstract class AnnotationUtils { * @see java.lang.Class#getDeclaredAnnotation(Class) * @see #isAnnotationInherited(Class, Class) */ - public static boolean isAnnotationDeclaredLocally(Class annotationType, Class clazz) { - return InternalAnnotationUtils.isAnnotationDeclaredLocally(annotationType, clazz); + public static boolean isAnnotationDeclaredLocally( + Class annotationType, Class clazz) { + + return MigrateMethod.from(() -> + InternalAnnotationUtils.isAnnotationDeclaredLocally(annotationType, clazz) + ).withSkippedOriginalExceptionCheck().to(() -> + MergedAnnotations.from(clazz).get(annotationType).isDirectlyPresent() + ); } /** @@ -540,8 +694,18 @@ public abstract class AnnotationUtils { * @see Class#isAnnotationPresent(Class) * @see #isAnnotationDeclaredLocally(Class, Class) */ - public static boolean isAnnotationInherited(Class annotationType, Class clazz) { - return InternalAnnotationUtils.isAnnotationInherited(annotationType, clazz); + public static boolean isAnnotationInherited( + Class annotationType, Class clazz) { + + return MigrateMethod.from(() -> + InternalAnnotationUtils.isAnnotationInherited(annotationType, clazz) + ).to(() -> + MergedAnnotations.from(clazz, SearchStrategy.INHERITED_ANNOTATIONS) + .stream(annotationType) + .filter(MergedAnnotation::isDirectlyPresent) + .findFirst().orElseGet(MergedAnnotation::missing) + .getAggregateIndex() > 0 + ); } /** @@ -552,10 +716,17 @@ public abstract class AnnotationUtils { * @return {@code true} if such an annotation is meta-present * @since 4.2.1 */ - public static boolean isAnnotationMetaPresent(Class annotationType, + public static boolean isAnnotationMetaPresent( + Class annotationType, @Nullable Class metaAnnotationType) { - return InternalAnnotationUtils.isAnnotationMetaPresent(annotationType, - metaAnnotationType); + + return MigrateMethod.from(() -> + InternalAnnotationUtils.isAnnotationMetaPresent(annotationType, + metaAnnotationType) + ).to(() -> + MergedAnnotations.from(annotationType, SearchStrategy.EXHAUSTIVE) + .isPresent(annotationType) + ); } /** @@ -565,7 +736,11 @@ public abstract class AnnotationUtils { * @return {@code true} if the annotation is in the {@code java.lang.annotation} package */ public static boolean isInJavaLangAnnotationPackage(@Nullable Annotation annotation) { - return InternalAnnotationUtils.isInJavaLangAnnotationPackage(annotation); + return MigrateMethod.from(() -> + InternalAnnotationUtils.isInJavaLangAnnotationPackage(annotation) + ).to(() -> + JAVA_LANG_ANNOTATION_FILTER.matches(annotation) + ); } /** @@ -576,7 +751,11 @@ public abstract class AnnotationUtils { * @since 4.2 */ public static boolean isInJavaLangAnnotationPackage(@Nullable String annotationType) { - return InternalAnnotationUtils.isInJavaLangAnnotationPackage(annotationType); + return MigrateMethod.from(() -> + InternalAnnotationUtils.isInJavaLangAnnotationPackage(annotationType) + ).to(() -> + JAVA_LANG_ANNOTATION_FILTER.matches(annotationType) + ); } /** @@ -592,7 +771,12 @@ public abstract class AnnotationUtils { * @see #getAnnotationAttributes(Annotation) */ public static void validateAnnotation(Annotation annotation) { - InternalAnnotationUtils.validateAnnotation(annotation); + MigrateMethod.fromCall(() -> + InternalAnnotationUtils.validateAnnotation(annotation) + ).to(() -> + AttributeMethods.forAnnotationType(annotation.annotationType()) + .validate(annotation) + ); } /** @@ -611,7 +795,7 @@ public abstract class AnnotationUtils { * @see #getAnnotationAttributes(AnnotatedElement, Annotation, boolean, boolean) */ public static Map getAnnotationAttributes(Annotation annotation) { - return InternalAnnotationUtils.getAnnotationAttributes(annotation); + return getAnnotationAttributes(null, annotation); } /** @@ -628,9 +812,10 @@ public abstract class AnnotationUtils { * corresponding attribute values as values (never {@code null}) * @see #getAnnotationAttributes(Annotation, boolean, boolean) */ - public static Map getAnnotationAttributes(Annotation annotation, boolean classValuesAsString) { - return InternalAnnotationUtils.getAnnotationAttributes(annotation, - classValuesAsString); + public static Map getAnnotationAttributes(Annotation annotation, + boolean classValuesAsString) { + + return getAnnotationAttributes(annotation, classValuesAsString, false); } /** @@ -649,10 +834,11 @@ public abstract class AnnotationUtils { * and corresponding attribute values as values (never {@code null}) * @since 3.1.1 */ - public static AnnotationAttributes getAnnotationAttributes(Annotation annotation, boolean classValuesAsString, - boolean nestedAnnotationsAsMap) { - return InternalAnnotationUtils.getAnnotationAttributes(annotation, - classValuesAsString, nestedAnnotationsAsMap); + public static AnnotationAttributes getAnnotationAttributes(Annotation annotation, + boolean classValuesAsString, boolean nestedAnnotationsAsMap) { + + return getAnnotationAttributes(null, annotation, classValuesAsString, + nestedAnnotationsAsMap); } /** @@ -668,9 +854,10 @@ public abstract class AnnotationUtils { * @since 4.2 * @see #getAnnotationAttributes(AnnotatedElement, Annotation, boolean, boolean) */ - public static AnnotationAttributes getAnnotationAttributes(@Nullable AnnotatedElement annotatedElement, Annotation annotation) { - return InternalAnnotationUtils.getAnnotationAttributes(annotatedElement, - annotation); + public static AnnotationAttributes getAnnotationAttributes( + @Nullable AnnotatedElement annotatedElement, Annotation annotation) { + + return getAnnotationAttributes(annotatedElement, annotation, false, false); } /** @@ -691,10 +878,31 @@ public abstract class AnnotationUtils { * and corresponding attribute values as values (never {@code null}) * @since 4.2 */ - public static AnnotationAttributes getAnnotationAttributes(@Nullable AnnotatedElement annotatedElement, - Annotation annotation, boolean classValuesAsString, boolean nestedAnnotationsAsMap) { - return InternalAnnotationUtils.getAnnotationAttributes(annotatedElement, - annotation, classValuesAsString, nestedAnnotationsAsMap); + public static AnnotationAttributes getAnnotationAttributes( + @Nullable AnnotatedElement annotatedElement, Annotation annotation, + boolean classValuesAsString, boolean nestedAnnotationsAsMap) { + + return MigrateMethod.from(() -> + InternalAnnotationUtils.getAnnotationAttributes(annotatedElement, + annotation, classValuesAsString, nestedAnnotationsAsMap) + ).to(() -> { + ClassLoader classLoader = annotation.annotationType().getClassLoader(); + MapValues[] mapValues = MapValues.of(classValuesAsString, nestedAnnotationsAsMap); + return MergedAnnotation.from(annotatedElement, annotation) + .withNonMergedAttributes() + .asMap(getAnnotationAttributesFactory(classLoader), mapValues); + }); + } + + @SuppressWarnings("unchecked") + private static Function, AnnotationAttributes> getAnnotationAttributesFactory( + ClassLoader classLoader) { + + return annotation -> { + Class type = (Class) ClassUtils.resolveClassName( + annotation.getType(), classLoader); + return new AnnotationAttributes(type, true); + }; } /** @@ -704,7 +912,52 @@ public abstract class AnnotationUtils { * @since 4.3.2 */ public static void registerDefaultValues(AnnotationAttributes attributes) { - InternalAnnotationUtils.registerDefaultValues(attributes); + AnnotationAttributes copy = new AnnotationAttributes(attributes); + MigrateMethod.fromCall(()-> + InternalAnnotationUtils.registerDefaultValues(copy) + ).withArgumentCheck(copy, attributes).to(()-> { + Class annotationType = attributes.annotationType(); + if (annotationType != null && Modifier.isPublic(annotationType.getModifiers())) { + getDefaultValues(annotationType).forEach(attributes::putIfAbsent); + } + }); + } + + private static Map getDefaultValues( + Class annotationType) { + + return defaultValuesCache.computeIfAbsent(annotationType, + AnnotationUtils::computeDefaultValues); + } + + private static Map computeDefaultValues( + Class annotationType) { + + AttributeMethods methods = AttributeMethods.forAnnotationType(annotationType); + if (!methods.hasDefaultValueMethod()) { + return Collections.emptyMap(); + } + Map result = new LinkedHashMap<>(methods.size()); + if (!methods.hasNestedAnnotation()) { + // Use simpler method if there are no nested annotations + for (int i = 0; i < methods.size(); i++) { + Method method = methods.get(i); + Object defaultValue = method.getDefaultValue(); + if(defaultValue != null) { + result.put(method.getName(), new DefaultValueHolder(defaultValue)); + } + } + } + else { + // If we have nested annotations, we need them as nested maps + AnnotationAttributes attributes = MergedAnnotation.from(annotationType).asMap( + getAnnotationAttributesFactory(annotationType.getClassLoader()), + MapValues.ANNOTATION_TO_MAP); + for (Map.Entry element : attributes.entrySet()) { + result.put(element.getKey(), new DefaultValueHolder(element.getValue())); + } + } + return result; } /** @@ -721,13 +974,91 @@ public abstract class AnnotationUtils { * compatibility with {@link org.springframework.core.type.AnnotationMetadata}) * or to preserve them as Class references * @since 4.3.2 - * @see #postProcessAnnotationAttributes(Object, AnnotationAttributes, boolean, boolean) * @see #getDefaultValue(Class, String) */ public static void postProcessAnnotationAttributes(@Nullable Object annotatedElement, AnnotationAttributes attributes, boolean classValuesAsString) { - InternalAnnotationUtils.postProcessAnnotationAttributes(annotatedElement, - attributes, classValuesAsString); + + AnnotationAttributes copy = new AnnotationAttributes(attributes); + MigrateMethod.fromCall(() -> + InternalAnnotationUtils.postProcessAnnotationAttributes(annotatedElement, copy, + classValuesAsString) + ).withArgumentCheck(copy, attributes).to(()-> { + if (attributes == null || attributes.annotationType() == null) { + return; + } + if (!attributes.validated) { + Class annotationType = attributes.annotationType(); + if (annotationType != null) { + AnnotationTypeMapping mapping = AnnotationTypeMappings + .forAnnotationType(annotationType).get(0); + for (int i = 0; i < mapping.getMirrorSets().size(); i++) { + MirrorSet mirrorSet = mapping.getMirrorSets().get(i); + int resolved = mirrorSet.resolve(attributes.displayName, attributes, + AnnotationUtils::getAttributeValueForMirrorResolution); + if (resolved != -1) { + Method attribute = mapping.getAttributes().get(resolved); + Object value = attributes.get(attribute.getName()); + for (int j = 0; j < mirrorSet.size(); j++) { + Method mirror = mirrorSet.get(j); + if (mirror != attribute) { + attributes.put(mirror.getName(), + adaptValue(annotatedElement, value, classValuesAsString)); + } + } + } + } + } + } + for (Map.Entry attributeEntry : attributes.entrySet()) { + String attributeName = attributeEntry.getKey(); + Object value = attributeEntry.getValue(); + if (value instanceof DefaultValueHolder) { + value = ((DefaultValueHolder) value).defaultValue; + attributes.put(attributeName, + adaptValue(annotatedElement, value, classValuesAsString)); + } + } + }); + } + + private static Object getAttributeValueForMirrorResolution(Method attribute, + Object attributes) { + + Object result = ((AnnotationAttributes) attributes).get(attribute.getName()); + return result instanceof DefaultValueHolder ? + ((DefaultValueHolder) result).defaultValue : + result; + } + + @Nullable + static Object adaptValue(@Nullable Object annotatedElement, @Nullable Object value, + boolean classValuesAsString) { + + if (classValuesAsString) { + if (value instanceof Class) { + return ((Class) value).getName(); + } + if (value instanceof Class[]) { + return Arrays.stream((Class[]) value).map(Class::getName).toArray( + String[]::new); + } + } + if (value instanceof Annotation) { + Annotation annotation = (Annotation) value; + return MergedAnnotation.from(annotatedElement, annotation).synthesize(); + } + if (value instanceof Annotation[]) { + Annotation[] annotations = (Annotation[]) value; + Annotation[] synthesized = (Annotation[]) Array.newInstance( + annotations.getClass().getComponentType(), annotations.length); + for (int i = 0; i < annotations.length; i++) { + synthesized[i] = MergedAnnotation.from(annotatedElement, + annotations[i]).synthesize(); + } + return synthesized; + } + return value; } /** @@ -741,7 +1072,7 @@ public abstract class AnnotationUtils { */ @Nullable public static Object getValue(Annotation annotation) { - return InternalAnnotationUtils.getValue(annotation); + return getValue(annotation, VALUE); } /** @@ -752,10 +1083,11 @@ public abstract class AnnotationUtils { * value cannot be retrieved due to an {@link AnnotationConfigurationException}, * in which case such an exception will be rethrown * @see #getValue(Annotation) - * @see #rethrowAnnotationConfigurationException(Throwable) */ @Nullable - public static Object getValue(@Nullable Annotation annotation, @Nullable String attributeName) { + public static Object getValue(@Nullable Annotation annotation, + @Nullable String attributeName) { + return InternalAnnotationUtils.getValue(annotation, attributeName); } @@ -768,7 +1100,7 @@ public abstract class AnnotationUtils { */ @Nullable public static Object getDefaultValue(Annotation annotation) { - return InternalAnnotationUtils.getDefaultValue(annotation); + return getDefaultValue(annotation, VALUE); } /** @@ -779,8 +1111,12 @@ public abstract class AnnotationUtils { * @see #getDefaultValue(Class, String) */ @Nullable - public static Object getDefaultValue(@Nullable Annotation annotation, @Nullable String attributeName) { - return InternalAnnotationUtils.getDefaultValue(annotation, attributeName); + public static Object getDefaultValue(@Nullable Annotation annotation, + @Nullable String attributeName) { + + return annotation != null ? + getDefaultValue(annotation.annotationType(), attributeName) : + null; } /** @@ -792,7 +1128,7 @@ public abstract class AnnotationUtils { */ @Nullable public static Object getDefaultValue(Class annotationType) { - return InternalAnnotationUtils.getDefaultValue(annotationType); + return getDefaultValue(annotationType, VALUE); } /** @@ -805,8 +1141,18 @@ public abstract class AnnotationUtils { */ @Nullable public static Object getDefaultValue( - @Nullable Class annotationType, @Nullable String attributeName) { - return InternalAnnotationUtils.getDefaultValue(annotationType, attributeName); + @Nullable Class annotationType, + @Nullable String attributeName) { + + return MigrateMethod.from(() -> + InternalAnnotationUtils.getDefaultValue(annotationType, attributeName) + ).toNullable(() -> { + if (annotationType == null || !StringUtils.hasText(attributeName)) { + return null; + } + return MergedAnnotation.from(annotationType) + .getDefaultValue(attributeName).orElse(null); + }); } /** @@ -828,7 +1174,8 @@ public abstract class AnnotationUtils { */ public static A synthesizeAnnotation( A annotation, @Nullable AnnotatedElement annotatedElement) { - return InternalAnnotationUtils.synthesizeAnnotation(annotation, annotatedElement); + + return synthesizeAnnotation(annotation, (Object) annotatedElement); } /** @@ -860,10 +1207,22 @@ public abstract class AnnotationUtils { * @see #getAnnotationAttributes(AnnotatedElement, Annotation) * @see #getAnnotationAttributes(AnnotatedElement, Annotation, boolean, boolean) */ - public static A synthesizeAnnotation(Map attributes, - Class annotationType, @Nullable AnnotatedElement annotatedElement) { - return InternalAnnotationUtils.synthesizeAnnotation(attributes, annotationType, - annotatedElement); + public static A synthesizeAnnotation( + Map attributes, Class annotationType, + @Nullable AnnotatedElement annotatedElement) { + + return MigrateMethod.from(()-> + InternalAnnotationUtils.synthesizeAnnotation(attributes, annotationType, + annotatedElement) + ).to(()-> { + try { + return MergedAnnotation.from(annotatedElement, annotationType, attributes) + .synthesize(); + } + catch (NoSuchElementException | IllegalStateException ex) { + throw new IllegalArgumentException(ex); + } + }); } /** @@ -882,14 +1241,15 @@ public abstract class AnnotationUtils { * @see #synthesizeAnnotation(Annotation, AnnotatedElement) */ public static A synthesizeAnnotation(Class annotationType) { - return InternalAnnotationUtils.synthesizeAnnotation(annotationType); + return synthesizeAnnotation(Collections.emptyMap(), annotationType, null); } /** * Synthesize an array of annotations from the supplied array * of {@code annotations} by creating a new array of the same size and - * type and populating it with {@linkplain #synthesizeAnnotation(Annotation) - * synthesized} versions of the annotations from the input array. + * type and populating it with {@linkplain #synthesizeAnnotation(Annotation, + * AnnotatedElement) synthesized} versions of the annotations from the input + * array. * @param annotations the array of annotations to synthesize * @param annotatedElement the element that is annotated with the supplied * array of annotations; may be {@code null} if unknown @@ -904,8 +1264,30 @@ public abstract class AnnotationUtils { static Annotation[] synthesizeAnnotationArray(Annotation[] annotations, @Nullable Object annotatedElement) { - return InternalAnnotationUtils.synthesizeAnnotationArray(annotations, - annotatedElement); + if (AnnotationsScanner.hasPlainJavaAnnotationsOnly(annotatedElement)) { + return annotations; + } + Annotation[] synthesized = (Annotation[]) Array.newInstance( + annotations.getClass().getComponentType(), annotations.length); + for (int i = 0; i < annotations.length; i++) { + synthesized[i] = synthesizeAnnotation(annotations[i], annotatedElement); + } + return synthesized; + } + + private static A synthesizeAnnotation(A annotation, + @Nullable Object annotatedElement) { + + return MigrateMethod.from(()-> + InternalAnnotationUtils.synthesizeAnnotation(annotation, annotatedElement) + ).withSkippedOriginalExceptionCheck().to(() -> { + if (annotation instanceof SynthesizedAnnotation || + AnnotationsScanner.hasPlainJavaAnnotationsOnly(annotatedElement) || + AnnotationFilter.PLAIN.matches(annotation)) { + return annotation; + } + return MergedAnnotation.from(annotatedElement, annotation).synthesize(); + }); } /** @@ -916,4 +1298,10 @@ public abstract class AnnotationUtils { InternalAnnotationUtils.clearCache(); } + private static boolean isSingleLevelPresent( + MergedAnnotation mergedAnnotation) { + int depth = mergedAnnotation.getDepth(); + return depth == 0 || depth == 1; + } + } diff --git a/spring-core/src/main/java/org/springframework/core/annotation/MigrateMethod.java b/spring-core/src/main/java/org/springframework/core/annotation/MigrateMethod.java new file mode 100644 index 00000000000..a7823c337d9 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/core/annotation/MigrateMethod.java @@ -0,0 +1,293 @@ +/* + * Copyright 2002-2019 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.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.function.BooleanSupplier; +import java.util.function.Supplier; + +import org.springframework.core.annotation.InternalAnnotationUtils.DefaultValueHolder; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; + +/** + * Internal class used to help migrate annotation util methods to a new implementation. + * + * @author Phillip Webb + * @since 5.2 + */ +final class MigrateMethod { + + private MigrateMethod() { + } + + /** + * Create a new {@link ReplacementMethod} builder for the deprecated method. + * @param originalMethod the original method being migrated + * @return a replacement builder. + */ + static ReplacementMethod from(Supplier originalMethod) { + return new ReplacementMethod<>(originalMethod); + } + + /** + * Create a new {@link ReplacementVoidMethod} for the deprecated method. + * @param originalMethod the original method being migrated + * @return a replacement builder. + */ + static ReplacementVoidMethod fromCall(Runnable originalMethod) { + return new ReplacementVoidMethod(originalMethod); + } + + private static boolean isEquivalent(@Nullable Object result, @Nullable Object expectedResult) { + if (ObjectUtils.nullSafeEquals(result, expectedResult)) { + return true; + } + if (result == null && String.valueOf(expectedResult).startsWith( + "@org.springframework.lang.")) { + // Original methods don't filter spring annotation but we do + return true; + } + if (result == null || expectedResult == null) { + return false; + } + if (result instanceof DefaultValueHolder && expectedResult instanceof DefaultValueHolder) { + return isEquivalent(((DefaultValueHolder) result).defaultValue, + ((DefaultValueHolder) expectedResult).defaultValue); + } + if (result instanceof Map && expectedResult instanceof Map) { + return isEquivalentMap((Map) result, (Map) expectedResult); + } + if (result instanceof List && expectedResult instanceof List) { + return isEquivalentList((List) result, (List) expectedResult); + } + if (result instanceof Object[] && expectedResult instanceof Object[]) { + return isEquivalentArray((Object[]) result, (Object[]) expectedResult); + } + if (result instanceof Object[]) { + if (isEquivalentArray((Object[]) result, new Object[] { expectedResult })) { + return true; + } + } + if (!(result instanceof Object[]) && expectedResult instanceof Object[]) { + if (isEquivalentArray(new Object[] { result }, (Object[]) expectedResult)) { + return true; + } + } + return false; + } + + private static boolean isEquivalentMap(Map result, Map expectedResult) { + if (result.size() != expectedResult.size()) { + return false; + } + for (Map.Entry entry : result.entrySet()) { + if (!expectedResult.containsKey(entry.getKey())) { + return false; + } + if (!isEquivalent(entry.getValue(), expectedResult.get(entry.getKey()))) { + return false; + } + } + return true; + } + + private static boolean isEquivalentList(List result, List expectedResult) { + if (result.size() != expectedResult.size()) { + return false; + } + for (int i = 0; i < result.size(); i++) { + if (!isEquivalent(result.get(i), expectedResult.get(i))) { + return false; + } + } + return true; + } + + private static boolean isEquivalentArray(Object[] result, Object[] expectedResult) { + if (result.length != expectedResult.length) { + return false; + } + for (int i = 0; i < result.length; i++) { + if (!isEquivalent(result[i], expectedResult[i])) { + return false; + } + } + return true; + } + + /** + * Builder to complete replacement details for a deprecated annotation method. + * @param the return type + */ + static class ReplacementMethod { + + private final Supplier originalMethod; + + @Nullable + private Supplier description; + + private boolean skipOriginalExceptionCheck; + + private BooleanSupplier skipEquivalentCheck = () -> false; + + ReplacementMethod(Supplier deprecatedMethod) { + this.originalMethod = deprecatedMethod; + } + + /** + * Add a description for the method. + * @param description a description supplier + * @return this instance + */ + public ReplacementMethod withDescription(Supplier description) { + this.description = description; + return this; + } + + public ReplacementMethod withSkippedOriginalExceptionCheck() { + this.skipOriginalExceptionCheck = true; + return this; + } + + public ReplacementMethod withSkippedEquivalentCheck(BooleanSupplier supplier) { + this.skipEquivalentCheck = supplier; + return this; + } + + /** + * Provide the replacement method that should be used instead of the deprecated + * one. The replacement method is called, and when appropriate the result is + * checked against the deprecated method. + * @param replacementMethod the replacement method + * @return the result of the replacement method + */ + public T to(Supplier replacementMethod) { + T result = toNullable(replacementMethod); + if (result == null) { + throw new IllegalStateException("Unexpected null result"); + } + return result; + } + + /** + * Provide the replacement method that should be used instead of the deprecated + * one. The replacement method is called, and when appropriate the result is + * checked against the deprecated method. + * @param replacementMethod the replacement method + * @return the result of the replacement method + */ + @Nullable + public T toNullable(Supplier replacementMethod) { + T result = tryInvoke(replacementMethod); + T expectedResult = this.originalMethod.get(); + if (!isEquivalent(result, expectedResult)) { + if (this.skipEquivalentCheck.getAsBoolean()) { + return expectedResult; + } + String description = (this.description != null ? " [" + + this.description.get() + "]" : ""); + throw new IllegalStateException("Expected " + expectedResult + + " got " + result + description); + } + return result; + } + + private T tryInvoke(Supplier replacementMethod) { + try { + return replacementMethod.get(); + } + catch (RuntimeException expected) { + try { + T expectedResult = this.originalMethod.get(); + if (this.skipOriginalExceptionCheck) { + return expectedResult; + } + throw new Error("Expected exception not thrown", expected); + } + catch (RuntimeException actual) { + if (!expected.getClass().isInstance(actual)) { + throw new Error( + "Exception is not " + expected.getClass().getName(), + actual); + } + throw actual; + } + } + } + + } + + /** + * Builder to complete replacement details for a deprecated annotation method that + * returns void. + */ + static class ReplacementVoidMethod { + + private final Runnable originalMethod; + + private final List argumentChecks = new ArrayList<>(); + + public ReplacementVoidMethod(Runnable originalMethod) { + this.originalMethod = originalMethod; + } + + public ReplacementVoidMethod withArgumentCheck(Object originalArgument, + Object replacementArgument) { + this.argumentChecks.add( + new Object[] { originalArgument, replacementArgument }); + return this; + } + + public void to(Runnable replacementMethod) { + tryInvoke(this.originalMethod); + replacementMethod.run(); + for (Object[] arguments : this.argumentChecks) { + Object expectedArgument = arguments[0]; + Object actualArgument = arguments[1]; + Assert.state(isEquivalent(actualArgument, expectedArgument), + () -> "Expected argument mutation of " + expectedArgument + + " got " + actualArgument); + } + } + + private void tryInvoke(Runnable replacementMethod) { + try { + replacementMethod.run(); + } + catch (RuntimeException expected) { + try { + this.originalMethod.run(); + throw new Error("Expected exception not thrown", expected); + } + catch (RuntimeException actual) { + if (!expected.getClass().isInstance(actual)) { + throw new Error( + "Exception is not " + expected.getClass().getName(), + actual); + } + throw actual; + } + } + } + + } + +} diff --git a/spring-core/src/test/java/org/springframework/core/annotation/AnnotatedElementUtilsTests.java b/spring-core/src/test/java/org/springframework/core/annotation/AnnotatedElementUtilsTests.java index 9ec75fc32d9..f26a07bf0bd 100644 --- a/spring-core/src/test/java/org/springframework/core/annotation/AnnotatedElementUtilsTests.java +++ b/spring-core/src/test/java/org/springframework/core/annotation/AnnotatedElementUtilsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 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. @@ -459,7 +459,9 @@ public class AnnotatedElementUtilsTests { exception.expectMessage(either( containsString("values of [{duplicateDeclaration}] and [{requiredLocationsDeclaration}]")).or( containsString("values of [{requiredLocationsDeclaration}] and [{duplicateDeclaration}]"))); - exception.expectMessage(containsString("but only one is permitted")); + exception.expectMessage(either( + containsString("but only one is permitted")).or( + containsString("Different @AliasFor mirror values for annotation"))); getMergedAnnotationAttributes(element, ContextConfig.class); } diff --git a/spring-core/src/test/java/org/springframework/core/annotation/AnnotationUtilsTests.java b/spring-core/src/test/java/org/springframework/core/annotation/AnnotationUtilsTests.java index 6c50a35d443..0df3e210604 100644 --- a/spring-core/src/test/java/org/springframework/core/annotation/AnnotationUtilsTests.java +++ b/spring-core/src/test/java/org/springframework/core/annotation/AnnotationUtilsTests.java @@ -562,7 +562,9 @@ public class AnnotationUtilsTests { exception.expect(AnnotationConfigurationException.class); exception.expectMessage(startsWith("Attribute 'value' in")); exception.expectMessage(containsString(BrokenContextConfig.class.getName())); - exception.expectMessage(containsString("@AliasFor [location]")); + exception.expectMessage(either( + containsString("@AliasFor [location]")).or( + containsString("@AliasFor 'location'"))); getRepeatableAnnotations(BrokenConfigHierarchyTestCase.class, BrokenContextConfig.class, BrokenHierarchy.class); } @@ -897,7 +899,9 @@ public class AnnotationUtilsTests { exception.expect(AnnotationConfigurationException.class); exception.expectMessage(startsWith("In @AliasFor declared on attribute 'foo' in annotation")); exception.expectMessage(containsString(AliasForWithDuplicateAttributeDeclaration.class.getName())); - exception.expectMessage(containsString("attribute 'attribute' and its alias 'value' are present with values of [baz] and [bar]")); + exception.expectMessage(anyOf( + containsString("attribute 'attribute' and its alias 'value' are present with values of [baz] and [bar]"), + containsString("attribute 'attribute' and its alias 'value' are present with values of 'bar' and 'baz'"))); InternalAnnotationUtils.synthesizeAnnotation(annotation); } @@ -905,9 +909,14 @@ public class AnnotationUtilsTests { public void synthesizeAnnotationWithAttributeAliasForNonexistentAttribute() throws Exception { AliasForNonexistentAttribute annotation = AliasForNonexistentAttributeClass.class.getAnnotation(AliasForNonexistentAttribute.class); exception.expect(AnnotationConfigurationException.class); - exception.expectMessage(startsWith("Attribute 'foo' in")); - exception.expectMessage(containsString(AliasForNonexistentAttribute.class.getName())); - exception.expectMessage(containsString("is declared as an @AliasFor nonexistent attribute 'bar'")); + exception.expectMessage(either(allOf( + startsWith("Attribute 'foo' in"), + containsString(AliasForNonexistentAttribute.class.getName()), + containsString("is declared as an @AliasFor nonexistent attribute 'bar'")) + ).or( + containsString("@AliasFor declaration on attribute 'foo' in annotation [" + + AliasForNonexistentAttribute.class.getName() + + "] declares an alias for 'bar' which is not present"))); InternalAnnotationUtils.synthesizeAnnotation(annotation); } @@ -916,9 +925,9 @@ public class AnnotationUtilsTests { AliasForWithoutMirroredAliasFor annotation = AliasForWithoutMirroredAliasForClass.class.getAnnotation(AliasForWithoutMirroredAliasFor.class); exception.expect(AnnotationConfigurationException.class); - exception.expectMessage(startsWith("Attribute 'bar' in")); - exception.expectMessage(containsString(AliasForWithoutMirroredAliasFor.class.getName())); - exception.expectMessage(containsString("@AliasFor [foo]")); + exception.expectMessage(allOf(startsWith("Attribute 'bar' in"), + containsString(AliasForWithoutMirroredAliasFor.class.getName()), + either(containsString("@AliasFor [foo]")).or(containsString("@AliasFor 'foo'")))); InternalAnnotationUtils.synthesizeAnnotation(annotation); } @@ -929,8 +938,10 @@ public class AnnotationUtilsTests { exception.expect(AnnotationConfigurationException.class); exception.expectMessage(startsWith("Attribute 'bar' in")); exception.expectMessage(containsString(AliasForWithMirroredAliasForWrongAttribute.class.getName())); - exception.expectMessage(either(containsString("must be declared as an @AliasFor [foo], not [quux]")). - or(containsString("is declared as an @AliasFor nonexistent attribute 'quux'"))); + exception.expectMessage(anyOf( + containsString("must be declared as an @AliasFor [foo], not [quux]"), + containsString("is declared as an @AliasFor nonexistent attribute 'quux'"), + containsString("must be declared as an @AliasFor 'foo', not attribute 'quux'"))); InternalAnnotationUtils.synthesizeAnnotation(annotation); } @@ -980,7 +991,7 @@ public class AnnotationUtilsTests { exception.expect(AnnotationConfigurationException.class); exception.expectMessage(startsWith("@AliasFor declaration on attribute 'xmlConfigFile' in annotation")); exception.expectMessage(containsString(AliasedComposedContextConfigNotMetaPresent.class.getName())); - exception.expectMessage(containsString("declares an alias for attribute 'location' in meta-annotation")); + exception.expectMessage(containsString("declares an alias for attribute 'location'")); exception.expectMessage(containsString(ContextConfig.class.getName())); exception.expectMessage(containsString("not meta-present")); InternalAnnotationUtils.synthesizeAnnotation(annotation); @@ -1133,21 +1144,16 @@ public class AnnotationUtilsTests { ImplicitAliasesWithDuplicateValuesContextConfig config = clazz.getAnnotation(annotationType); assertNotNull(config); - ImplicitAliasesWithDuplicateValuesContextConfig synthesizedConfig = synthesizeAnnotation(config, clazz); - assertNotNull(synthesizedConfig); - exception.expect(AnnotationConfigurationException.class); - exception.expectMessage(startsWith("In annotation")); + exception.expectMessage(either(startsWith("In annotation")).or(startsWith("Different @AliasFor mirror values"))); exception.expectMessage(containsString(annotationType.getName())); exception.expectMessage(containsString("declared on class")); exception.expectMessage(containsString(clazz.getName())); - exception.expectMessage(containsString("and synthesized from")); exception.expectMessage(either(containsString("attribute 'location1' and its alias 'location2'")).or( containsString("attribute 'location2' and its alias 'location1'"))); - exception.expectMessage(either(containsString("are present with values of [1] and [2]")).or( - containsString("are present with values of [2] and [1]"))); - - synthesizedConfig.location1(); + exception.expectMessage(either(containsString("with values of [1] and [2]")).or( + containsString("with values of [2] and [1]"))); + synthesizeAnnotation(config, clazz).location1(); } @Test @@ -1241,8 +1247,8 @@ public class AnnotationUtilsTests { @Test public void synthesizeAnnotationWithAttributeAliasesWithDifferentValues() throws Exception { - ContextConfig contextConfig = InternalAnnotationUtils.synthesizeAnnotation(ContextConfigMismatch.class.getAnnotation(ContextConfig.class)); exception.expect(AnnotationConfigurationException.class); + ContextConfig contextConfig = InternalAnnotationUtils.synthesizeAnnotation(ContextConfigMismatch.class.getAnnotation(ContextConfig.class)); getValue(contextConfig); } @@ -1306,23 +1312,29 @@ public class AnnotationUtilsTests { private void assertMissingTextAttribute(Map attributes) { exception.expect(IllegalArgumentException.class); - exception.expectMessage(startsWith("Attributes map")); - exception.expectMessage(containsString("returned null for required attribute 'text'")); - exception.expectMessage(containsString("defined by annotation type [" + AnnotationWithoutDefaults.class.getName() + "]")); + exception.expectMessage(either(allOf(startsWith("Attributes map"), + containsString("returned null for required attribute 'text'"), + containsString("defined by annotation type [" + + AnnotationWithoutDefaults.class.getName() + "]"))).or( + containsString( + "No value found for attribute named 'text' in merged annotation"))); synthesizeAnnotation(attributes, AnnotationWithoutDefaults.class, null); } @Test public void synthesizeAnnotationFromMapWithAttributeOfIncorrectType() throws Exception { Map map = Collections.singletonMap(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() + "]")); - + exception.expectMessage(either(allOf(startsWith("Attributes map"), + containsString("returned a value of type [java.lang.Long]"), + containsString("for attribute 'value'"), + containsString("but a value of type [java.lang.String] is required"), + containsString("as defined by annotation type [" + + Component.class.getName() + "]"))).or( + containsString("Attribute 'value' in annotation " + + Component.class.getName() + + " should be of type " + + "java.lang.String but a java.lang.Long value was returned"))); synthesizeAnnotation(map, Component.class, null); } diff --git a/spring-test/src/test/java/org/springframework/test/context/jdbc/SqlScriptsTestExecutionListenerTests.java b/spring-test/src/test/java/org/springframework/test/context/jdbc/SqlScriptsTestExecutionListenerTests.java index 306223f8f1c..5089978d6a5 100644 --- a/spring-test/src/test/java/org/springframework/test/context/jdbc/SqlScriptsTestExecutionListenerTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/jdbc/SqlScriptsTestExecutionListenerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2019 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. @@ -79,7 +79,8 @@ 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 is permitted")); + exception.expectMessage(either(containsString("but only one is permitted")).or( + containsString("Different @AliasFor mirror values"))); listener.beforeTestMethod(testContext); } diff --git a/spring-test/src/test/java/org/springframework/test/context/support/ContextLoaderUtilsConfigurationAttributesTests.java b/spring-test/src/test/java/org/springframework/test/context/support/ContextLoaderUtilsConfigurationAttributesTests.java index d109be06ceb..3ed5b4a9b66 100644 --- a/spring-test/src/test/java/org/springframework/test/context/support/ContextLoaderUtilsConfigurationAttributesTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/support/ContextLoaderUtilsConfigurationAttributesTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2019 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. @@ -73,7 +73,9 @@ public class ContextLoaderUtilsConfigurationAttributesTests extends AbstractCont exception.expectMessage(either( containsString("values of [{x}] and [{y}]")).or( containsString("values of [{y}] and [{x}]"))); - exception.expectMessage(containsString("but only one is permitted")); + exception.expectMessage(either( + containsString("Different @AliasFor mirror values")).or( + containsString("but only one is permitted"))); resolveContextConfigurationAttributes(ConflictingLocations.class); }