Restore retrieval of plain annotations through direct presence checks

Includes deprecation of several AnnotationUtils methods and nullability refinements for passed-in function arguments at the MergedAnnotation API level... also, MergedAnnotation.getType() returns a Class now.

Closes gh-22663
Closes gh-22685
This commit is contained in:
Juergen Hoeller 2019-03-26 16:13:41 +01:00
parent f7a4850675
commit 210b178922
16 changed files with 430 additions and 552 deletions

View File

@ -521,9 +521,10 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean
* @param ann the Autowired annotation * @param ann the Autowired annotation
* @return whether the annotation indicates that a dependency is required * @return whether the annotation indicates that a dependency is required
*/ */
@SuppressWarnings("deprecation")
protected boolean determineRequiredStatus(MergedAnnotation<?> ann) { protected boolean determineRequiredStatus(MergedAnnotation<?> ann) {
return determineRequiredStatus( return determineRequiredStatus((AnnotationAttributes)
ann.asMap(mergedAnnotation -> new AnnotationAttributes())); ann.asMap(mergedAnnotation -> new AnnotationAttributes(mergedAnnotation.getType())));
} }
/** /**
@ -533,7 +534,7 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean
* or method when no beans are found. * or method when no beans are found.
* @param ann the Autowired annotation * @param ann the Autowired annotation
* @return whether the annotation indicates that a dependency is required * @return whether the annotation indicates that a dependency is required
* @deprecated since 5.2 in favor of {@link #determineRequiredStatus(MergedAnnotation)} * @deprecated since 5.2, in favor of {@link #determineRequiredStatus(MergedAnnotation)}
*/ */
@Deprecated @Deprecated
protected boolean determineRequiredStatus(AnnotationAttributes ann) { protected boolean determineRequiredStatus(AnnotationAttributes ann) {

View File

@ -18,7 +18,6 @@ package org.springframework.core.annotation;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.lang.reflect.Array; import java.lang.reflect.Array;
import java.util.Map;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;
import java.util.Optional; import java.util.Optional;
import java.util.function.Predicate; import java.util.function.Predicate;
@ -30,6 +29,7 @@ import org.springframework.util.Assert;
* Abstract base class for {@link MergedAnnotation} implementations. * Abstract base class for {@link MergedAnnotation} implementations.
* *
* @author Phillip Webb * @author Phillip Webb
* @author Juergen Hoeller
* @since 5.2 * @since 5.2
* @param <A> the annotation type * @param <A> the annotation type
*/ */
@ -167,19 +167,10 @@ abstract class AbstractMergedAnnotation<A extends Annotation> implements MergedA
} }
@Override @Override
public Map<String, Object> asMap(MapValues... options) { public Optional<A> synthesize(Predicate<? super MergedAnnotation<A>> condition)
return asMap(null, options);
}
@Override
public Optional<A> synthesize(
@Nullable Predicate<? super MergedAnnotation<A>> condition)
throws NoSuchElementException { throws NoSuchElementException {
if (condition == null || condition.test(this)) { return (condition.test(this) ? Optional.of(synthesize()) : Optional.empty());
return Optional.of(synthesize());
}
return Optional.empty();
} }
@Override @Override
@ -199,7 +190,7 @@ abstract class AbstractMergedAnnotation<A extends Annotation> implements MergedA
T value = getAttributeValue(attributeName, type); T value = getAttributeValue(attributeName, type);
if (value == null) { if (value == null) {
throw new NoSuchElementException("No attribute named '" + attributeName + throw new NoSuchElementException("No attribute named '" + attributeName +
"' present in merged annotation " + getType()); "' present in merged annotation " + getType().getName());
} }
return value; return value;
} }

View File

