Support customer repeatable containers in type map

Update `AnnotationTypeMappings` so that a custom `RepeatableContainers`
instances can be used. Prior to this commit, only standard repeatables
were used when reading the annotations. This works in most situations,
but causes regressions for some `AnnotationUtils` methods.

Fixed gh-23856
This commit is contained in:
Phillip Webb 2019-10-24 11:54:42 -07:00
parent f8b875d2d8
commit 013ec6abdb
3 changed files with 44 additions and 10 deletions

View File

@ -47,15 +47,22 @@ final class AnnotationTypeMappings {
private static final IntrospectionFailureLogger failureLogger = IntrospectionFailureLogger.DEBUG;
private static final Map<AnnotationFilter, Cache> cache = new ConcurrentReferenceHashMap<>();
private static final Map<AnnotationFilter, Cache> standardRepeatablesCache = new ConcurrentReferenceHashMap<>();
private static final Map<AnnotationFilter, Cache> noRepeatablesCache = new ConcurrentReferenceHashMap<>();
private final RepeatableContainers repeatableContainers;
private final AnnotationFilter filter;
private final List<AnnotationTypeMapping> mappings;
private AnnotationTypeMappings(AnnotationFilter filter, Class<? extends Annotation> annotationType) {
private AnnotationTypeMappings(RepeatableContainers repeatableContainers,
AnnotationFilter filter, Class<? extends Annotation> annotationType) {
this.repeatableContainers = repeatableContainers;
this.filter = filter;
this.mappings = new ArrayList<>();
addAllMappings(annotationType);
@ -80,7 +87,7 @@ final class AnnotationTypeMappings {
if (!isMappable(source, metaAnnotation)) {
continue;
}
Annotation[] repeatedAnnotations = RepeatableContainers.standardRepeatables()
Annotation[] repeatedAnnotations = this.repeatableContainers
.findRepeatedAnnotations(metaAnnotation);
if (repeatedAnnotations != null) {
for (Annotation repeatedAnnotation : repeatedAnnotations) {
@ -178,11 +185,37 @@ final class AnnotationTypeMappings {
static AnnotationTypeMappings forAnnotationType(
Class<? extends Annotation> annotationType, AnnotationFilter annotationFilter) {
return cache.computeIfAbsent(annotationFilter, Cache::new).get(annotationType);
return forAnnotationType(annotationType,
RepeatableContainers.standardRepeatables(), annotationFilter);
}
/**
* Create {@link AnnotationTypeMappings} for the specified annotation type.
* @param annotationType the source annotation type
* @param annotationFilter the annotation filter used to limit which
* annotations are considered
* @return type mappings for the annotation type
*/
static AnnotationTypeMappings forAnnotationType(
Class<? extends Annotation> annotationType,
RepeatableContainers repeatableContainers,
AnnotationFilter annotationFilter) {
if (repeatableContainers == RepeatableContainers.standardRepeatables()) {
return standardRepeatablesCache.computeIfAbsent(annotationFilter,
key -> new Cache(repeatableContainers, key)).get(annotationType);
}
if (repeatableContainers == RepeatableContainers.none()) {
return noRepeatablesCache.computeIfAbsent(annotationFilter,
key -> new Cache(repeatableContainers, key)).get(annotationType);
}
return new AnnotationTypeMappings(repeatableContainers, annotationFilter,
annotationType);
}
static void clearCache() {
cache.clear();
standardRepeatablesCache.clear();
noRepeatablesCache.clear();
}
@ -191,6 +224,8 @@ final class AnnotationTypeMappings {
*/
private static class Cache {
private final RepeatableContainers repeatableContainers;
private final AnnotationFilter filter;
private final Map<Class<? extends Annotation>, AnnotationTypeMappings> mappings;
@ -199,7 +234,8 @@ final class AnnotationTypeMappings {
* Create a cache instance with the specified filter.
* @param filter the annotation filter
*/
Cache(AnnotationFilter filter) {
Cache(RepeatableContainers repeatableContainers, AnnotationFilter filter) {
this.repeatableContainers = repeatableContainers;
this.filter = filter;
this.mappings = new ConcurrentReferenceHashMap<>();
}
@ -214,7 +250,7 @@ final class AnnotationTypeMappings {
}
AnnotationTypeMappings createMappings(Class<? extends Annotation> annotationType) {
return new AnnotationTypeMappings(this.filter, annotationType);
return new AnnotationTypeMappings(this.repeatableContainers, this.filter, annotationType);
}
}

View File

@ -413,7 +413,7 @@ final class TypeMappedAnnotations implements MergedAnnotations {
return doWithAnnotations(type, aggregateIndex, source, repeatedAnnotations);
}
AnnotationTypeMappings mappings = AnnotationTypeMappings.forAnnotationType(
annotation.annotationType(), annotationFilter);
annotation.annotationType(), repeatableContainers, annotationFilter);
for (int i = 0; i < mappings.size(); i++) {
AnnotationTypeMapping mapping = mappings.get(i);
if (isMappingForType(mapping, annotationFilter, this.requiredType)) {

View File

@ -34,7 +34,6 @@ import javax.annotation.Nonnull;
import javax.annotation.ParametersAreNonnullByDefault;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.core.Ordered;
@ -974,7 +973,6 @@ class AnnotationUtilsTests {
}
@Test // gh-23856
@Disabled("disabled until gh-23856 is resolved")
void findAnnotationFindsRepeatableContainerOnComposedAnnotationMetaAnnotatedWithRepeatableAnnotationsOnMethod() throws NoSuchMethodException {
Method method = getClass().getDeclaredMethod("methodWithComposedAnnotationMetaAnnotatedWithRepeatableAnnotations");
MyRepeatableContainer annotation = AnnotationUtils.findAnnotation(method, MyRepeatableContainer.class);