@ -21,7 +21,6 @@ import java.lang.reflect.AnnotatedElement;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -137,9 +136,7 @@ public abstract class AnnotatedElementUtils {
* @see #getMetaAnnotationTypes(AnnotatedElement, Class) * @see #getMetaAnnotationTypes(AnnotatedElement, Class)
* @see #hasMetaAnnotationTypes * @see #hasMetaAnnotationTypes
*/ */
public static Set<String> getMetaAnnotationTypes(AnnotatedElement element, public static Set<String> getMetaAnnotationTypes(AnnotatedElement element, String annotationName) {
String annotationName) {
for (Annotation annotation : element.getAnnotations()) { for (Annotation annotation : element.getAnnotations()) {
if (annotation.annotationType().getName().equals(annotationName)) { if (annotation.annotationType().getName().equals(annotationName)) {
return getMetaAnnotationTypes(element, annotation); return getMetaAnnotationTypes(element, annotation);
@ -148,14 +145,12 @@ public abstract class AnnotatedElementUtils {
return Collections.emptySet(); return Collections.emptySet();
} }
private static Set<String> getMetaAnnotationTypes(AnnotatedElement element, private static Set<String> getMetaAnnotationTypes(AnnotatedElement element, @Nullable Annotation annotation) {
@Nullable Annotation annotation) {
if (annotation == null) { if (annotation == null) {
return Collections.emptySet(); return Collections.emptySet();
} }
return getAnnotations(annotation.annotationType()).stream() return getAnnotations(annotation.annotationType()).stream()
.map(MergedAnnotation::getType) .map(mergedAnnotation -> mergedAnnotation.getType().getName())
.collect(Collectors.toCollection(LinkedHashSet::new)); .collect(Collectors.toCollection(LinkedHashSet::new));
} }
@ -171,11 +166,8 @@ public abstract class AnnotatedElementUtils {
* @since 4.2.3 * @since 4.2.3
* @see #getMetaAnnotationTypes * @see #getMetaAnnotationTypes
*/ */
public static boolean hasMetaAnnotationTypes(AnnotatedElement element, public static boolean hasMetaAnnotationTypes(AnnotatedElement element, Class<? extends Annotation> annotationType) {
Class<? extends Annotation> annotationType) { return getAnnotations(element).stream(annotationType).anyMatch(MergedAnnotation::isMetaPresent);
return getAnnotations(element).stream(annotationType)
.anyMatch(MergedAnnotation::isMetaPresent);
} }
/** /**
@ -190,11 +182,8 @@ public abstract class AnnotatedElementUtils {
* @return {@code true} if a matching meta-annotation is present * @return {@code true} if a matching meta-annotation is present
* @see #getMetaAnnotationTypes * @see #getMetaAnnotationTypes
*/ */
public static boolean hasMetaAnnotationTypes(AnnotatedElement element, public static boolean hasMetaAnnotationTypes(AnnotatedElement element, String annotationName) {
String annotationName) { return getAnnotations(element).stream(annotationName).anyMatch(MergedAnnotation::isMetaPresent);
return getAnnotations(element).stream(annotationName)
.anyMatch(MergedAnnotation::isMetaPresent);
} }
/** /**
@ -211,9 +200,16 @@ public abstract class AnnotatedElementUtils {
* @since 4.2.3 * @since 4.2.3
* @see #hasAnnotation(AnnotatedElement, Class) * @see #hasAnnotation(AnnotatedElement, Class)
*/ */
public static boolean isAnnotated(AnnotatedElement element, public static boolean isAnnotated(AnnotatedElement element,Class<? extends Annotation> annotationType) {
Class<? extends Annotation> annotationType) { // Shortcut: directly present on the element, with no processing needed?
if (AnnotationFilter.PLAIN.matches(annotationType)) {
return element.isAnnotationPresent(annotationType);
}
// Shortcut: no searchable annotations to be found on plain Java classes and core Spring types...
if (AnnotationsScanner.hasPlainJavaAnnotationsOnly(element)) {
return false;
}
// Exhaustive retrieval of merged annotations...
return getAnnotations(element).isPresent(annotationType); return getAnnotations(element).isPresent(annotationType);
} }
@ -278,8 +274,8 @@ public abstract class AnnotatedElementUtils {
* @see #getAllAnnotationAttributes(AnnotatedElement, String) * @see #getAllAnnotationAttributes(AnnotatedElement, String)
*/ */
@Nullable @Nullable
public static AnnotationAttributes getMergedAnnotationAttributes( public static AnnotationAttributes getMergedAnnotationAttributes(AnnotatedElement element,
AnnotatedElement element, String annotationName) { String annotationName) {
return getMergedAnnotationAttributes(element, annotationName, false, false); return getMergedAnnotationAttributes(element, annotationName, false, false);
} }
@ -311,9 +307,8 @@ public abstract class AnnotatedElementUtils {
* @see #getAllAnnotationAttributes(AnnotatedElement, String, boolean, boolean) * @see #getAllAnnotationAttributes(AnnotatedElement, String, boolean, boolean)
*/ */
@Nullable @Nullable
public static AnnotationAttributes getMergedAnnotationAttributes( public static AnnotationAttributes getMergedAnnotationAttributes(AnnotatedElement element,
AnnotatedElement element, String annotationName, boolean classValuesAsString, String annotationName, boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
boolean nestedAnnotationsAsMap) {
MergedAnnotation<?> mergedAnnotation = getAnnotations(element) MergedAnnotation<?> mergedAnnotation = getAnnotations(element)
.get(annotationName, null, MergedAnnotationSelectors.firstDirectlyDeclared()); .get(annotationName, null, MergedAnnotationSelectors.firstDirectlyDeclared());
@ -328,30 +323,23 @@ public abstract class AnnotatedElementUtils {
* the result back into an annotation of the specified {@code annotationType}. * the result back into an annotation of the specified {@code annotationType}.
* <p>{@link AliasFor @AliasFor} semantics are fully supported, both * <p>{@link AliasFor @AliasFor} semantics are fully supported, both
* within a single annotation and within the annotation hierarchy. * within a single annotation and within the annotation hierarchy.
* <p>This method delegates to {@link #getMergedAnnotationAttributes(AnnotatedElement, Class)}
* and {@link AnnotationUtils#synthesizeAnnotation(Map, Class, AnnotatedElement)}.
* @param element the annotated element * @param element the annotated element
* @param annotationType the annotation type to find * @param annotationType the annotation type to find
* @return the merged, synthesized {@code Annotation}, or {@code null} if not found * @return the merged, synthesized {@code Annotation}, or {@code null} if not found
* @since 4.2 * @since 4.2
* @see #getMergedAnnotationAttributes(AnnotatedElement, Class)
* @see #findMergedAnnotation(AnnotatedElement, Class) * @see #findMergedAnnotation(AnnotatedElement, Class)
* @see AnnotationUtils#synthesizeAnnotation(Map, Class, AnnotatedElement)
*/ */
@Nullable @Nullable
public static <A extends Annotation> A getMergedAnnotation(AnnotatedElement element, public static <A extends Annotation> A getMergedAnnotation(AnnotatedElement element, Class<A> annotationType) {
Class<A> annotationType) { // Shortcut: directly present on the element, with no merging needed?
if (AnnotationFilter.PLAIN.matches(annotationType)) {
if (AnnotationsScanner.hasPlainJavaAnnotationsOnly(element)) { return element.getDeclaredAnnotation(annotationType);
return null; }
} // Shortcut: no searchable annotations to be found on plain Java classes and core Spring types...
A annotation = AnnotationsScanner.getDeclaredAnnotation(element, annotationType);
if (annotation != null) {
return AnnotationUtils.synthesizeAnnotation(annotation, element);
}
if (AnnotationsScanner.hasPlainJavaAnnotationsOnly(element)) { if (AnnotationsScanner.hasPlainJavaAnnotationsOnly(element)) {
return null; return null;
} }
// Exhaustive retrieval of merged annotations...
return getAnnotations(element) return getAnnotations(element)
.get(annotationType, null, MergedAnnotationSelectors.firstDirectlyDeclared()) .get(annotationType, null, MergedAnnotationSelectors.firstDirectlyDeclared())
.synthesize(MergedAnnotation::isPresent).orElse(null); .synthesize(MergedAnnotation::isPresent).orElse(null);
@ -515,9 +503,8 @@ public abstract class AnnotatedElementUtils {
* attributes from all annotations found, or {@code null} if not found * attributes from all annotations found, or {@code null} if not found
*/ */
@Nullable @Nullable
public static MultiValueMap<String, Object> getAllAnnotationAttributes( public static MultiValueMap<String, Object> getAllAnnotationAttributes(AnnotatedElement element,
AnnotatedElement element, String annotationName, String annotationName, final boolean classValuesAsString, final boolean nestedAnnotationsAsMap) {
final boolean classValuesAsString, final boolean nestedAnnotationsAsMap) {
MapValues[] mapValues = MapValues.of(classValuesAsString, nestedAnnotationsAsMap); MapValues[] mapValues = MapValues.of(classValuesAsString, nestedAnnotationsAsMap);
return getAnnotations(element).stream(annotationName) return getAnnotations(element).stream(annotationName)
@ -540,16 +527,16 @@ public abstract class AnnotatedElementUtils {
* @since 4.3 * @since 4.3
* @see #isAnnotated(AnnotatedElement, Class) * @see #isAnnotated(AnnotatedElement, Class)
*/ */
public static boolean hasAnnotation(AnnotatedElement element, public static boolean hasAnnotation(AnnotatedElement element, Class<? extends Annotation> annotationType) {
Class<? extends Annotation> annotationType) {
// Shortcut: directly present on the element, with no processing needed? // Shortcut: directly present on the element, with no processing needed?
if (AnnotationFilter.PLAIN.matches(annotationType)) {
return element.isAnnotationPresent(annotationType);
}
// Shortcut: no searchable annotations to be found on plain Java classes and core Spring types...
if (AnnotationsScanner.hasPlainJavaAnnotationsOnly(element)) { if (AnnotationsScanner.hasPlainJavaAnnotationsOnly(element)) {
return false; return false;
} }
if (AnnotationsScanner.getDeclaredAnnotation(element, annotationType) != null) { // Exhaustive retrieval of merged annotations...
return true;
}
return findAnnotations(element).isPresent(annotationType); return findAnnotations(element).isPresent(annotationType);
} }
@ -581,9 +568,8 @@ public abstract class AnnotatedElementUtils {
* @see #getMergedAnnotationAttributes(AnnotatedElement, String, boolean, boolean) * @see #getMergedAnnotationAttributes(AnnotatedElement, String, boolean, boolean)
*/ */
@Nullable @Nullable
public static AnnotationAttributes findMergedAnnotationAttributes( public static AnnotationAttributes findMergedAnnotationAttributes(AnnotatedElement element,
AnnotatedElement element, Class<? extends Annotation> annotationType, Class<? extends Annotation> annotationType, boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
MergedAnnotation<?> mergedAnnotation = findAnnotations(element) MergedAnnotation<?> mergedAnnotation = findAnnotations(element)
.get(annotationType, null, MergedAnnotationSelectors.firstDirectlyDeclared()); .get(annotationType, null, MergedAnnotationSelectors.firstDirectlyDeclared());
@ -618,9 +604,8 @@ public abstract class AnnotatedElementUtils {
* @see #getMergedAnnotationAttributes(AnnotatedElement, String, boolean, boolean) * @see #getMergedAnnotationAttributes(AnnotatedElement, String, boolean, boolean)
*/ */
@Nullable @Nullable
public static AnnotationAttributes findMergedAnnotationAttributes( public static AnnotationAttributes findMergedAnnotationAttributes(AnnotatedElement element,
AnnotatedElement element, String annotationName, boolean classValuesAsString, String annotationName, boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
boolean nestedAnnotationsAsMap) {
MergedAnnotation<?> mergedAnnotation = findAnnotations(element) MergedAnnotation<?> mergedAnnotation = findAnnotations(element)
.get(annotationName, null, MergedAnnotationSelectors.firstDirectlyDeclared()); .get(annotationName, null, MergedAnnotationSelectors.firstDirectlyDeclared());
@ -646,16 +631,16 @@ public abstract class AnnotatedElementUtils {
* @see #getMergedAnnotationAttributes(AnnotatedElement, Class) * @see #getMergedAnnotationAttributes(AnnotatedElement, Class)
*/ */
@Nullable @Nullable
public static <A extends Annotation> A findMergedAnnotation(AnnotatedElement element, public static <A extends Annotation> A findMergedAnnotation(AnnotatedElement element, Class<A> annotationType) {
Class<A> annotationType) { // Shortcut: directly present on the element, with no merging needed?
if (AnnotationFilter.PLAIN.matches(annotationType)) {
A annotation = AnnotationsScanner.getDeclaredAnnotation(element, annotationType); return element.getDeclaredAnnotation(annotationType);
if (annotation != null) {
return AnnotationUtils.synthesizeAnnotation(annotation, element);
} }
// Shortcut: no searchable annotations to be found on plain Java classes and core Spring types...
if (AnnotationsScanner.hasPlainJavaAnnotationsOnly(element)) { if (AnnotationsScanner.hasPlainJavaAnnotationsOnly(element)) {
return null; return null;
} }
// Exhaustive retrieval of merged annotations...
return findAnnotations(element) return findAnnotations(element)
.get(annotationType, null, MergedAnnotationSelectors.firstDirectlyDeclared()) .get(annotationType, null, MergedAnnotationSelectors.firstDirectlyDeclared())
.synthesize(MergedAnnotation::isPresent).orElse(null); .synthesize(MergedAnnotation::isPresent).orElse(null);
@ -680,9 +665,7 @@ public abstract class AnnotatedElementUtils {
* @see #findMergedAnnotation(AnnotatedElement, Class) * @see #findMergedAnnotation(AnnotatedElement, Class)
* @see #getAllMergedAnnotations(AnnotatedElement, Class) * @see #getAllMergedAnnotations(AnnotatedElement, Class)
*/ */
public static <A extends Annotation> Set<A> findAllMergedAnnotations( public static <A extends Annotation> Set<A> findAllMergedAnnotations(AnnotatedElement element, Class<A> annotationType) {
AnnotatedElement element, Class<A> annotationType) {
return findAnnotations(element).stream(annotationType) return findAnnotations(element).stream(annotationType)
.sorted(highAggregateIndexesFirst()) .sorted(highAggregateIndexesFirst())
.collect(MergedAnnotationCollectors.toAnnotationSet()); .collect(MergedAnnotationCollectors.toAnnotationSet());
@ -706,9 +689,7 @@ public abstract class AnnotatedElementUtils {
* @since 5.1 * @since 5.1
* @see #findAllMergedAnnotations(AnnotatedElement, Class) * @see #findAllMergedAnnotations(AnnotatedElement, Class)
*/ */
public static Set<Annotation> findAllMergedAnnotations(AnnotatedElement element, public static Set<Annotation> findAllMergedAnnotations(AnnotatedElement element, Set<Class<? extends Annotation>> annotationTypes) {
Set<Class<? extends Annotation>> annotationTypes) {
return findAnnotations(element).stream() return findAnnotations(element).stream()
.filter(MergedAnnotationPredicates.typeIn(annotationTypes)) .filter(MergedAnnotationPredicates.typeIn(annotationTypes))
.sorted(highAggregateIndexesFirst()) .sorted(highAggregateIndexesFirst())
@ -739,8 +720,8 @@ public abstract class AnnotatedElementUtils {
* @see #findAllMergedAnnotations(AnnotatedElement, Class) * @see #findAllMergedAnnotations(AnnotatedElement, Class)
* @see #findMergedRepeatableAnnotations(AnnotatedElement, Class, Class) * @see #findMergedRepeatableAnnotations(AnnotatedElement, Class, Class)
*/ */
public static <A extends Annotation> Set<A> findMergedRepeatableAnnotations( public static <A extends Annotation> Set<A> findMergedRepeatableAnnotations(AnnotatedElement element,
AnnotatedElement element, Class<A> annotationType) { Class<A> annotationType) {
return findMergedRepeatableAnnotations(element, annotationType, null); return findMergedRepeatableAnnotations(element, annotationType, null);
} }
@ -771,9 +752,8 @@ public abstract class AnnotatedElementUtils {
* @see #findMergedAnnotation(AnnotatedElement, Class) * @see #findMergedAnnotation(AnnotatedElement, Class)
* @see #findAllMergedAnnotations(AnnotatedElement, Class) * @see #findAllMergedAnnotations(AnnotatedElement, Class)
*/ */
public static <A extends Annotation> Set<A> findMergedRepeatableAnnotations( public static <A extends Annotation> Set<A> findMergedRepeatableAnnotations(AnnotatedElement element,
AnnotatedElement element, Class<A> annotationType, Class<A> annotationType, @Nullable Class<? extends Annotation> containerType) {
@Nullable Class<? extends Annotation> containerType) {
return findRepeatableAnnotations(element, containerType, annotationType) return findRepeatableAnnotations(element, containerType, annotationType)
.stream(annotationType) .stream(annotationType)
@ -783,43 +763,40 @@ public abstract class AnnotatedElementUtils {
private static MergedAnnotations getAnnotations(AnnotatedElement element) { private static MergedAnnotations getAnnotations(AnnotatedElement element) {
return MergedAnnotations.from(element, SearchStrategy.INHERITED_ANNOTATIONS, return MergedAnnotations.from(element, SearchStrategy.INHERITED_ANNOTATIONS,
RepeatableContainers.none(), AnnotationUtils.JAVA_LANG_ANNOTATION_FILTER); RepeatableContainers.none(), AnnotationFilter.PLAIN);
} }
private static MergedAnnotations getRepeatableAnnotations(AnnotatedElement element, private static MergedAnnotations getRepeatableAnnotations(AnnotatedElement element,
@Nullable Class<? extends Annotation> containerType, @Nullable Class<? extends Annotation> containerType, Class<? extends Annotation> annotationType) {
Class<? extends Annotation> annotationType) {
RepeatableContainers repeatableContainers = RepeatableContainers.of(annotationType, containerType); RepeatableContainers repeatableContainers = RepeatableContainers.of(annotationType, containerType);
return MergedAnnotations.from(element, SearchStrategy.INHERITED_ANNOTATIONS, return MergedAnnotations.from(element, SearchStrategy.INHERITED_ANNOTATIONS,
repeatableContainers, AnnotationUtils.JAVA_LANG_ANNOTATION_FILTER); repeatableContainers, AnnotationFilter.PLAIN);
} }
private static MergedAnnotations findAnnotations(AnnotatedElement element) { private static MergedAnnotations findAnnotations(AnnotatedElement element) {
return MergedAnnotations.from(element, SearchStrategy.EXHAUSTIVE, return MergedAnnotations.from(element, SearchStrategy.EXHAUSTIVE,
RepeatableContainers.none(), AnnotationUtils.JAVA_LANG_ANNOTATION_FILTER); RepeatableContainers.none(), AnnotationFilter.PLAIN);
} }
private static MergedAnnotations findRepeatableAnnotations(AnnotatedElement element, private static MergedAnnotations findRepeatableAnnotations(AnnotatedElement element,
@Nullable Class<? extends Annotation> containerType, @Nullable Class<? extends Annotation> containerType, Class<? extends Annotation> annotationType) {
Class<? extends Annotation> annotationType) {
RepeatableContainers repeatableContainers = RepeatableContainers.of(annotationType, containerType); RepeatableContainers repeatableContainers = RepeatableContainers.of(annotationType, containerType);
return MergedAnnotations.from(element, SearchStrategy.EXHAUSTIVE, return MergedAnnotations.from(element, SearchStrategy.EXHAUSTIVE,
repeatableContainers, AnnotationUtils.JAVA_LANG_ANNOTATION_FILTER); repeatableContainers, AnnotationFilter.PLAIN);
} }
private static Object parentAndType(MergedAnnotation<Annotation> annotation) { private static Object parentAndType(MergedAnnotation<Annotation> annotation) {
if (annotation.getParent() == null) { if (annotation.getParent() == null) {
return annotation.getType(); return annotation.getType().getName();
} }
return annotation.getParent().getType() + ":" + annotation.getParent().getType(); return annotation.getParent().getType().getName() + ":" + annotation.getParent().getType().getName();
} }
@Nullable @Nullable
private static MultiValueMap<String, Object> nullIfEmpty( private static MultiValueMap<String, Object> nullIfEmpty(MultiValueMap<String, Object> map) {
MultiValueMap<String, Object> map) { return (map.isEmpty() ? null : map);
return map.isEmpty() ? null : map;
} }
private static <A extends Annotation> Comparator<MergedAnnotation<A>> highAggregateIndexesFirst() { private static <A extends Annotation> Comparator<MergedAnnotation<A>> highAggregateIndexesFirst() {
@ -834,7 +811,7 @@ public abstract class AnnotatedElementUtils {
if (!annotation.isPresent()) { if (!annotation.isPresent()) {
return null; return null;
} }
return annotation.asMap(mergedAnnotation -> new AnnotationAttributes(), return annotation.asMap(mergedAnnotation -> new AnnotationAttributes(mergedAnnotation.getType()),
MapValues.of(classValuesAsString, nestedAnnotationsAsMap)); MapValues.of(classValuesAsString, nestedAnnotationsAsMap));
} }

View File

@ -17,10 +17,6 @@
package org.springframework.core.annotation; package org.springframework.core.annotation;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.Collection;
import org.springframework.util.Assert;
/** /**
* Callback interface that can be used to filter specific annotation types. * Callback interface that can be used to filter specific annotation types.
@ -96,52 +92,4 @@ public interface AnnotationFilter {
return new PackagesAnnotationFilter(packages); return new PackagesAnnotationFilter(packages);
} }
/**
* Return an {@link AnnotationFilter} that is the most appropriate for, and
* will always match the given annotation type. Whenever possible,
* {@link AnnotationFilter#PLAIN} will be returned.
* @param annotationType the annotation type to check
* @return the most appropriate annotation filter
*/
static AnnotationFilter mostAppropriateFor(Class<?> annotationType) {
return (PLAIN.matches(annotationType) ? NONE : PLAIN);
}
/**
* Return an {@link AnnotationFilter} that is the most appropriate for, and
* will always match all the given annotation types. Whenever possible,
* {@link AnnotationFilter#PLAIN} will be returned.
* @param annotationTypes the annotation types to check
* @return the most appropriate annotation filter
*/
static AnnotationFilter mostAppropriateFor(Class<?>... annotationTypes) {
return mostAppropriateFor(Arrays.asList(annotationTypes));
}
/**
* Return an {@link AnnotationFilter} that is the most appropriate for, and
* will always match all the given annotation types. Whenever possible,
* {@link AnnotationFilter#PLAIN} will be returned.
* @param annotationTypes the annotation types to check (may be class names
* or class types)
* @return the most appropriate annotation filter
*/
@SuppressWarnings("unchecked")
static AnnotationFilter mostAppropriateFor(Collection<?> annotationTypes) {
for (Object annotationType : annotationTypes) {
if (annotationType == null) {
continue;
}
Assert.isTrue(annotationType instanceof Class || annotationType instanceof String,
"AnnotationType must be a Class or String");
if (annotationType instanceof Class && PLAIN.matches((Class<Annotation>) annotationType)) {
return NONE;
}
if (annotationType instanceof String && PLAIN.matches((String) annotationType)) {
return NONE;
}
}
return PLAIN;
}
} }

View File

@ -55,8 +55,7 @@ final class AnnotationTypeMappings {
private final List<AnnotationTypeMapping> mappings; private final List<AnnotationTypeMapping> mappings;
private AnnotationTypeMappings(AnnotationFilter filter, private AnnotationTypeMappings(AnnotationFilter filter, Class<? extends Annotation> annotationType) {
Class<? extends Annotation> annotationType) {
this.filter = filter; this.filter = filter;
this.mappings = new ArrayList<>(); this.mappings = new ArrayList<>();
addAllMappings(annotationType); addAllMappings(annotationType);
@ -97,27 +96,23 @@ final class AnnotationTypeMappings {
} }
} }
private void addIfPossible(Deque<AnnotationTypeMapping> queue, private void addIfPossible(Deque<AnnotationTypeMapping> queue, AnnotationTypeMapping parent, Annotation ann) {
AnnotationTypeMapping parent, Annotation annotation) { addIfPossible(queue, parent, ann.annotationType(), ann);
addIfPossible(queue, parent, annotation.annotationType(), annotation);
} }
private void addIfPossible(Deque<AnnotationTypeMapping> queue, private void addIfPossible(Deque<AnnotationTypeMapping> queue, @Nullable AnnotationTypeMapping parent,
@Nullable AnnotationTypeMapping parent, Class<? extends Annotation> annotationType, @Nullable Annotation ann) {
Class<? extends Annotation> annotationType, @Nullable Annotation annotation) {
try { try {
queue.addLast(new AnnotationTypeMapping(parent, annotationType, annotation)); queue.addLast(new AnnotationTypeMapping(parent, annotationType, ann));
} }
catch (Exception ex) { catch (Exception ex) {
if (ex instanceof AnnotationConfigurationException) { if (ex instanceof AnnotationConfigurationException) {
throw (AnnotationConfigurationException) ex; throw (AnnotationConfigurationException) ex;
} }
if (failureLogger.isEnabled()) { if (failureLogger.isEnabled()) {
failureLogger.log( failureLogger.log("Failed to introspect meta-annotation " + annotationType.getName(),
"Failed to introspect meta-annotation " (parent != null ? parent.getAnnotationType() : null), ex);
+ annotationType.getName(),
(parent != null) ? parent.getAnnotationType() : null, ex);
} }
} }
} }
@ -167,7 +162,7 @@ final class AnnotationTypeMappings {
* @return type mappings for the annotation type * @return type mappings for the annotation type
*/ */
static AnnotationTypeMappings forAnnotationType(Class<? extends Annotation> annotationType) { static AnnotationTypeMappings forAnnotationType(Class<? extends Annotation> annotationType) {
return forAnnotationType(annotationType, AnnotationFilter.mostAppropriateFor(annotationType)); return forAnnotationType(annotationType, AnnotationFilter.PLAIN);
} }
/** /**
@ -197,7 +192,6 @@ final class AnnotationTypeMappings {
private final Map<Class<? extends Annotation>, AnnotationTypeMappings> mappings; private final Map<Class<? extends Annotation>, AnnotationTypeMappings> mappings;
/** /**
* Create a cache instance with the specified filter. * Create a cache instance with the specified filter.
* @param filter the annotation filter * @param filter the annotation filter
@ -207,7 +201,6 @@ final class AnnotationTypeMappings {
this.mappings = new ConcurrentReferenceHashMap<>(); this.mappings = new ConcurrentReferenceHashMap<>();
} }
/** /**
* Return or create {@link AnnotationTypeMappings} for the specified * Return or create {@link AnnotationTypeMappings} for the specified
* annotation type. * annotation type.
@ -218,8 +211,7 @@ final class AnnotationTypeMappings {
return this.mappings.computeIfAbsent(annotationType, this::createMappings); return this.mappings.computeIfAbsent(annotationType, this::createMappings);
} }
AnnotationTypeMappings createMappings( AnnotationTypeMappings createMappings(Class<? extends Annotation> annotationType) {
Class<? extends Annotation> annotationType) {
return new AnnotationTypeMappings(this.filter, annotationType); return new AnnotationTypeMappings(this.filter, annotationType);
} }

View File

@ -36,7 +36,6 @@ import org.springframework.core.annotation.AnnotationTypeMapping.MirrorSets.Mirr
import org.springframework.core.annotation.MergedAnnotation.MapValues; import org.springframework.core.annotation.MergedAnnotation.MapValues;
import org.springframework.core.annotation.MergedAnnotations.SearchStrategy; import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
import org.springframework.util.ConcurrentReferenceHashMap; import org.springframework.util.ConcurrentReferenceHashMap;
import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
@ -51,12 +50,12 @@ import org.springframework.util.StringUtils;
* *
* <p>As a general rule for runtime-retained annotations (e.g. for transaction * <p>As a general rule for runtime-retained annotations (e.g. for transaction
* control, authorization, or service exposure), always use the lookup methods * control, authorization, or service exposure), always use the lookup methods
* on this class (e.g., {@link #findAnnotation(Method, Class)}, * on this class (e.g., {@link #findAnnotation(Method, Class)} and
* {@link #getAnnotation(Method, Class)}, and {@link #getAnnotations(Method)}) * {@link #getAnnotation(Method, Class)}) instead of the plain annotation lookup
* instead of the plain annotation lookup methods in the JDK. You can still * methods in the JDK. You can still explicitly choose between a <em>get</em>
* explicitly choose between a <em>get</em> lookup on the given class level only * lookup on the given class level only ({@link #getAnnotation(Method, Class)})
* ({@link #getAnnotation(Method, Class)}) and a <em>find</em> lookup in the entire * and a <em>find</em> lookup in the entire inheritance hierarchy of the given
* inheritance hierarchy of the given method ({@link #findAnnotation(Method, Class)}). * method ({@link #findAnnotation(Method, Class)}).
* *
* <h3>Terminology</h3> * <h3>Terminology</h3>
* The terms <em>directly present</em>, <em>indirectly present</em>, and * The terms <em>directly present</em>, <em>indirectly present</em>, and
@ -111,14 +110,13 @@ public abstract class AnnotationUtils {
*/ */
public static final String VALUE = MergedAnnotation.VALUE; public static final String VALUE = MergedAnnotation.VALUE;
private static final AnnotationFilter JAVA_LANG_ANNOTATION_FILTER =
static final AnnotationFilter JAVA_LANG_ANNOTATION_FILTER =
AnnotationFilter.packages("java.lang.annotation"); AnnotationFilter.packages("java.lang.annotation");
private static Map<Class<? extends Annotation>, Map<String, DefaultValueHolder>> defaultValuesCache = private static Map<Class<? extends Annotation>, Map<String, DefaultValueHolder>> defaultValuesCache =
new ConcurrentReferenceHashMap<>(); new ConcurrentReferenceHashMap<>();
/** /**
* Determine whether the given class is a candidate for carrying one of the specified * Determine whether the given class is a candidate for carrying one of the specified
* annotations (at type, method or field level). * annotations (at type, method or field level).
@ -180,10 +178,18 @@ public abstract class AnnotationUtils {
* @return the first matching annotation, or {@code null} if not found * @return the first matching annotation, or {@code null} if not found
* @since 4.0 * @since 4.0
*/ */
@SuppressWarnings("unchecked")
@Nullable @Nullable
public static <A extends Annotation> A getAnnotation(Annotation annotation, public static <A extends Annotation> A getAnnotation(Annotation annotation, Class<A> annotationType) {
Class<A> annotationType) { // Shortcut: directly present on the element, with no merging needed?
if (annotationType.isInstance(annotation)) {
return synthesizeAnnotation((A) annotation, annotationType);
}
// Shortcut: no searchable annotations to be found on plain Java classes and core Spring types...
if (AnnotationsScanner.hasPlainJavaAnnotationsOnly(annotation)) {
return null;
}
// Exhaustive retrieval of merged annotations...
return MergedAnnotations.from(annotation) return MergedAnnotations.from(annotation)
.get(annotationType).withNonMergedAttributes() .get(annotationType).withNonMergedAttributes()
.synthesize(AnnotationUtils::isSingleLevelPresent).orElse(null); .synthesize(AnnotationUtils::isSingleLevelPresent).orElse(null);
@ -202,15 +208,27 @@ public abstract class AnnotationUtils {
* @since 3.1 * @since 3.1
*/ */
@Nullable @Nullable
public static <A extends Annotation> A getAnnotation( public static <A extends Annotation> A getAnnotation(AnnotatedElement annotatedElement, Class<A> annotationType) {
AnnotatedElement annotatedElement, Class<A> annotationType) { // Shortcut: directly present on the element, with no merging needed?
if (AnnotationFilter.PLAIN.matches(annotationType)) {
return annotatedElement.getAnnotation(annotationType);
}
// Shortcut: no searchable annotations to be found on plain Java classes and core Spring types...
if (AnnotationsScanner.hasPlainJavaAnnotationsOnly(annotatedElement)) {
return null;
}
// Exhaustive retrieval of merged annotations...
return MergedAnnotations.from(annotatedElement, SearchStrategy.INHERITED_ANNOTATIONS, return MergedAnnotations.from(annotatedElement, SearchStrategy.INHERITED_ANNOTATIONS,
RepeatableContainers.none(), AnnotationFilter.PLAIN) RepeatableContainers.none(), AnnotationFilter.PLAIN)
.get(annotationType).withNonMergedAttributes() .get(annotationType).withNonMergedAttributes()
.synthesize(AnnotationUtils::isSingleLevelPresent).orElse(null); .synthesize(AnnotationUtils::isSingleLevelPresent).orElse(null);
} }
private static <A extends Annotation> boolean isSingleLevelPresent(MergedAnnotation<A> mergedAnnotation) {
int depth = mergedAnnotation.getDepth();
return (depth == 0 || depth == 1);
}
/** /**
* Get a single {@link Annotation} of {@code annotationType} from the * Get a single {@link Annotation} of {@code annotationType} from the
* supplied {@link Method}, where the annotation is either <em>present</em> * supplied {@link Method}, where the annotation is either <em>present</em>
@ -226,13 +244,9 @@ public abstract class AnnotationUtils {
* @see #getAnnotation(AnnotatedElement, Class) * @see #getAnnotation(AnnotatedElement, Class)
*/ */
@Nullable @Nullable
public static <A extends Annotation> A getAnnotation(Method method, public static <A extends Annotation> A getAnnotation(Method method, Class<A> annotationType) {
Class<A> annotationType) { Method resolvedMethod = BridgeMethodResolver.findBridgedMethod(method);
return getAnnotation((AnnotatedElement) resolvedMethod, annotationType);
return MergedAnnotations.from(method, SearchStrategy.INHERITED_ANNOTATIONS,
RepeatableContainers.none(), AnnotationFilter.PLAIN)
.get(annotationType).withNonMergedAttributes()
.synthesize(AnnotationUtils::isSingleLevelPresent).orElse(null);
} }
/** /**
@ -245,14 +259,18 @@ public abstract class AnnotationUtils {
* failed to resolve at runtime) * failed to resolve at runtime)
* @since 4.0.8 * @since 4.0.8
* @see AnnotatedElement#getAnnotations() * @see AnnotatedElement#getAnnotations()
* @deprecated as of 5.2 since it is superseded by the {@link MergedAnnotations} API
*/ */
@Deprecated
@Nullable @Nullable
public static Annotation[] getAnnotations(AnnotatedElement annotatedElement) { public static Annotation[] getAnnotations(AnnotatedElement annotatedElement) {
return MergedAnnotations.from(annotatedElement, SearchStrategy.INHERITED_ANNOTATIONS, try {
RepeatableContainers.none(), AnnotationFilter.NONE).stream() return synthesizeAnnotationArray(annotatedElement.getAnnotations(), annotatedElement);
.filter(MergedAnnotation::isDirectlyPresent) }
.map(MergedAnnotation::withNonMergedAttributes) catch (Throwable ex) {
.collect(MergedAnnotationCollectors.toAnnotationArray()); handleIntrospectionFailure(annotatedElement, ex);
return null;
}
} }
/** /**
@ -266,14 +284,18 @@ public abstract class AnnotationUtils {
* failed to resolve at runtime) * failed to resolve at runtime)
* @see org.springframework.core.BridgeMethodResolver#findBridgedMethod(Method) * @see org.springframework.core.BridgeMethodResolver#findBridgedMethod(Method)
* @see AnnotatedElement#getAnnotations() * @see AnnotatedElement#getAnnotations()
* @deprecated as of 5.2 since it is superseded by the {@link MergedAnnotations} API
*/ */
@Deprecated
@Nullable @Nullable
public static Annotation[] getAnnotations(Method method) { public static Annotation[] getAnnotations(Method method) {
return MergedAnnotations.from(method, SearchStrategy.INHERITED_ANNOTATIONS, try {
RepeatableContainers.none(), AnnotationFilter.NONE).stream() return synthesizeAnnotationArray(BridgeMethodResolver.findBridgedMethod(method).getAnnotations(), method);
.filter(MergedAnnotation::isDirectlyPresent) }
.map(MergedAnnotation::withNonMergedAttributes) catch (Throwable ex) {
.collect(MergedAnnotationCollectors.toAnnotationArray()); handleIntrospectionFailure(method, ex);
return null;
}
} }
/** /**
@ -302,9 +324,11 @@ public abstract class AnnotationUtils {
* @see org.springframework.core.BridgeMethodResolver#findBridgedMethod * @see org.springframework.core.BridgeMethodResolver#findBridgedMethod
* @see java.lang.annotation.Repeatable * @see java.lang.annotation.Repeatable
* @see java.lang.reflect.AnnotatedElement#getAnnotationsByType * @see java.lang.reflect.AnnotatedElement#getAnnotationsByType
* @deprecated as of 5.2 since it is superseded by the {@link MergedAnnotations} API
*/ */
public static <A extends Annotation> Set<A> getRepeatableAnnotations( @Deprecated
AnnotatedElement annotatedElement, Class<A> annotationType) { public static <A extends Annotation> Set<A> getRepeatableAnnotations(AnnotatedElement annotatedElement,
Class<A> annotationType) {
return getRepeatableAnnotations(annotatedElement, annotationType, null); return getRepeatableAnnotations(annotatedElement, annotationType, null);
} }
@ -338,17 +362,17 @@ public abstract class AnnotationUtils {
* @see org.springframework.core.BridgeMethodResolver#findBridgedMethod * @see org.springframework.core.BridgeMethodResolver#findBridgedMethod
* @see java.lang.annotation.Repeatable * @see java.lang.annotation.Repeatable
* @see java.lang.reflect.AnnotatedElement#getAnnotationsByType * @see java.lang.reflect.AnnotatedElement#getAnnotationsByType
* @deprecated as of 5.2 since it is superseded by the {@link MergedAnnotations} API
*/ */
public static <A extends Annotation> Set<A> getRepeatableAnnotations( @Deprecated
AnnotatedElement annotatedElement, Class<A> annotationType, public static <A extends Annotation> Set<A> getRepeatableAnnotations(AnnotatedElement annotatedElement,
@Nullable Class<? extends Annotation> containerAnnotationType) { Class<A> annotationType, @Nullable Class<? extends Annotation> containerAnnotationType) {
RepeatableContainers repeatableContainers = containerAnnotationType != null ? RepeatableContainers repeatableContainers = (containerAnnotationType != null ?
RepeatableContainers.of(annotationType, containerAnnotationType) : RepeatableContainers.of(annotationType, containerAnnotationType) :
RepeatableContainers.standardRepeatables(); RepeatableContainers.standardRepeatables());
AnnotationFilter annotationFilter = AnnotationFilter.mostAppropriateFor(annotationType);
return MergedAnnotations.from(annotatedElement, SearchStrategy.SUPER_CLASS, return MergedAnnotations.from(annotatedElement, SearchStrategy.SUPER_CLASS,
repeatableContainers, annotationFilter) repeatableContainers, AnnotationFilter.PLAIN)
.stream(annotationType) .stream(annotationType)
.filter(MergedAnnotationPredicates.firstRunOf(MergedAnnotation::getAggregateIndex)) .filter(MergedAnnotationPredicates.firstRunOf(MergedAnnotation::getAggregateIndex))
.map(MergedAnnotation::withNonMergedAttributes) .map(MergedAnnotation::withNonMergedAttributes)
@ -382,9 +406,11 @@ public abstract class AnnotationUtils {
* @see org.springframework.core.BridgeMethodResolver#findBridgedMethod * @see org.springframework.core.BridgeMethodResolver#findBridgedMethod
* @see java.lang.annotation.Repeatable * @see java.lang.annotation.Repeatable
* @see java.lang.reflect.AnnotatedElement#getDeclaredAnnotationsByType * @see java.lang.reflect.AnnotatedElement#getDeclaredAnnotationsByType
* @deprecated as of 5.2 since it is superseded by the {@link MergedAnnotations} API
*/ */
public static <A extends Annotation> Set<A> getDeclaredRepeatableAnnotations( @Deprecated
AnnotatedElement annotatedElement, Class<A> annotationType) { public static <A extends Annotation> Set<A> getDeclaredRepeatableAnnotations(AnnotatedElement annotatedElement,
Class<A> annotationType) {
return getDeclaredRepeatableAnnotations(annotatedElement, annotationType, null); return getDeclaredRepeatableAnnotations(annotatedElement, annotationType, null);
} }
@ -418,18 +444,17 @@ public abstract class AnnotationUtils {
* @see org.springframework.core.BridgeMethodResolver#findBridgedMethod * @see org.springframework.core.BridgeMethodResolver#findBridgedMethod
* @see java.lang.annotation.Repeatable * @see java.lang.annotation.Repeatable
* @see java.lang.reflect.AnnotatedElement#getDeclaredAnnotationsByType * @see java.lang.reflect.AnnotatedElement#getDeclaredAnnotationsByType
* @deprecated as of 5.2 since it is superseded by the {@link MergedAnnotations} API
*/ */
public static <A extends Annotation> Set<A> getDeclaredRepeatableAnnotations( @Deprecated
AnnotatedElement annotatedElement, Class<A> annotationType, public static <A extends Annotation> Set<A> getDeclaredRepeatableAnnotations(AnnotatedElement annotatedElement,
@Nullable Class<? extends Annotation> containerAnnotationType) { Class<A> annotationType, @Nullable Class<? extends Annotation> containerAnnotationType) {
RepeatableContainers repeatableContainers = containerAnnotationType != null ? RepeatableContainers repeatableContainers = containerAnnotationType != null ?
RepeatableContainers.of(annotationType, containerAnnotationType) : RepeatableContainers.of(annotationType, containerAnnotationType) :
RepeatableContainers.standardRepeatables(); RepeatableContainers.standardRepeatables();
AnnotationFilter annotationFilter = AnnotationFilter.mostAppropriateFor(
annotationType, containerAnnotationType);
return MergedAnnotations.from(annotatedElement, SearchStrategy.DIRECT, return MergedAnnotations.from(annotatedElement, SearchStrategy.DIRECT,
repeatableContainers, annotationFilter).stream(annotationType) repeatableContainers, AnnotationFilter.PLAIN).stream(annotationType)
.map(MergedAnnotation::withNonMergedAttributes) .map(MergedAnnotation::withNonMergedAttributes)
.collect(MergedAnnotationCollectors.toAnnotationSet()); .collect(MergedAnnotationCollectors.toAnnotationSet());
} }
@ -452,11 +477,21 @@ public abstract class AnnotationUtils {
*/ */
@Nullable @Nullable
public static <A extends Annotation> A findAnnotation( public static <A extends Annotation> A findAnnotation(
AnnotatedElement annotatedElement, Class<A> annotationType) { AnnotatedElement annotatedElement, @Nullable Class<A> annotationType) {
AnnotationFilter annotationFilter = AnnotationFilter.mostAppropriateFor(annotationType); if (annotationType == null) {
return MergedAnnotations.from(annotatedElement, SearchStrategy.DIRECT, return null;
RepeatableContainers.none(), annotationFilter) }
// Shortcut: directly present on the element, with no merging needed?
if (AnnotationFilter.PLAIN.matches(annotationType)) {
return annotatedElement.getDeclaredAnnotation(annotationType);
}
// Shortcut: no searchable annotations to be found on plain Java classes and core Spring types...
if (AnnotationsScanner.hasPlainJavaAnnotationsOnly(annotatedElement)) {
return null;
}
// Exhaustive retrieval of merged annotations...
return MergedAnnotations.from(annotatedElement, SearchStrategy.INHERITED_ANNOTATIONS)
.get(annotationType).withNonMergedAttributes() .get(annotationType).withNonMergedAttributes()
.synthesize(MergedAnnotation::isPresent).orElse(null); .synthesize(MergedAnnotation::isPresent).orElse(null);
} }
@ -481,10 +516,16 @@ public abstract class AnnotationUtils {
if (annotationType == null) { if (annotationType == null) {
return null; return null;
} }
// Shortcut: directly present on the element, with no merging needed?
AnnotationFilter annotationFilter = AnnotationFilter.mostAppropriateFor(annotationType); if (AnnotationFilter.PLAIN.matches(annotationType)) {
return MergedAnnotations.from(method, SearchStrategy.EXHAUSTIVE, return method.getDeclaredAnnotation(annotationType);
RepeatableContainers.none(), annotationFilter) }
// Shortcut: no searchable annotations to be found on plain Java classes and core Spring types...
if (AnnotationsScanner.hasPlainJavaAnnotationsOnly(method)) {
return null;
}
// Exhaustive retrieval of merged annotations...
return MergedAnnotations.from(method, SearchStrategy.EXHAUSTIVE)
.get(annotationType).withNonMergedAttributes() .get(annotationType).withNonMergedAttributes()
.synthesize(MergedAnnotation::isPresent).orElse(null); .synthesize(MergedAnnotation::isPresent).orElse(null);
} }
@ -512,10 +553,20 @@ public abstract class AnnotationUtils {
* @return the first matching annotation, or {@code null} if not found * @return the first matching annotation, or {@code null} if not found
*/ */
@Nullable @Nullable
public static <A extends Annotation> A findAnnotation(Class<?> clazz, Class<A> annotationType) { public static <A extends Annotation> A findAnnotation(Class<?> clazz, @Nullable Class<A> annotationType) {
AnnotationFilter annotationFilter = AnnotationFilter.mostAppropriateFor(annotationType); if (annotationType == null) {
return MergedAnnotations.from(clazz, SearchStrategy.EXHAUSTIVE, return null;
RepeatableContainers.none(), annotationFilter) }
// Shortcut: directly present on the element, with no merging needed?
if (AnnotationFilter.PLAIN.matches(annotationType)) {
return clazz.getDeclaredAnnotation(annotationType);
}
// Shortcut: no searchable annotations to be found on plain Java classes and core Spring types...
if (AnnotationsScanner.hasPlainJavaAnnotationsOnly(clazz)) {
return null;
}
// Exhaustive retrieval of merged annotations...
return MergedAnnotations.from(clazz, SearchStrategy.EXHAUSTIVE)
.get(annotationType).withNonMergedAttributes() .get(annotationType).withNonMergedAttributes()
.synthesize(MergedAnnotation::isPresent).orElse(null); .synthesize(MergedAnnotation::isPresent).orElse(null);
} }
@ -539,9 +590,9 @@ public abstract class AnnotationUtils {
* or {@code null} if not found * or {@code null} if not found
* @see Class#isAnnotationPresent(Class) * @see Class#isAnnotationPresent(Class)
* @see Class#getDeclaredAnnotations() * @see Class#getDeclaredAnnotations()
* @see #findAnnotationDeclaringClassForTypes(List, Class) * @deprecated as of 5.2 since it is superseded by the {@link MergedAnnotations} API
* @see #isAnnotationDeclaredLocally(Class, Class)
*/ */
@Deprecated
@Nullable @Nullable
public static Class<?> findAnnotationDeclaringClass( public static Class<?> findAnnotationDeclaringClass(
Class<? extends Annotation> annotationType, @Nullable Class<?> clazz) { Class<? extends Annotation> annotationType, @Nullable Class<?> clazz) {
@ -550,9 +601,7 @@ public abstract class AnnotationUtils {
return null; return null;
} }
AnnotationFilter annotationFilter = AnnotationFilter.mostAppropriateFor(annotationType); return (Class<?>) MergedAnnotations.from(clazz, SearchStrategy.SUPER_CLASS)
return (Class<?>) MergedAnnotations.from(clazz, SearchStrategy.SUPER_CLASS,
RepeatableContainers.none(), annotationFilter)
.get(annotationType, MergedAnnotation::isDirectlyPresent) .get(annotationType, MergedAnnotation::isDirectlyPresent)
.getSource(); .getSource();
} }
@ -578,9 +627,9 @@ public abstract class AnnotationUtils {
* @since 3.2.2 * @since 3.2.2
* @see Class#isAnnotationPresent(Class) * @see Class#isAnnotationPresent(Class)
* @see Class#getDeclaredAnnotations() * @see Class#getDeclaredAnnotations()
* @see #findAnnotationDeclaringClass(Class, Class) * @deprecated as of 5.2 since it is superseded by the {@link MergedAnnotations} API
* @see #isAnnotationDeclaredLocally(Class, Class)
*/ */
@Deprecated
@Nullable @Nullable
public static Class<?> findAnnotationDeclaringClassForTypes( public static Class<?> findAnnotationDeclaringClassForTypes(
List<Class<? extends Annotation>> annotationTypes, @Nullable Class<?> clazz) { List<Class<? extends Annotation>> annotationTypes, @Nullable Class<?> clazz) {
@ -589,9 +638,7 @@ public abstract class AnnotationUtils {
return null; return null;
} }
AnnotationFilter annotationFilter = AnnotationFilter.mostAppropriateFor(annotationTypes); return (Class<?>) MergedAnnotations.from(clazz, SearchStrategy.SUPER_CLASS)
return (Class<?>) MergedAnnotations.from(clazz, SearchStrategy.SUPER_CLASS,
RepeatableContainers.none(), annotationFilter)
.stream() .stream()
.filter(MergedAnnotationPredicates.typeIn(annotationTypes).and(MergedAnnotation::isDirectlyPresent)) .filter(MergedAnnotationPredicates.typeIn(annotationTypes).and(MergedAnnotation::isDirectlyPresent))
.map(MergedAnnotation::getSource) .map(MergedAnnotation::getSource)
@ -605,16 +652,13 @@ public abstract class AnnotationUtils {
* <p>The supplied {@link Class} may represent any type. * <p>The supplied {@link Class} may represent any type.
* <p>Meta-annotations will <em>not</em> be searched. * <p>Meta-annotations will <em>not</em> be searched.
* <p>Note: This method does <strong>not</strong> determine if the annotation * <p>Note: This method does <strong>not</strong> determine if the annotation
* is {@linkplain java.lang.annotation.Inherited inherited}. For greater * is {@linkplain java.lang.annotation.Inherited inherited}.
* clarity regarding inherited annotations, consider using
* {@link #isAnnotationInherited(Class, Class)} instead.
* @param annotationType the annotation type to look for * @param annotationType the annotation type to look for
* @param clazz the class to check for the annotation on * @param clazz the class to check for the annotation on
* @return {@code true} if an annotation of the specified {@code annotationType} * @return {@code true} if an annotation of the specified {@code annotationType}
* is <em>directly present</em> * is <em>directly present</em>
* @see java.lang.Class#getDeclaredAnnotations() * @see java.lang.Class#getDeclaredAnnotations()
* @see java.lang.Class#getDeclaredAnnotation(Class) * @see java.lang.Class#getDeclaredAnnotation(Class)
* @see #isAnnotationInherited(Class, Class)
*/ */
public static boolean isAnnotationDeclaredLocally(Class<? extends Annotation> annotationType, Class<?> clazz) { public static boolean isAnnotationDeclaredLocally(Class<? extends Annotation> annotationType, Class<?> clazz) {
return MergedAnnotations.from(clazz).get(annotationType).isDirectlyPresent(); return MergedAnnotations.from(clazz).get(annotationType).isDirectlyPresent();
@ -638,7 +682,9 @@ public abstract class AnnotationUtils {
* is <em>present</em> and <em>inherited</em> * is <em>present</em> and <em>inherited</em>
* @see Class#isAnnotationPresent(Class) * @see Class#isAnnotationPresent(Class)
* @see #isAnnotationDeclaredLocally(Class, Class) * @see #isAnnotationDeclaredLocally(Class, Class)
* @deprecated as of 5.2 since it is superseded by the {@link MergedAnnotations} API
*/ */
@Deprecated
public static boolean isAnnotationInherited(Class<? extends Annotation> annotationType, Class<?> clazz) { public static boolean isAnnotationInherited(Class<? extends Annotation> annotationType, Class<?> clazz) {
return MergedAnnotations.from(clazz, SearchStrategy.INHERITED_ANNOTATIONS) return MergedAnnotations.from(clazz, SearchStrategy.INHERITED_ANNOTATIONS)
.stream(annotationType) .stream(annotationType)
@ -654,11 +700,27 @@ public abstract class AnnotationUtils {
* @param metaAnnotationType the type of meta-annotation to search for * @param metaAnnotationType the type of meta-annotation to search for
* @return {@code true} if such an annotation is meta-present * @return {@code true} if such an annotation is meta-present
* @since 4.2.1 * @since 4.2.1
* @deprecated as of 5.2 since it is superseded by the {@link MergedAnnotations} API
*/ */
@Deprecated
public static boolean isAnnotationMetaPresent(Class<? extends Annotation> annotationType, public static boolean isAnnotationMetaPresent(Class<? extends Annotation> annotationType,
@Nullable Class<? extends Annotation> metaAnnotationType) { @Nullable Class<? extends Annotation> metaAnnotationType) {
return MergedAnnotations.from(annotationType, SearchStrategy.EXHAUSTIVE).isPresent(annotationType); if (metaAnnotationType == null) {
return false;
}
// Shortcut: directly present on the element, with no merging needed?
if (AnnotationFilter.PLAIN.matches(annotationType) ||
AnnotationFilter.PLAIN.matches(metaAnnotationType)) {
return annotationType.isAnnotationPresent(metaAnnotationType);
}
// Shortcut: no searchable annotations to be found on plain Java classes and core Spring types...
if (AnnotationsScanner.hasPlainJavaAnnotationsOnly(annotationType)) {
return false;
}
// Exhaustive retrieval of merged annotations...
return (MergedAnnotations.from(
annotationType, SearchStrategy.INHERITED_ANNOTATIONS).isPresent(metaAnnotationType));
} }
/** /**
@ -731,8 +793,8 @@ public abstract class AnnotationUtils {
* corresponding attribute values as values (never {@code null}) * corresponding attribute values as values (never {@code null})
* @see #getAnnotationAttributes(Annotation, boolean, boolean) * @see #getAnnotationAttributes(Annotation, boolean, boolean)
*/ */
public static Map<String, Object> getAnnotationAttributes(Annotation annotation, public static Map<String, Object> getAnnotationAttributes(
boolean classValuesAsString) { Annotation annotation, boolean classValuesAsString) {
return getAnnotationAttributes(annotation, classValuesAsString, false); return getAnnotationAttributes(annotation, classValuesAsString, false);
} }
@ -753,11 +815,10 @@ public abstract class AnnotationUtils {
* and corresponding attribute values as values (never {@code null}) * and corresponding attribute values as values (never {@code null})
* @since 3.1.1 * @since 3.1.1
*/ */
public static AnnotationAttributes getAnnotationAttributes(Annotation annotation, public static AnnotationAttributes getAnnotationAttributes(
boolean classValuesAsString, boolean nestedAnnotationsAsMap) { Annotation annotation, boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
return getAnnotationAttributes(null, annotation, classValuesAsString, return getAnnotationAttributes(null, annotation, classValuesAsString, nestedAnnotationsAsMap);
nestedAnnotationsAsMap);
} }
/** /**
@ -801,22 +862,14 @@ public abstract class AnnotationUtils {
@Nullable AnnotatedElement annotatedElement, Annotation annotation, @Nullable AnnotatedElement annotatedElement, Annotation annotation,
boolean classValuesAsString, boolean nestedAnnotationsAsMap) { boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
ClassLoader classLoader = annotation.annotationType().getClassLoader();
MapValues[] mapValues = MapValues.of(classValuesAsString, nestedAnnotationsAsMap); MapValues[] mapValues = MapValues.of(classValuesAsString, nestedAnnotationsAsMap);
return MergedAnnotation.from(annotatedElement, annotation) return MergedAnnotation.from(annotatedElement, annotation)
.withNonMergedAttributes() .withNonMergedAttributes()
.asMap(getAnnotationAttributesFactory(classLoader), mapValues); .asMap(getAnnotationAttributesFactory(), mapValues);
} }
@SuppressWarnings("unchecked") private static Function<MergedAnnotation<?>, AnnotationAttributes> getAnnotationAttributesFactory() {
private static Function<MergedAnnotation<?>, AnnotationAttributes> getAnnotationAttributesFactory( return (annotation -> new AnnotationAttributes(annotation.getType(), true));
ClassLoader classLoader) {
return annotation -> {
Class<Annotation> type = (Class<Annotation>) ClassUtils
.resolveClassName(annotation.getType(), classLoader);
return new AnnotationAttributes(type, true);
};
} }
/** /**
@ -861,9 +914,8 @@ public abstract class AnnotationUtils {
} }
else { else {
// If we have nested annotations, we need them as nested maps // If we have nested annotations, we need them as nested maps
AnnotationAttributes attributes = MergedAnnotation.from(annotationType).asMap( AnnotationAttributes attributes = MergedAnnotation.from(annotationType)
getAnnotationAttributesFactory(annotationType.getClassLoader()), .asMap(getAnnotationAttributesFactory(), MapValues.ANNOTATION_TO_MAP);
MapValues.ANNOTATION_TO_MAP);
for (Map.Entry<String, Object> element : attributes.entrySet()) { for (Map.Entry<String, Object> element : attributes.entrySet()) {
result.put(element.getKey(), new DefaultValueHolder(element.getValue())); result.put(element.getKey(), new DefaultValueHolder(element.getValue()));
} }
@ -927,18 +979,14 @@ public abstract class AnnotationUtils {
} }
} }
private static Object getAttributeValueForMirrorResolution(Method attribute, private static Object getAttributeValueForMirrorResolution(Method attribute, Object attributes) {
Object attributes) {
Object result = ((AnnotationAttributes) attributes).get(attribute.getName()); Object result = ((AnnotationAttributes) attributes).get(attribute.getName());
return result instanceof DefaultValueHolder ? return (result instanceof DefaultValueHolder ? ((DefaultValueHolder) result).defaultValue : result);
((DefaultValueHolder) result).defaultValue :
result;
} }
@Nullable @Nullable
private static Object adaptValue(@Nullable Object annotatedElement, private static Object adaptValue(
@Nullable Object value, boolean classValuesAsString) { @Nullable Object annotatedElement, @Nullable Object value, boolean classValuesAsString) {
if (classValuesAsString) { if (classValuesAsString) {
if (value instanceof Class) { if (value instanceof Class) {
@ -962,8 +1010,7 @@ public abstract class AnnotationUtils {
Annotation[] synthesized = (Annotation[]) Array.newInstance( Annotation[] synthesized = (Annotation[]) Array.newInstance(
annotations.getClass().getComponentType(), annotations.length); annotations.getClass().getComponentType(), annotations.length);
for (int i = 0; i < annotations.length; i++) { for (int i = 0; i < annotations.length; i++) {
synthesized[i] = MergedAnnotation.from(annotatedElement, annotations[i]) synthesized[i] = MergedAnnotation.from(annotatedElement, annotations[i]).synthesize();
.synthesize();
} }
return synthesized; return synthesized;
} }
@ -994,9 +1041,7 @@ public abstract class AnnotationUtils {
* @see #getValue(Annotation) * @see #getValue(Annotation)
*/ */
@Nullable @Nullable
public static Object getValue(@Nullable Annotation annotation, public static Object getValue(@Nullable Annotation annotation, @Nullable String attributeName) {
@Nullable String attributeName) {
if (annotation == null || !StringUtils.hasText(attributeName)) { if (annotation == null || !StringUtils.hasText(attributeName)) {
return null; return null;
} }
@ -1047,8 +1092,7 @@ public abstract class AnnotationUtils {
* @param ex the exception that we encountered * @param ex the exception that we encountered
* @see #rethrowAnnotationConfigurationException * @see #rethrowAnnotationConfigurationException
*/ */
private static void handleIntrospectionFailure(@Nullable AnnotatedElement element, private static void handleIntrospectionFailure(@Nullable AnnotatedElement element, Throwable ex) {
Throwable ex) {
rethrowAnnotationConfigurationException(ex); rethrowAnnotationConfigurationException(ex);
IntrospectionFailureLogger logger = IntrospectionFailureLogger.INFO; IntrospectionFailureLogger logger = IntrospectionFailureLogger.INFO;
boolean meta = false; boolean meta = false;
@ -1085,12 +1129,8 @@ public abstract class AnnotationUtils {
* @see #getDefaultValue(Class, String) * @see #getDefaultValue(Class, String)
*/ */
@Nullable @Nullable
public static Object getDefaultValue(@Nullable Annotation annotation, public static Object getDefaultValue(@Nullable Annotation annotation, @Nullable String attributeName) {
@Nullable String attributeName) { return (annotation != null ? getDefaultValue(annotation.annotationType(), attributeName) : null);
return annotation != null ?
getDefaultValue(annotation.annotationType(), attributeName) :
null;
} }
/** /**
@ -1115,14 +1155,12 @@ public abstract class AnnotationUtils {
*/ */
@Nullable @Nullable
public static Object getDefaultValue( public static Object getDefaultValue(
@Nullable Class<? extends Annotation> annotationType, @Nullable Class<? extends Annotation> annotationType, @Nullable String attributeName) {
@Nullable String attributeName) {
if (annotationType == null || !StringUtils.hasText(attributeName)) { if (annotationType == null || !StringUtils.hasText(attributeName)) {
return null; return null;
} }
return MergedAnnotation.from(annotationType) return MergedAnnotation.from(annotationType).getDefaultValue(attributeName).orElse(null);
.getDefaultValue(attributeName).orElse(null);
} }
/** /**
@ -1145,7 +1183,29 @@ public abstract class AnnotationUtils {
public static <A extends Annotation> A synthesizeAnnotation( public static <A extends Annotation> A synthesizeAnnotation(
A annotation, @Nullable AnnotatedElement annotatedElement) { A annotation, @Nullable AnnotatedElement annotatedElement) {
return synthesizeAnnotation(annotation, (Object) annotatedElement); if (annotation instanceof SynthesizedAnnotation || AnnotationFilter.PLAIN.matches(annotation)) {
return annotation;
}
return MergedAnnotation.from(annotatedElement, annotation).synthesize();
}
/**
* <em>Synthesize</em> an annotation from its default attributes values.
* <p>This method simply delegates to
* {@link #synthesizeAnnotation(Map, Class, AnnotatedElement)},
* supplying an empty map for the source attribute values and {@code null}
* for the {@link AnnotatedElement}.
* @param annotationType the type of annotation to synthesize
* @return the synthesized annotation
* @throws IllegalArgumentException if a required attribute is missing
* @throws AnnotationConfigurationException if invalid configuration of
* {@code @AliasFor} is detected
* @since 4.2
* @see #synthesizeAnnotation(Map, Class, AnnotatedElement)
* @see #synthesizeAnnotation(Annotation, AnnotatedElement)
*/
public static <A extends Annotation> A synthesizeAnnotation(Class<A> annotationType) {
return synthesizeAnnotation(Collections.emptyMap(), annotationType, null);
} }
/** /**
@ -1177,38 +1237,17 @@ public abstract class AnnotationUtils {
* @see #getAnnotationAttributes(AnnotatedElement, Annotation) * @see #getAnnotationAttributes(AnnotatedElement, Annotation)
* @see #getAnnotationAttributes(AnnotatedElement, Annotation, boolean, boolean) * @see #getAnnotationAttributes(AnnotatedElement, Annotation, boolean, boolean)
*/ */
public static <A extends Annotation> A synthesizeAnnotation( public static <A extends Annotation> A synthesizeAnnotation(Map<String, Object> attributes,
Map<String, Object> attributes, Class<A> annotationType, Class<A> annotationType, @Nullable AnnotatedElement annotatedElement) {
@Nullable AnnotatedElement annotatedElement) {
try { try {
return MergedAnnotation.from(annotatedElement, annotationType, attributes) return MergedAnnotation.from(annotatedElement, annotationType, attributes).synthesize();
.synthesize();
} }
catch (NoSuchElementException | IllegalStateException ex) { catch (NoSuchElementException | IllegalStateException ex) {
throw new IllegalArgumentException(ex); throw new IllegalArgumentException(ex);
} }
} }
/**
* <em>Synthesize</em> an annotation from its default attributes values.
* <p>This method simply delegates to
* {@link #synthesizeAnnotation(Map, Class, AnnotatedElement)},
* supplying an empty map for the source attribute values and {@code null}
* for the {@link AnnotatedElement}.
* @param annotationType the type of annotation to synthesize
* @return the synthesized annotation
* @throws IllegalArgumentException if a required attribute is missing
* @throws AnnotationConfigurationException if invalid configuration of
* {@code @AliasFor} is detected
* @since 4.2
* @see #synthesizeAnnotation(Map, Class, AnnotatedElement)
* @see #synthesizeAnnotation(Annotation, AnnotatedElement)
*/
public static <A extends Annotation> A synthesizeAnnotation(Class<A> annotationType) {
return synthesizeAnnotation(Collections.emptyMap(), annotationType, null);
}
/** /**
* <em>Synthesize</em> an array of annotations from the supplied array * <em>Synthesize</em> an array of annotations from the supplied array
* of {@code annotations} by creating a new array of the same size and * of {@code annotations} by creating a new array of the same size and
@ -1226,9 +1265,7 @@ public abstract class AnnotationUtils {
* @see #synthesizeAnnotation(Annotation, AnnotatedElement) * @see #synthesizeAnnotation(Annotation, AnnotatedElement)
* @see #synthesizeAnnotation(Map, Class, AnnotatedElement) * @see #synthesizeAnnotation(Map, Class, AnnotatedElement)
*/ */
static Annotation[] synthesizeAnnotationArray(Annotation[] annotations, static Annotation[] synthesizeAnnotationArray(Annotation[] annotations, AnnotatedElement annotatedElement) {
@Nullable Object annotatedElement) {
if (AnnotationsScanner.hasPlainJavaAnnotationsOnly(annotatedElement)) { if (AnnotationsScanner.hasPlainJavaAnnotationsOnly(annotatedElement)) {
return annotations; return annotations;
} }
@ -1240,16 +1277,6 @@ public abstract class AnnotationUtils {
return synthesized; return synthesized;
} }
private static <A extends Annotation> A synthesizeAnnotation(A annotation,
@Nullable Object annotatedElement) {
if (annotation instanceof SynthesizedAnnotation ||
AnnotationsScanner.hasPlainJavaAnnotationsOnly(annotatedElement) ||
AnnotationFilter.PLAIN.matches(annotation)) {
return annotation;
}
return MergedAnnotation.from(annotatedElement, annotation).synthesize();
}
/** /**
* Clear the internal annotation metadata cache. * Clear the internal annotation metadata cache.
* @since 4.3.15 * @since 4.3.15
@ -1259,11 +1286,6 @@ public abstract class AnnotationUtils {
AnnotationsScanner.clearCache(); AnnotationsScanner.clearCache();
} }
private static <A extends Annotation> boolean isSingleLevelPresent(
MergedAnnotation<A> mergedAnnotation) {
int depth = mergedAnnotation.getDepth();
return depth == 0 || depth == 1;
}
/** /**
* Internal holder used to wrap default values. * Internal holder used to wrap default values.
@ -1280,7 +1302,6 @@ public abstract class AnnotationUtils {
public String toString() { public String toString() {
return "*" + this.defaultValue; return "*" + this.defaultValue;
} }
} }
} }

View File

@ -51,6 +51,7 @@ import org.springframework.lang.Nullable;
* synthesized} back into an actual {@link java.lang.annotation.Annotation}. * synthesized} back into an actual {@link java.lang.annotation.Annotation}.
* *
* @author Phillip Webb * @author Phillip Webb
* @author Juergen Hoeller
* @since 5.2 * @since 5.2
* @param <A> the annotation type * @param <A> the annotation type
* @see MergedAnnotations * @see MergedAnnotations
@ -65,10 +66,10 @@ public interface MergedAnnotation<A extends Annotation> {
/** /**
* Return the class name of the actual annotation type. * Return the {@code Class} reference for the actual annotation type.
* @return the annotation type * @return the annotation type
*/ */
String getType(); Class<A> getType();
/** /**
* Return if the annotation is present on the source. Considers * Return if the annotation is present on the source. Considers
@ -323,8 +324,7 @@ public interface MergedAnnotation<A extends Annotation> {
* @return the value as a enum * @return the value as a enum
* @throws NoSuchElementException if there is no matching attribute * @throws NoSuchElementException if there is no matching attribute
*/ */
<E extends Enum<E>> E getEnum(String attributeName, Class<E> type) <E extends Enum<E>> E getEnum(String attributeName, Class<E> type) throws NoSuchElementException;
throws NoSuchElementException;
/** /**
* Return a required enum array attribute value from the annotation. * Return a required enum array attribute value from the annotation.
@ -333,8 +333,7 @@ public interface MergedAnnotation<A extends Annotation> {
* @return the value as a enum array * @return the value as a enum array
* @throws NoSuchElementException if there is no matching attribute * @throws NoSuchElementException if there is no matching attribute
*/ */
<E extends Enum<E>> E[] getEnumArray(String attributeName, Class<E> type) <E extends Enum<E>> E[] getEnumArray(String attributeName, Class<E> type) throws NoSuchElementException;
throws NoSuchElementException;
/** /**
* Return a required annotation attribute value from the annotation. * Return a required annotation attribute value from the annotation.
@ -353,8 +352,8 @@ public interface MergedAnnotation<A extends Annotation> {
* @return the value as a {@link MergedAnnotation} array * @return the value as a {@link MergedAnnotation} array
* @throws NoSuchElementException if there is no matching attribute * @throws NoSuchElementException if there is no matching attribute
*/ */
<T extends Annotation> MergedAnnotation<T>[] getAnnotationArray(String attributeName, <T extends Annotation> MergedAnnotation<T>[] getAnnotationArray(String attributeName, Class<T> type)
Class<T> type) throws NoSuchElementException; throws NoSuchElementException;
/** /**
* Return an optional attribute value from the annotation. * Return an optional attribute value from the annotation.
@ -422,24 +421,21 @@ public interface MergedAnnotation<A extends Annotation> {
MergedAnnotation<A> withNonMergedAttributes(); MergedAnnotation<A> withNonMergedAttributes();
/** /**
* Return an immutable {@link Map} that contains all the annotation * Return an immutable {@link Map} that contains all the annotation attributes.
* attributes. The {@link MapValues} options may be used to change the way * The {@link MapValues} options may be used to change the way that values are added.
* that values are added.
* @param options map value options * @param options map value options
* @return a map containing the attributes and values * @return an immutable map containing the attributes and values
*/ */
Map<String, Object> asMap(MapValues... options); Map<String, Object> asMap(MapValues... options);
/** /**
* Return a {@link Map} of the supplied type that contains all the annotation * Return a {@link Map} of the given type that contains all the annotation attributes.
* attributes. The {@link MapValues} options may be used to change the way * The {@link MapValues} options may be used to change the way that values are added.
* that values are added. * @param factory a map factory
* @param factory a map factory or {@code null} to return an immutable map.
* @param options map value options * @param options map value options
* @return a map containing the attributes and values * @return a map containing the attributes and values
*/ */
<T extends Map<String, Object>> T asMap( <T extends Map<String, Object>> T asMap(Function<MergedAnnotation<?>, T> factory, MapValues... options);
@Nullable Function<MergedAnnotation<?>, T> factory, MapValues... options);
/** /**
* Return a type-safe synthesized version of this annotation that can be * Return a type-safe synthesized version of this annotation that can be
@ -460,8 +456,7 @@ public interface MergedAnnotation<A extends Annotation> {
* @throws NoSuchElementException on a missing annotation * @throws NoSuchElementException on a missing annotation
* @see MergedAnnotationPredicates * @see MergedAnnotationPredicates
*/ */
Optional<A> synthesize(@Nullable Predicate<? super MergedAnnotation<A>> condition) Optional<A> synthesize(Predicate<? super MergedAnnotation<A>> condition) throws NoSuchElementException;
throws NoSuchElementException;
/** /**
@ -511,10 +506,9 @@ public interface MergedAnnotation<A extends Annotation> {
* Create a new {@link MergedAnnotations} instance from the specified * Create a new {@link MergedAnnotations} instance from the specified
* annotation type and attributes maps. * annotation type and attributes maps.
* @param annotationType the annotation type * @param annotationType the annotation type
* @param attributes the annotation attributes or {@code null} if just * @param attributes the annotation attributes or {@code null} if just default
* default values should be used * values should be used
* @return a {@link MergedAnnotation} instance for the annotation and * @return a {@link MergedAnnotation} instance for the annotation and attributes
* attributes
* @see #from(AnnotatedElement, Class, Map) * @see #from(AnnotatedElement, Class, Map)
*/ */
static <A extends Annotation> MergedAnnotation<A> from( static <A extends Annotation> MergedAnnotation<A> from(
@ -530,10 +524,9 @@ public interface MergedAnnotation<A extends Annotation> {
* information and logging. It does not need to <em>actually</em> contain * information and logging. It does not need to <em>actually</em> contain
* the specified annotations and it will not be searched. * the specified annotations and it will not be searched.
* @param annotationType the annotation type * @param annotationType the annotation type
* @param attributes the annotation attributes or {@code null} if just * @param attributes the annotation attributes or {@code null} if just default
* default values should be used * values should be used
* @return a {@link MergedAnnotation} instance for the annotation and * @return a {@link MergedAnnotation} instance for the annotation and attributes
* attributes
*/ */
static <A extends Annotation> MergedAnnotation<A> from( static <A extends Annotation> MergedAnnotation<A> from(
@Nullable AnnotatedElement source, Class<A> annotationType, @Nullable Map<String, ?> attributes) { @Nullable AnnotatedElement source, Class<A> annotationType, @Nullable Map<String, ?> attributes) {

View File

@ -17,7 +17,6 @@
package org.springframework.core.annotation; package org.springframework.core.annotation;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
@ -50,7 +49,7 @@ public abstract class MergedAnnotationPredicates {
* @return a {@link Predicate} to test the annotation type * @return a {@link Predicate} to test the annotation type
*/ */
public static <A extends Annotation> Predicate<MergedAnnotation<? extends A>> typeIn(String... typeNames) { public static <A extends Annotation> Predicate<MergedAnnotation<? extends A>> typeIn(String... typeNames) {
return annotation -> ObjectUtils.containsElement(typeNames, annotation.getType()); return annotation -> ObjectUtils.containsElement(typeNames, annotation.getType().getName());
} }
/** /**
@ -62,7 +61,7 @@ public abstract class MergedAnnotationPredicates {
* @return a {@link Predicate} to test the annotation type * @return a {@link Predicate} to test the annotation type
*/ */
public static <A extends Annotation> Predicate<MergedAnnotation<? extends A>> typeIn(Class<?>... types) { public static <A extends Annotation> Predicate<MergedAnnotation<? extends A>> typeIn(Class<?>... types) {
return annotation -> Arrays.stream(types).anyMatch(type -> type.getName().equals(annotation.getType())); return annotation -> ObjectUtils.containsElement(types, annotation.getType());
} }
/** /**
@ -76,7 +75,7 @@ public abstract class MergedAnnotationPredicates {
public static <A extends Annotation> Predicate<MergedAnnotation<? extends A>> typeIn(Collection<?> types) { public static <A extends Annotation> Predicate<MergedAnnotation<? extends A>> typeIn(Collection<?> types) {
return annotation -> types.stream() return annotation -> types.stream()
.map(type -> type instanceof Class ? ((Class<?>) type).getName() : type.toString()) .map(type -> type instanceof Class ? ((Class<?>) type).getName() : type.toString())
.anyMatch(typeName -> typeName.equals(annotation.getType())); .anyMatch(typeName -> typeName.equals(annotation.getType().getName()));
} }
/** /**
@ -141,7 +140,6 @@ public abstract class MergedAnnotationPredicates {
return ObjectUtils.nullSafeEquals(value, this.lastValue); return ObjectUtils.nullSafeEquals(value, this.lastValue);
} }
} }
@ -165,7 +163,6 @@ public abstract class MergedAnnotationPredicates {
K key = this.keyExtractor.apply(annotation); K key = this.keyExtractor.apply(annotation);
return this.seen.add(key); return this.seen.add(key);
} }
} }
} }

View File

@ -31,6 +31,7 @@ import org.springframework.lang.Nullable;
* {@link MergedAnnotation#missing()}. * {@link MergedAnnotation#missing()}.
* *
* @author Phillip Webb * @author Phillip Webb
* @author Juergen Hoeller
* @since 5.2 * @since 5.2
* @param <A> the annotation type * @param <A> the annotation type
*/ */
@ -44,7 +45,7 @@ final class MissingMergedAnnotation<A extends Annotation> extends AbstractMerged
@Override @Override
public String getType() { public Class<A> getType() {
throw new NoSuchElementException("Unable to get type for missing annotation"); throw new NoSuchElementException("Unable to get type for missing annotation");
} }
@ -112,13 +113,8 @@ final class MissingMergedAnnotation<A extends Annotation> extends AbstractMerged
} }
@Override @Override
@SuppressWarnings({ "unchecked", "rawtypes" }) public <T extends Map<String, Object>> T asMap(Function<MergedAnnotation<?>, T> factory, MapValues... options) {
public <T extends Map<String, Object>> T asMap( return factory.apply(this);
@Nullable Function<MergedAnnotation<?>, T> factory, MapValues... options) {
if (factory != null) {
return factory.apply(this);
}
return (T) ((Map) Collections.emptyMap());
} }
@Override @Override

View File

@ -176,7 +176,7 @@ final class SynthesizedMergedAnnotationInvocationHandler<A extends Annotation> i
Class<?> type = ClassUtils.resolvePrimitiveIfNecessary(method.getReturnType()); Class<?> type = ClassUtils.resolvePrimitiveIfNecessary(method.getReturnType());
return this.annotation.getValue(name, type).orElseThrow( return this.annotation.getValue(name, type).orElseThrow(
() -> new NoSuchElementException("No value found for attribute named '" + name + () -> new NoSuchElementException("No value found for attribute named '" + name +
"' in merged annotation " + this.annotation.getType())); "' in merged annotation " + this.annotation.getType().getName()));
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")

View File

@ -49,7 +49,7 @@ import org.springframework.util.ReflectionUtils;
* return type, namely: * return type, namely:
* *
* <p><table border="1"> * <p><table border="1">
* <tr><th >Return Type</th><th >Extracted Type</th></tr> * <tr><th>Return Type</th><th >Extracted Type</th></tr>
* <tr><td>Class</td><td>Class or String</td></tr> * <tr><td>Class</td><td>Class or String</td></tr>
* <tr><td>Class[]</td><td>Class[] or String[]</td></tr> * <tr><td>Class[]</td><td>Class[] or String[]</td></tr>
* <tr><td>Annotation</td><td>Annotation, Map or Object compatible with the value * <tr><td>Annotation</td><td>Annotation, Map or Object compatible with the value
@ -60,6 +60,7 @@ import org.springframework.util.ReflectionUtils;
* </table> * </table>
* *
* @author Phillip Webb * @author Phillip Webb
* @author Juergen Hoeller
* @since 5.2 * @since 5.2
* @param <A> the annotation type * @param <A> the annotation type
* @see TypeMappedAnnotations * @see TypeMappedAnnotations
@ -91,17 +92,16 @@ final class TypeMappedAnnotation<A extends Annotation> extends AbstractMergedAnn
private String string; private String string;
private TypeMappedAnnotation(AnnotationTypeMapping mapping, private TypeMappedAnnotation(AnnotationTypeMapping mapping, @Nullable Object source,
@Nullable Object source, @Nullable Object rootAttributes, @Nullable Object rootAttributes, BiFunction<Method, Object, Object> valueExtractor,
BiFunction<Method, Object, Object> valueExtractor, int aggregateIndex) { int aggregateIndex) {
this(mapping, source, rootAttributes, valueExtractor, aggregateIndex, null); this(mapping, source, rootAttributes, valueExtractor, aggregateIndex, null);
} }
private TypeMappedAnnotation(AnnotationTypeMapping mapping, private TypeMappedAnnotation(AnnotationTypeMapping mapping, @Nullable Object source,
@Nullable Object source, @Nullable Object rootAttributes, @Nullable Object rootAttributes, BiFunction<Method, Object, Object> valueExtractor,
BiFunction<Method, Object, Object> valueExtractor, int aggregateIndex, int aggregateIndex, @Nullable int[] resolvedRootMirrors) {
@Nullable int[] resolvedRootMirrors) {
this.source = source; this.source = source;
this.rootAttributes = rootAttributes; this.rootAttributes = rootAttributes;
@ -110,18 +110,15 @@ final class TypeMappedAnnotation<A extends Annotation> extends AbstractMergedAnn
this.aggregateIndex = aggregateIndex; this.aggregateIndex = aggregateIndex;
this.useMergedValues = true; this.useMergedValues = true;
this.attributeFilter = null; this.attributeFilter = null;
this.resolvedRootMirrors = resolvedRootMirrors != null ? resolvedRootMirrors this.resolvedRootMirrors = (resolvedRootMirrors != null ? resolvedRootMirrors :
: mapping.getRoot().getMirrorSets().resolve(source, rootAttributes, mapping.getRoot().getMirrorSets().resolve(source, rootAttributes, this.valueExtractor));
this.valueExtractor); this.resolvedMirrors = (getDepth() == 0 ? this.resolvedRootMirrors :
this.resolvedMirrors = getDepth() == 0 ? this.resolvedRootMirrors mapping.getMirrorSets().resolve(source, this, this::getValueForMirrorResolution));
: mapping.getMirrorSets().resolve(source, this,
this::getValueForMirrorResolution);
} }
private TypeMappedAnnotation(AnnotationTypeMapping mapping, private TypeMappedAnnotation(AnnotationTypeMapping mapping, @Nullable Object source,
@Nullable Object source, @Nullable Object rootAnnotation, @Nullable Object rootAnnotation, BiFunction<Method, Object, Object> valueExtractor,
BiFunction<Method, Object, Object> valueExtractor, int aggregateIndex, int aggregateIndex, boolean useMergedValues, @Nullable Predicate<String> attributeFilter,
boolean useMergedValues, @Nullable Predicate<String> attributeFilter,
int[] resolvedRootMirrors, int[] resolvedMirrors) { int[] resolvedRootMirrors, int[] resolvedMirrors) {
this.source = source; this.source = source;
@ -136,16 +133,10 @@ final class TypeMappedAnnotation<A extends Annotation> extends AbstractMergedAnn
} }
@Nullable
private Object getValueForMirrorResolution(Method attribute, Object annotation) {
int attributeIndex = this.mapping.getAttributes().indexOf(attribute);
boolean valueAttribute = VALUE.equals(attribute.getName());
return getValue(attributeIndex, !valueAttribute, true);
}
@Override @Override
public String getType() { @SuppressWarnings("unchecked")
return getAnnotationType().getName(); public Class<A> getType() {
return (Class<A>) this.mapping.getAnnotationType();
} }
@Override @Override
@ -184,8 +175,7 @@ final class TypeMappedAnnotation<A extends Annotation> extends AbstractMergedAnn
public boolean hasDefaultValue(String attributeName) { public boolean hasDefaultValue(String attributeName) {
int attributeIndex = getAttributeIndex(attributeName, true); int attributeIndex = getAttributeIndex(attributeName, true);
Object value = getValue(attributeIndex, true, false); Object value = getValue(attributeIndex, true, false);
return value == null || this.mapping.isEquivalentToDefaultValue(attributeIndex, value, return (value == null || this.mapping.isEquivalentToDefaultValue(attributeIndex, value, this.valueExtractor));
this.valueExtractor);
} }
@Override @Override
@ -197,8 +187,8 @@ final class TypeMappedAnnotation<A extends Annotation> extends AbstractMergedAnn
Method attribute = this.mapping.getAttributes().get(attributeIndex); Method attribute = this.mapping.getAttributes().get(attributeIndex);
Assert.notNull(type, "Type must not be null"); Assert.notNull(type, "Type must not be null");
Assert.isAssignable(type, attribute.getReturnType(), Assert.isAssignable(type, attribute.getReturnType(),
"Attribute " + attributeName + " type mismatch:"); () -> "Attribute " + attributeName + " type mismatch:");
return (MergedAnnotation<T>) getRequiredValue(attributeIndex, Object.class); return (MergedAnnotation<T>) getRequiredValue(attributeIndex);
} }
@Override @Override
@ -212,7 +202,7 @@ final class TypeMappedAnnotation<A extends Annotation> extends AbstractMergedAnn
Assert.notNull(type, "Type must not be null"); Assert.notNull(type, "Type must not be null");
Assert.notNull(componentType, () -> "Attribute " + attributeName + " is not an array"); Assert.notNull(componentType, () -> "Attribute " + attributeName + " is not an array");
Assert.isAssignable(type, componentType, () -> "Attribute " + attributeName + " component type mismatch:"); Assert.isAssignable(type, componentType, () -> "Attribute " + attributeName + " component type mismatch:");
return (MergedAnnotation<T>[]) getRequiredValue(attributeIndex, Object.class); return (MergedAnnotation<T>[]) getRequiredValue(attributeIndex);
} }
@Override @Override
@ -243,55 +233,48 @@ final class TypeMappedAnnotation<A extends Annotation> extends AbstractMergedAnn
} }
@Override @Override
@SuppressWarnings("unchecked") public Map<String, Object> asMap(MapValues... options) {
public <T extends Map<String, Object>> T asMap( return Collections.unmodifiableMap(asMap(mergedAnnotation -> new LinkedHashMap<>(), options));
@Nullable Function<MergedAnnotation<?>, T> factory, MapValues... options) { }
T map = (factory != null ? factory.apply(this) : (T) new LinkedHashMap<String, Object>()); @Override
public <T extends Map<String, Object>> T asMap(Function<MergedAnnotation<?>, T> factory, MapValues... options) {
T map = factory.apply(this);
Assert.state(map != null, "Factory used to create MergedAnnotation Map must not return null"); Assert.state(map != null, "Factory used to create MergedAnnotation Map must not return null");
AttributeMethods attributes = this.mapping.getAttributes(); AttributeMethods attributes = this.mapping.getAttributes();
for (int i = 0; i < attributes.size(); i++) { for (int i = 0; i < attributes.size(); i++) {
Method attribute = attributes.get(i); Method attribute = attributes.get(i);
Object value = isFiltered(attribute.getName()) ? Object value = (isFiltered(attribute.getName()) ? null :
null : getValue(i, getTypeForMapOptions(attribute, options)));
getValue(i, getTypeForMapOptions(attribute, options));
if (value != null) { if (value != null) {
map.put(attribute.getName(), map.put(attribute.getName(),
adaptValueForMapOptions(attribute, value, factory, options)); adaptValueForMapOptions(attribute, value, map.getClass(), factory, options));
} }
} }
return (factory != null) ? map : (T) Collections.unmodifiableMap(map); return map;
} }
private Class<?> getTypeForMapOptions(Method attribute, MapValues[] options) { private Class<?> getTypeForMapOptions(Method attribute, MapValues[] options) {
Class<?> attributeType = attribute.getReturnType(); Class<?> attributeType = attribute.getReturnType();
Class<?> componentType = attributeType.isArray() ? Class<?> componentType = (attributeType.isArray() ? attributeType.getComponentType() : attributeType);
attributeType.getComponentType() :
attributeType;
if (MapValues.CLASS_TO_STRING.isIn(options) && componentType == Class.class) { if (MapValues.CLASS_TO_STRING.isIn(options) && componentType == Class.class) {
return attributeType.isArray() ? String[].class : String.class; return (attributeType.isArray() ? String[].class : String.class);
} }
return Object.class; return Object.class;
} }
private <T extends Map<String, Object>> Object adaptValueForMapOptions( private <T extends Map<String, Object>> Object adaptValueForMapOptions(Method attribute, Object value,
Method attribute, Object value, Class<?> mapType, Function<MergedAnnotation<?>, T> factory, MapValues[] options) {
@Nullable Function<MergedAnnotation<?>, T> factory, MapValues[] options) {
if (value instanceof MergedAnnotation) { if (value instanceof MergedAnnotation) {
MergedAnnotation<?> annotation = (MergedAnnotation<?>) value; MergedAnnotation<?> annotation = (MergedAnnotation<?>) value;
return MapValues.ANNOTATION_TO_MAP.isIn(options) ? return (MapValues.ANNOTATION_TO_MAP.isIn(options) ?
annotation.asMap(factory, options) : annotation.asMap(factory, options) : annotation.synthesize());
annotation.synthesize();
} }
if (value instanceof MergedAnnotation[]) { if (value instanceof MergedAnnotation[]) {
MergedAnnotation<?>[] annotations = (MergedAnnotation<?>[]) value; MergedAnnotation<?>[] annotations = (MergedAnnotation<?>[]) value;
if (MapValues.ANNOTATION_TO_MAP.isIn(options)) { if (MapValues.ANNOTATION_TO_MAP.isIn(options)) {
Class<?> componentType = Map.class; Object result = Array.newInstance(mapType, annotations.length);
if (factory != null) {
componentType = factory.apply(this).getClass();
}
Object result = Array.newInstance(componentType, annotations.length);
for (int i = 0; i < annotations.length; i++) { for (int i = 0; i < annotations.length; i++) {
Array.set(result, i, annotations[i].asMap(factory, options)); Array.set(result, i, annotations[i].asMap(factory, options));
} }
@ -309,7 +292,7 @@ final class TypeMappedAnnotation<A extends Annotation> extends AbstractMergedAnn
@Override @Override
protected A createSynthesized() { protected A createSynthesized() {
return SynthesizedMergedAnnotationInvocationHandler.createProxy(this, getAnnotationType()); return SynthesizedMergedAnnotationInvocationHandler.createProxy(this, getType());
} }
@Override @Override
@ -318,7 +301,7 @@ final class TypeMappedAnnotation<A extends Annotation> extends AbstractMergedAnn
if (string == null) { if (string == null) {
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
builder.append("@"); builder.append("@");
builder.append(getType()); builder.append(getType().getName());
builder.append("("); builder.append("(");
for (int i = 0; i < this.mapping.getAttributes().size(); i++) { for (int i = 0; i < this.mapping.getAttributes().size(); i++) {
Method attribute = this.mapping.getAttributes().get(i); Method attribute = this.mapping.getAttributes().get(i);
@ -360,8 +343,8 @@ final class TypeMappedAnnotation<A extends Annotation> extends AbstractMergedAnn
return (attributeIndex != -1 ? getValue(attributeIndex, type) : null); return (attributeIndex != -1 ? getValue(attributeIndex, type) : null);
} }
protected final <T> T getRequiredValue(int attributeIndex, Class<T> type) { private final Object getRequiredValue(int attributeIndex) {
T value = getValue(attributeIndex, type); Object value = getValue(attributeIndex, Object.class);
if (value == null) { if (value == null) {
throw new NoSuchElementException("No element at attribute index " + attributeIndex); throw new NoSuchElementException("No element at attribute index " + attributeIndex);
} }
@ -379,9 +362,7 @@ final class TypeMappedAnnotation<A extends Annotation> extends AbstractMergedAnn
} }
@Nullable @Nullable
private Object getValue(int attributeIndex, boolean useConventionMapping, private Object getValue(int attributeIndex, boolean useConventionMapping, boolean forMirrorResolution) {
boolean forMirrorResolution) {
AnnotationTypeMapping mapping = this.mapping; AnnotationTypeMapping mapping = this.mapping;
if (this.useMergedValues) { if (this.useMergedValues) {
int mappedIndex = this.mapping.getAliasMapping(attributeIndex); int mappedIndex = this.mapping.getAliasMapping(attributeIndex);
@ -419,6 +400,13 @@ final class TypeMappedAnnotation<A extends Annotation> extends AbstractMergedAnn
return ReflectionUtils.invokeMethod(attribute, this.mapping.getAnnotation()); return ReflectionUtils.invokeMethod(attribute, this.mapping.getAnnotation());
} }
@Nullable
private Object getValueForMirrorResolution(Method attribute, Object annotation) {
int attributeIndex = this.mapping.getAttributes().indexOf(attribute);
boolean valueAttribute = VALUE.equals(attribute.getName());
return getValue(attributeIndex, !valueAttribute, true);
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Nullable @Nullable
private <T> T adapt(Method attribute, @Nullable Object value, Class<T> type) { private <T> T adapt(Method attribute, @Nullable Object value, Class<T> type) {
@ -485,21 +473,16 @@ final class TypeMappedAnnotation<A extends Annotation> extends AbstractMergedAnn
} }
if (!attributeType.isInstance(value)) { if (!attributeType.isInstance(value)) {
throw new IllegalStateException("Attribute '" + attribute.getName() + throw new IllegalStateException("Attribute '" + attribute.getName() +
"' in annotation " + getType() + " should be compatible with " + "' in annotation " + getType().getName() + " should be compatible with " +
attributeType.getName() + " but a " + value.getClass().getName() + attributeType.getName() + " but a " + value.getClass().getName() +
" value was returned"); " value was returned");
} }
return value; return value;
} }
private MergedAnnotation<?> adaptToMergedAnnotation(Object value, private MergedAnnotation<?> adaptToMergedAnnotation(Object value, Class<? extends Annotation> annotationType) {
Class<? extends Annotation> annotationType) { AnnotationTypeMapping mapping = AnnotationTypeMappings.forAnnotationType(annotationType).get(0);
return new TypeMappedAnnotation<>(mapping, this.source, value, getValueExtractor(value), this.aggregateIndex);
AnnotationFilter filter = AnnotationFilter.mostAppropriateFor(annotationType);
AnnotationTypeMapping mapping = AnnotationTypeMappings.forAnnotationType(
annotationType, filter).get(0);
return new TypeMappedAnnotation<>(mapping, this.source, value,
this.getValueExtractor(value), this.aggregateIndex);
} }
private BiFunction<Method, Object, Object> getValueExtractor(Object value) { private BiFunction<Method, Object, Object> getValueExtractor(Object value) {
@ -529,7 +512,7 @@ final class TypeMappedAnnotation<A extends Annotation> extends AbstractMergedAnn
this.mapping.getAttributes().indexOf(attributeName)); this.mapping.getAttributes().indexOf(attributeName));
if (attributeIndex == -1 && required) { if (attributeIndex == -1 && required) {
throw new NoSuchElementException("No attribute named '" + attributeName + throw new NoSuchElementException("No attribute named '" + attributeName +
"' present in merged annotation " + getType()); "' present in merged annotation " + getType().getName());
} }
return attributeIndex; return attributeIndex;
} }
@ -541,10 +524,6 @@ final class TypeMappedAnnotation<A extends Annotation> extends AbstractMergedAnn
return false; return false;
} }
@SuppressWarnings("unchecked")
private Class<A> getAnnotationType() {
return (Class<A>) this.mapping.getAnnotationType();
}
static <A extends Annotation> MergedAnnotation<A> from(@Nullable Object source, A annotation) { static <A extends Annotation> MergedAnnotation<A> from(@Nullable Object source, A annotation) {
Assert.notNull(annotation, "Annotation must not be null"); Assert.notNull(annotation, "Annotation must not be null");
@ -557,8 +536,8 @@ final class TypeMappedAnnotation<A extends Annotation> extends AbstractMergedAnn
Assert.notNull(annotationType, "Annotation type must not be null"); Assert.notNull(annotationType, "Annotation type must not be null");
AnnotationTypeMappings mappings = AnnotationTypeMappings.forAnnotationType(annotationType); AnnotationTypeMappings mappings = AnnotationTypeMappings.forAnnotationType(annotationType);
return new TypeMappedAnnotation<>(mappings.get(0), source, attributes, return new TypeMappedAnnotation<>(
TypeMappedAnnotation::extractFromMap, 0); mappings.get(0), source, attributes, TypeMappedAnnotation::extractFromMap, 0);
} }
@Nullable @Nullable

View File

@ -23,7 +23,9 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
import java.lang.reflect.AnnotatedElement; import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import javax.annotation.Resource; import javax.annotation.Resource;
@ -34,9 +36,9 @@ import org.junit.Test;
import org.junit.internal.ArrayComparisonFailure; import org.junit.internal.ArrayComparisonFailure;
import org.junit.rules.ExpectedException; import org.junit.rules.ExpectedException;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.stereotype.Indexed; import org.springframework.stereotype.Indexed;
import org.springframework.util.Assert;
import org.springframework.util.MultiValueMap; import org.springframework.util.MultiValueMap;
import static java.util.Arrays.*; import static java.util.Arrays.*;
@ -631,7 +633,6 @@ public class AnnotatedElementUtilsTests {
} }
private AnnotationAttributes findMergedAnnotationAttributes(AnnotatedElement element, Class<? extends Annotation> annotationType) { private AnnotationAttributes findMergedAnnotationAttributes(AnnotatedElement element, Class<? extends Annotation> annotationType) {
Assert.notNull(annotationType, "annotationType must not be null");
return AnnotatedElementUtils.findMergedAnnotationAttributes(element, annotationType.getName(), false, false); return AnnotatedElementUtils.findMergedAnnotationAttributes(element, annotationType.getName(), false, false);
} }
@ -699,6 +700,15 @@ public class AnnotatedElementUtilsTests {
assertArrayEquals("path attribute: ", asArray("/test"), webMapping.path()); assertArrayEquals("path attribute: ", asArray("/test"), webMapping.path());
} }
@Test
public void javaLangAnnotationTypeViaFindMergedAnnotation() throws Exception {
Constructor<?> deprecatedCtor = Date.class.getConstructor(String.class);
assertEquals(deprecatedCtor.getAnnotation(Deprecated.class),
findMergedAnnotation(deprecatedCtor, Deprecated.class));
assertEquals(Date.class.getAnnotation(Deprecated.class),
findMergedAnnotation(Date.class, Deprecated.class));
}
@Test @Test
public void javaxAnnotationTypeViaFindMergedAnnotation() throws Exception { public void javaxAnnotationTypeViaFindMergedAnnotation() throws Exception {
assertEquals(ResourceHolder.class.getAnnotation(Resource.class), assertEquals(ResourceHolder.class.getAnnotation(Resource.class),
@ -707,17 +717,26 @@ public class AnnotatedElementUtilsTests {
findMergedAnnotation(SpringAppConfigClass.class, Resource.class)); findMergedAnnotation(SpringAppConfigClass.class, Resource.class));
} }
@Test
public void nullableAnnotationTypeViaFindMergedAnnotation() throws Exception {
Method method = TransactionalServiceImpl.class.getMethod("doIt");
assertEquals(method.getAnnotation(Resource.class),
findMergedAnnotation(method, Resource.class));
assertEquals(method.getAnnotation(Resource.class),
findMergedAnnotation(method, Resource.class));
}
@Test @Test
public void getAllMergedAnnotationsOnClassWithInterface() throws Exception { public void getAllMergedAnnotationsOnClassWithInterface() throws Exception {
Method m = TransactionalServiceImpl.class.getMethod("doIt"); Method method = TransactionalServiceImpl.class.getMethod("doIt");
Set<Transactional> allMergedAnnotations = getAllMergedAnnotations(m, Transactional.class); Set<Transactional> allMergedAnnotations = getAllMergedAnnotations(method, Transactional.class);
assertTrue(allMergedAnnotations.isEmpty()); assertTrue(allMergedAnnotations.isEmpty());
} }
@Test @Test
public void findAllMergedAnnotationsOnClassWithInterface() throws Exception { public void findAllMergedAnnotationsOnClassWithInterface() throws Exception {
Method m = TransactionalServiceImpl.class.getMethod("doIt"); Method method = TransactionalServiceImpl.class.getMethod("doIt");
Set<Transactional> allMergedAnnotations = findAllMergedAnnotations(m, Transactional.class); Set<Transactional> allMergedAnnotations = findAllMergedAnnotations(method, Transactional.class);
assertEquals(1, allMergedAnnotations.size()); assertEquals(1, allMergedAnnotations.size());
} }
@ -1299,20 +1318,22 @@ public class AnnotatedElementUtilsTests {
interface TransactionalService { interface TransactionalService {
@Transactional @Transactional
void doIt(); @Nullable
Object doIt();
} }
class TransactionalServiceImpl implements TransactionalService { class TransactionalServiceImpl implements TransactionalService {
@Override @Override
public void doIt() { @Nullable
public Object doIt() {
return null;
} }
} }
@Deprecated @Deprecated
@ComponentScan @ComponentScan
class ForAnnotationsClass { class ForAnnotationsClass {
} }
} }

View File

@ -99,39 +99,6 @@ public class AnnotationFilterTests {
assertThat(AnnotationFilter.NONE.matches(TestAnnotation.class)).isFalse(); assertThat(AnnotationFilter.NONE.matches(TestAnnotation.class)).isFalse();
} }
@Test
public void pacakgesReturnsPackagesAnnotationFilter() {
assertThat(AnnotationFilter.packages("com.example")).isInstanceOf(PackagesAnnotationFilter.class);
}
@Test
public void mostAppropriateForCollectionReturnsPlainWhenPossible() {
AnnotationFilter filter = AnnotationFilter.mostAppropriateFor(
Arrays.asList(TestAnnotation.class, OtherAnnotation.class));
assertThat(filter).isSameAs(AnnotationFilter.PLAIN);
}
@Test
public void mostAppropriateForCollectionWhenCantUsePlainReturnsNone() {
AnnotationFilter filter = AnnotationFilter.mostAppropriateFor(Arrays.asList(
TestAnnotation.class, OtherAnnotation.class, Nullable.class));
assertThat(filter).isSameAs(AnnotationFilter.NONE);
}
@Test
public void mostAppropriateForArrayReturnsPlainWhenPossible() {
AnnotationFilter filter = AnnotationFilter.mostAppropriateFor(
TestAnnotation.class, OtherAnnotation.class);
assertThat(filter).isSameAs(AnnotationFilter.PLAIN);
}
@Test
public void mostAppropriateForArrayWhenCantUsePlainReturnsNone() {
AnnotationFilter filter = AnnotationFilter.mostAppropriateFor(
TestAnnotation.class, OtherAnnotation.class, Nullable.class);
assertThat(filter).isSameAs(AnnotationFilter.NONE);
}
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@interface TestAnnotation { @interface TestAnnotation {

View File

@ -17,6 +17,7 @@
package org.springframework.core.annotation; package org.springframework.core.annotation;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.Inherited; import java.lang.annotation.Inherited;
import java.lang.annotation.Repeatable; import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
@ -36,7 +37,7 @@ import org.junit.rules.ExpectedException;
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
import org.springframework.core.annotation.subpackage.NonPublicAnnotatedClass; import org.springframework.core.annotation.subpackage.NonPublicAnnotatedClass;
import org.springframework.lang.Nullable; import org.springframework.lang.NonNullApi;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import static java.util.Arrays.*; import static java.util.Arrays.*;
@ -55,6 +56,7 @@ import static org.springframework.core.annotation.AnnotationUtils.*;
* @author Phillip Webb * @author Phillip Webb
* @author Oleg Zhurakousky * @author Oleg Zhurakousky
*/ */
@SuppressWarnings("deprecation")
public class AnnotationUtilsTests { public class AnnotationUtilsTests {
@Rule @Rule
@ -396,7 +398,7 @@ public class AnnotationUtilsTests {
} }
@Test @Test
public void isAnnotationDeclaredLocallyForAllScenarios() throws Exception { public void isAnnotationDeclaredLocallyForAllScenarios() {
// no class-level annotation // no class-level annotation
assertFalse(isAnnotationDeclaredLocally(Transactional.class, NonAnnotatedInterface.class)); assertFalse(isAnnotationDeclaredLocally(Transactional.class, NonAnnotatedInterface.class));
assertFalse(isAnnotationDeclaredLocally(Transactional.class, NonAnnotatedClass.class)); assertFalse(isAnnotationDeclaredLocally(Transactional.class, NonAnnotatedClass.class));
@ -435,6 +437,12 @@ public class AnnotationUtilsTests {
assertFalse(isAnnotationInherited(Order.class, SubNonInheritedAnnotationClass.class)); assertFalse(isAnnotationInherited(Order.class, SubNonInheritedAnnotationClass.class));
} }
@Test
public void isAnnotationMetaPresentForJavaLangType() {
assertTrue(isAnnotationMetaPresent(Order.class, Documented.class));
assertTrue(isAnnotationMetaPresent(NonNullApi.class, Documented.class));
}
@Test @Test
public void getAnnotationAttributesWithoutAttributeAliases() { public void getAnnotationAttributesWithoutAttributeAliases() {
Component component = WebController.class.getAnnotation(Component.class); Component component = WebController.class.getAnnotation(Component.class);
@ -540,6 +548,13 @@ public class AnnotationUtilsTests {
assertEquals(Ordered.LOWEST_PRECEDENCE, getDefaultValue(Order.class)); assertEquals(Ordered.LOWEST_PRECEDENCE, getDefaultValue(Order.class));
} }
@Test
public void findRepeatableAnnotation() {
Repeatable repeatable = findAnnotation(MyRepeatable.class, Repeatable.class);
assertNotNull(repeatable);
assertEquals(MyRepeatableContainer.class, repeatable.value());
}
@Test @Test
public void getRepeatableAnnotationsDeclaredOnMethod() throws Exception { public void getRepeatableAnnotationsDeclaredOnMethod() throws Exception {
Method method = InterfaceWithRepeated.class.getMethod("foo"); Method method = InterfaceWithRepeated.class.getMethod("foo");
@ -924,10 +939,8 @@ public class AnnotationUtilsTests {
exception.expect(IllegalArgumentException.class); exception.expect(IllegalArgumentException.class);
exception.expectMessage(either(allOf(startsWith("Attributes map"), exception.expectMessage(either(allOf(startsWith("Attributes map"),
containsString("returned null for required attribute 'text'"), containsString("returned null for required attribute 'text'"),
containsString("defined by annotation type [" containsString("defined by annotation type [" + AnnotationWithoutDefaults.class.getName() + "]"))).or(
+ AnnotationWithoutDefaults.class.getName() + "]"))).or( containsString("No value found for attribute named 'text' in merged annotation")));
containsString(
"No value found for attribute named 'text' in merged annotation")));
synthesizeAnnotation(attributes, AnnotationWithoutDefaults.class, null); synthesizeAnnotation(attributes, AnnotationWithoutDefaults.class, null);
} }
@ -936,8 +949,8 @@ public class AnnotationUtilsTests {
Map<String, Object> map = Collections.singletonMap(VALUE, 42L); Map<String, Object> map = Collections.singletonMap(VALUE, 42L);
exception.expect(IllegalArgumentException.class); exception.expect(IllegalArgumentException.class);
exception.expectMessage(containsString( exception.expectMessage(containsString(
"Attribute 'value' in annotation org.springframework.stereotype.Component " "Attribute 'value' in annotation org.springframework.stereotype.Component " +
+ "should be compatible with java.lang.String but a java.lang.Long value was returned")); "should be compatible with java.lang.String but a java.lang.Long value was returned"));
synthesizeAnnotation(map, Component.class, null); synthesizeAnnotation(map, Component.class, null);
} }
@ -1055,12 +1068,6 @@ public class AnnotationUtilsTests {
void fromInterfaceImplementedByRoot(); void fromInterfaceImplementedByRoot();
} }
public interface NullableAnnotatedInterface {
@Nullable
void fromInterfaceImplementedByRoot();
}
public static class Root implements AnnotatedInterface { public static class Root implements AnnotatedInterface {
@Order(27) @Order(27)

View File

@ -77,18 +77,17 @@ public class MergedAnnotationsTests {
@Test @Test
public void streamWhenFromClassWithMetaDepth1() { public void streamWhenFromClassWithMetaDepth1() {
Stream<String> names = MergedAnnotations.from(TransactionalComponent.class) Stream<Class<?>> classes = MergedAnnotations.from(TransactionalComponent.class)
.stream().map(MergedAnnotation::getType); .stream().map(MergedAnnotation::getType);
assertThat(names).containsExactly(Transactional.class.getName(), assertThat(classes).containsExactly(Transactional.class, Component.class, Indexed.class);
Component.class.getName(), Indexed.class.getName());
} }
@Test @Test
public void streamWhenFromClassWithMetaDepth2() { public void streamWhenFromClassWithMetaDepth2() {
Stream<String> names = MergedAnnotations.from(ComposedTransactionalComponent.class) Stream<Class<?>> classes = MergedAnnotations.from(ComposedTransactionalComponent.class)
.stream().map(MergedAnnotation::getType); .stream().map(MergedAnnotation::getType);
assertThat(names).containsExactly(TransactionalComponent.class.getName(), assertThat(classes).containsExactly(TransactionalComponent.class,
Transactional.class.getName(), Component.class.getName(), Indexed.class.getName()); Transactional.class, Component.class, Indexed.class);
} }
@Test @Test
@ -1149,17 +1148,16 @@ public class MergedAnnotationsTests {
@Test @Test
public void getDirectWithoutAttributeAliases() { public void getDirectWithoutAttributeAliases() {
MergedAnnotation<?> annotation = MergedAnnotations.from(WebController.class).get( MergedAnnotation<?> annotation = MergedAnnotations.from(WebController.class)
Component.class); .get(Component.class);
assertThat(annotation.getString("value")).isEqualTo("webController"); assertThat(annotation.getString("value")).isEqualTo("webController");
} }
@Test @Test
public void getDirectWithNestedAnnotations() { public void getDirectWithNestedAnnotations() {
MergedAnnotation<?> annotation = MergedAnnotations.from( MergedAnnotation<?> annotation = MergedAnnotations.from(ComponentScanClass.class)
ComponentScanClass.class).get(ComponentScan.class); .get(ComponentScan.class);
MergedAnnotation<Filter>[] filters = annotation.getAnnotationArray( MergedAnnotation<Filter>[] filters = annotation.getAnnotationArray("excludeFilters", Filter.class);
"excludeFilters", Filter.class);
assertThat(Arrays.stream(filters).map( assertThat(Arrays.stream(filters).map(
filter -> filter.getString("pattern"))).containsExactly("*Foo", "*Bar"); filter -> filter.getString("pattern"))).containsExactly("*Foo", "*Bar");
} }
@ -1167,8 +1165,7 @@ public class MergedAnnotationsTests {
@Test @Test
public void getDirectWithAttributeAliases1() throws Exception { public void getDirectWithAttributeAliases1() throws Exception {
Method method = WebController.class.getMethod("handleMappedWithValueAttribute"); Method method = WebController.class.getMethod("handleMappedWithValueAttribute");
MergedAnnotation<?> annotation = MergedAnnotations.from(method).get( MergedAnnotation<?> annotation = MergedAnnotations.from(method).get(RequestMapping.class);
RequestMapping.class);
assertThat(annotation.getString("name")).isEqualTo("foo"); assertThat(annotation.getString("name")).isEqualTo("foo");
assertThat(annotation.getStringArray("value")).containsExactly("/test"); assertThat(annotation.getStringArray("value")).containsExactly("/test");
assertThat(annotation.getStringArray("path")).containsExactly("/test"); assertThat(annotation.getStringArray("path")).containsExactly("/test");
@ -1177,8 +1174,7 @@ public class MergedAnnotationsTests {
@Test @Test
public void getDirectWithAttributeAliases2() throws Exception { public void getDirectWithAttributeAliases2() throws Exception {
Method method = WebController.class.getMethod("handleMappedWithPathAttribute"); Method method = WebController.class.getMethod("handleMappedWithPathAttribute");
MergedAnnotation<?> annotation = MergedAnnotations.from(method).get( MergedAnnotation<?> annotation = MergedAnnotations.from(method).get(RequestMapping.class);
RequestMapping.class);
assertThat(annotation.getString("name")).isEqualTo("bar"); assertThat(annotation.getString("name")).isEqualTo("bar");
assertThat(annotation.getStringArray("value")).containsExactly("/test"); assertThat(annotation.getStringArray("value")).containsExactly("/test");
assertThat(annotation.getStringArray("path")).containsExactly("/test"); assertThat(annotation.getStringArray("path")).containsExactly("/test");
@ -1186,8 +1182,7 @@ public class MergedAnnotationsTests {
@Test @Test
public void getDirectWithAttributeAliasesWithDifferentValues() throws Exception { public void getDirectWithAttributeAliasesWithDifferentValues() throws Exception {
Method method = WebController.class.getMethod( Method method = WebController.class.getMethod("handleMappedWithDifferentPathAndValueAttributes");
"handleMappedWithDifferentPathAndValueAttributes");
assertThatExceptionOfType(AnnotationConfigurationException.class).isThrownBy( assertThatExceptionOfType(AnnotationConfigurationException.class).isThrownBy(
() -> MergedAnnotations.from(method).get( () -> MergedAnnotations.from(method).get(
RequestMapping.class)).withMessageContaining( RequestMapping.class)).withMessageContaining(
@ -1197,10 +1192,9 @@ public class MergedAnnotationsTests {
@Test @Test
public void getValueFromAnnotation() throws Exception { public void getValueFromAnnotation() throws Exception {
Method method = TransactionalStringGeneric.class.getMethod("something", Method method = TransactionalStringGeneric.class.getMethod("something", Object.class);
Object.class); MergedAnnotation<?> annotation = MergedAnnotations.from(method, SearchStrategy.EXHAUSTIVE)
MergedAnnotation<?> annotation = MergedAnnotations.from(method, .get(Order.class);
SearchStrategy.EXHAUSTIVE).get(Order.class);
assertThat(annotation.getInt("value")).isEqualTo(1); assertThat(annotation.getInt("value")).isEqualTo(1);
} }
@ -1210,21 +1204,17 @@ public class MergedAnnotationsTests {
assertThat(declaredAnnotations).hasSize(1); assertThat(declaredAnnotations).hasSize(1);
Annotation annotation = declaredAnnotations[0]; Annotation annotation = declaredAnnotations[0];
MergedAnnotation<Annotation> mergedAnnotation = MergedAnnotation.from(annotation); MergedAnnotation<Annotation> mergedAnnotation = MergedAnnotation.from(annotation);
assertThat(mergedAnnotation.getType()).contains("NonPublicAnnotation"); assertThat(mergedAnnotation.getType().getSimpleName()).isEqualTo("NonPublicAnnotation");
assertThat( assertThat(mergedAnnotation.synthesize().annotationType().getSimpleName()).isEqualTo("NonPublicAnnotation");
mergedAnnotation.synthesize().annotationType().getSimpleName()).isEqualTo(
"NonPublicAnnotation");
assertThat(mergedAnnotation.getInt("value")).isEqualTo(42); assertThat(mergedAnnotation.getInt("value")).isEqualTo(42);
} }
@Test @Test
public void getDefaultValueFromAnnotation() throws Exception { public void getDefaultValueFromAnnotation() throws Exception {
Method method = TransactionalStringGeneric.class.getMethod("something", Method method = TransactionalStringGeneric.class.getMethod("something", Object.class);
Object.class); MergedAnnotation<Order> annotation = MergedAnnotations.from(method, SearchStrategy.EXHAUSTIVE)
MergedAnnotation<Order> annotation = MergedAnnotations.from(method, .get(Order.class);
SearchStrategy.EXHAUSTIVE).get(Order.class); assertThat(annotation.getDefaultValue("value")).contains(Ordered.LOWEST_PRECEDENCE);
assertThat(annotation.getDefaultValue("value")).contains(
Ordered.LOWEST_PRECEDENCE);
} }
@Test @Test
@ -1233,7 +1223,7 @@ public class MergedAnnotationsTests {
assertThat(declaredAnnotations).hasSize(1); assertThat(declaredAnnotations).hasSize(1);
Annotation declaredAnnotation = declaredAnnotations[0]; Annotation declaredAnnotation = declaredAnnotations[0];
MergedAnnotation<?> annotation = MergedAnnotation.from(declaredAnnotation); MergedAnnotation<?> annotation = MergedAnnotation.from(declaredAnnotation);
assertThat(annotation.getType()).isEqualTo( assertThat(annotation.getType().getName()).isEqualTo(
"org.springframework.core.annotation.subpackage.NonPublicAnnotation"); "org.springframework.core.annotation.subpackage.NonPublicAnnotation");
assertThat(annotation.getDefaultValue("value")).contains(-1); assertThat(annotation.getDefaultValue("value")).contains(-1);
} }

View File

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -78,7 +78,6 @@ public abstract class MetaAnnotationUtils {
* @param annotationType the type of annotation to look for * @param annotationType the type of annotation to look for
* @return the corresponding annotation descriptor if the annotation was found; * @return the corresponding annotation descriptor if the annotation was found;
* otherwise {@code null} * otherwise {@code null}
* @see AnnotationUtils#findAnnotationDeclaringClass(Class, Class)
* @see #findAnnotationDescriptorForTypes(Class, Class...) * @see #findAnnotationDescriptorForTypes(Class, Class...)
*/ */
@Nullable @Nullable
@ -164,7 +163,6 @@ public abstract class MetaAnnotationUtils {
* @param annotationTypes the types of annotations to look for * @param annotationTypes the types of annotations to look for
* @return the corresponding annotation descriptor if one of the annotations * @return the corresponding annotation descriptor if one of the annotations
* was found; otherwise {@code null} * was found; otherwise {@code null}
* @see AnnotationUtils#findAnnotationDeclaringClassForTypes(java.util.List, Class)
* @see #findAnnotationDescriptor(Class, Class) * @see #findAnnotationDescriptor(Class, Class)
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")