Skip to content

Commit 9d6cf57

Browse files
philwebbjhoeller
authored andcommitted
Add MergedAnnotations.of method
Add a factory method to `MergedAnnotation` that allows an instance to be created for an explicit collection of root annotations. This method will allow ASM based readers to expose a `MergedAnnotation` instance that has root annotations loaded from bytecode, and meta-annotations loaded using reflection. See gh-22884
1 parent daec353 commit 9d6cf57

File tree

4 files changed

+652
-1
lines changed

4 files changed

+652
-1
lines changed

spring-core/src/main/java/org/springframework/core/annotation/MergedAnnotations.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.lang.annotation.Inherited;
2121
import java.lang.reflect.AnnotatedElement;
2222
import java.lang.reflect.Method;
23+
import java.util.Collection;
2324
import java.util.function.Predicate;
2425
import java.util.stream.Stream;
2526

@@ -368,6 +369,26 @@ static MergedAnnotations from(@Nullable Object source, Annotation[] annotations,
368369
return TypeMappedAnnotations.from(source, annotations, repeatableContainers, annotationFilter);
369370
}
370371

372+
/**
373+
* Create a new {@link MergedAnnotations} instance from the specified
374+
* collection of directly present annotations. This method allows a
375+
* {@link MergedAnnotations} instance to be created from annotations that
376+
* are not necessarily loaded using reflection. The provided annotations
377+
* must all be {@link MergedAnnotation#isDirectlyPresent() directly present}
378+
* and must have a {@link MergedAnnotation#getAggregateIndex() aggregate
379+
* index} of {@code 0}.
380+
* <p>
381+
* The resulting {@link MergedAnnotations} instance will contain both the
382+
* specified annotations, and any meta-annotations that can be read using
383+
* reflection.
384+
* @param annotations the annotations to include
385+
* @return a {@link MergedAnnotations} instance containing the annotations
386+
* @see MergedAnnotation#of(ClassLoader, Object, Class, java.util.Map)
387+
*/
388+
static MergedAnnotations of(Collection<MergedAnnotation<?>> annotations) {
389+
return MergedAnnotationsCollection.of(annotations);
390+
}
391+
371392

372393
/**
373394
* Search strategies supported by
Lines changed: 315 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,315 @@
1+
/*
2+
* Copyright 2002-2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.core.annotation;
18+
19+
import java.lang.annotation.Annotation;
20+
import java.util.Collection;
21+
import java.util.Iterator;
22+
import java.util.Spliterator;
23+
import java.util.Spliterators;
24+
import java.util.function.Consumer;
25+
import java.util.function.Predicate;
26+
import java.util.stream.Stream;
27+
import java.util.stream.StreamSupport;
28+
29+
import org.springframework.lang.Nullable;
30+
import org.springframework.util.Assert;
31+
32+
/**
33+
* {@link MergedAnnotations} implementation backed by a {@link Collection}
34+
* {@link MergedAnnotation} instances that represent direct annotations.
35+
*
36+
* @author Phillip Webb
37+
* @since 5.2
38+
* @see MergedAnnotations#of(Collection)
39+
*/
40+
final class MergedAnnotationsCollection implements MergedAnnotations {
41+
42+
private final MergedAnnotation<?>[] annotations;
43+
44+
private final AnnotationTypeMappings[] mappings;
45+
46+
private MergedAnnotationsCollection(Collection<MergedAnnotation<?>> annotations) {
47+
Assert.notNull(annotations, "Annotations must not be null");
48+
this.annotations = annotations.toArray(new MergedAnnotation<?>[0]);
49+
this.mappings = new AnnotationTypeMappings[this.annotations.length];
50+
for (int i = 0; i < this.annotations.length; i++) {
51+
MergedAnnotation<?> annotation = this.annotations[i];
52+
Assert.notNull(annotation, "Annotation must not be null");
53+
Assert.isTrue(annotation.isDirectlyPresent(), "Annotation must be directly present");
54+
Assert.isTrue(annotation.getAggregateIndex() == 0, "Annotation must have aggregate index of zero");
55+
this.mappings[i] = AnnotationTypeMappings.forAnnotationType(
56+
annotation.getType());
57+
}
58+
}
59+
60+
@Override
61+
public Iterator<MergedAnnotation<Annotation>> iterator() {
62+
return Spliterators.iterator(spliterator());
63+
}
64+
65+
@Override
66+
public Spliterator<MergedAnnotation<Annotation>> spliterator() {
67+
return spliterator(null);
68+
}
69+
70+
private <A extends Annotation> Spliterator<MergedAnnotation<A>> spliterator(
71+
@Nullable Object annotationType) {
72+
return new AnnotationsSpliterator<>(annotationType);
73+
}
74+
75+
@Override
76+
public <A extends Annotation> boolean isPresent(Class<A> annotationType) {
77+
return isPresent(annotationType, false);
78+
}
79+
80+
@Override
81+
public boolean isPresent(String annotationType) {
82+
return isPresent(annotationType, false);
83+
}
84+
85+
@Override
86+
public <A extends Annotation> boolean isDirectlyPresent(Class<A> annotationType) {
87+
return isPresent(annotationType, true);
88+
}
89+
90+
@Override
91+
public boolean isDirectlyPresent(String annotationType) {
92+
return isPresent(annotationType, true);
93+
}
94+
95+
private boolean isPresent(Object requiredType, boolean directOnly) {
96+
for (MergedAnnotation<?> annotation : this.annotations) {
97+
Class<? extends Annotation> type = annotation.getType();
98+
if (type == requiredType || type.getName().equals(requiredType)) {
99+
return true;
100+
}
101+
}
102+
if (!directOnly) {
103+
for (AnnotationTypeMappings mappings : this.mappings) {
104+
for (int i = 1; i < mappings.size(); i++) {
105+
AnnotationTypeMapping mapping = mappings.get(i);
106+
if (isMappingForType(mapping, requiredType)) {
107+
return true;
108+
}
109+
}
110+
}
111+
}
112+
return false;
113+
}
114+
115+
@Override
116+
public <A extends Annotation> MergedAnnotation<A> get(Class<A> annotationType) {
117+
return get(annotationType, null, null);
118+
}
119+
120+
@Override
121+
public <A extends Annotation> MergedAnnotation<A> get(Class<A> annotationType,
122+
@Nullable Predicate<? super MergedAnnotation<A>> predicate) {
123+
124+
return get(annotationType, predicate, null);
125+
}
126+
127+
@Override
128+
public <A extends Annotation> MergedAnnotation<A> get(Class<A> annotationType,
129+
@Nullable Predicate<? super MergedAnnotation<A>> predicate,
130+
@Nullable MergedAnnotationSelector<A> selector) {
131+
MergedAnnotation<A> result = find(annotationType, predicate, selector);
132+
return (result != null ? result : MergedAnnotation.missing());
133+
}
134+
135+
@Override
136+
public <A extends Annotation> MergedAnnotation<A> get(String annotationType) {
137+
return get(annotationType, null, null);
138+
}
139+
140+
@Override
141+
public <A extends Annotation> MergedAnnotation<A> get(String annotationType,
142+
@Nullable Predicate<? super MergedAnnotation<A>> predicate) {
143+
144+
return get(annotationType, predicate, null);
145+
}
146+
147+
@Override
148+
public <A extends Annotation> MergedAnnotation<A> get(String annotationType,
149+
@Nullable Predicate<? super MergedAnnotation<A>> predicate,
150+
@Nullable MergedAnnotationSelector<A> selector) {
151+
152+
MergedAnnotation<A> result = find(annotationType, predicate, selector);
153+
return (result != null ? result : MergedAnnotation.missing());
154+
}
155+
156+
@SuppressWarnings("unchecked")
157+
private <A extends Annotation> MergedAnnotation<A> find(Object requiredType,
158+
Predicate<? super MergedAnnotation<A>> predicate,
159+
MergedAnnotationSelector<A> selector) {
160+
if (selector == null) {
161+
selector = MergedAnnotationSelectors.nearest();
162+
}
163+
MergedAnnotation<A> result = null;
164+
for (int i = 0; i < this.annotations.length; i++) {
165+
MergedAnnotation<?> root = this.annotations[i];
166+
AnnotationTypeMappings mappings = this.mappings[i];
167+
for (int mappingIndex = 0; mappingIndex < mappings.size(); mappingIndex++) {
168+
AnnotationTypeMapping mapping = mappings.get(mappingIndex);
169+
if (!isMappingForType(mapping, requiredType)) {
170+
continue;
171+
}
172+
MergedAnnotation<A> candidate = (mappingIndex == 0
173+
? (MergedAnnotation<A>) root
174+
: TypeMappedAnnotation.createIfPossible(mapping, root, IntrospectionFailureLogger.INFO));
175+
if (candidate != null && (predicate == null || predicate.test(candidate))) {
176+
if (selector.isBestCandidate(candidate)) {
177+
return candidate;
178+
}
179+
result = (result != null ? selector.select(result, candidate) : candidate);
180+
}
181+
}
182+
}
183+
return result;
184+
}
185+
186+
@Override
187+
public <A extends Annotation> Stream<MergedAnnotation<A>> stream(Class<A> annotationType) {
188+
return StreamSupport.stream(spliterator(annotationType), false);
189+
}
190+
191+
@Override
192+
public <A extends Annotation> Stream<MergedAnnotation<A>> stream(String annotationType) {
193+
return StreamSupport.stream(spliterator(annotationType), false);
194+
}
195+
196+
@Override
197+
public Stream<MergedAnnotation<Annotation>> stream() {
198+
return StreamSupport.stream(spliterator(), false);
199+
}
200+
201+
private static boolean isMappingForType(AnnotationTypeMapping mapping, @Nullable Object requiredType) {
202+
if (requiredType == null) {
203+
return true;
204+
}
205+
Class<? extends Annotation> actualType = mapping.getAnnotationType();
206+
return (actualType == requiredType || actualType.getName().equals(requiredType));
207+
}
208+
209+
static MergedAnnotations of(Collection<MergedAnnotation<?>> annotations) {
210+
Assert.notNull(annotations, "Annotations must not be null");
211+
if(annotations.isEmpty()) {
212+
return TypeMappedAnnotations.NONE;
213+
}
214+
return new MergedAnnotationsCollection(annotations);
215+
}
216+
217+
218+
private class AnnotationsSpliterator<A extends Annotation> implements Spliterator<MergedAnnotation<A>> {
219+
220+
@Nullable
221+
private Object requiredType;
222+
223+
private final int[] mappingCursors;
224+
225+
public AnnotationsSpliterator(@Nullable Object requiredType) {
226+
this.mappingCursors = new int[annotations.length];
227+
this.requiredType = requiredType;
228+
}
229+
230+
@Override
231+
public boolean tryAdvance(Consumer<? super MergedAnnotation<A>> action) {
232+
int lowestDepth = Integer.MAX_VALUE;
233+
int annotationResult = -1;
234+
for (int annotationIndex = 0; annotationIndex < annotations.length; annotationIndex++) {
235+
AnnotationTypeMapping mapping = getNextSuitableMapping(annotationIndex);
236+
if (mapping != null && mapping.getDepth() < lowestDepth) {
237+
annotationResult = annotationIndex;
238+
lowestDepth = mapping.getDepth();
239+
}
240+
if (lowestDepth == 0) {
241+
break;
242+
}
243+
}
244+
if (annotationResult != -1) {
245+
MergedAnnotation<A> mergedAnnotation = createMergedAnnotationIfPossible(annotationResult, this.mappingCursors[annotationResult]);
246+
this.mappingCursors[annotationResult]++;
247+
if (mergedAnnotation == null) {
248+
return tryAdvance(action);
249+
}
250+
action.accept(mergedAnnotation);
251+
return true;
252+
}
253+
return false;
254+
}
255+
256+
@Nullable
257+
private AnnotationTypeMapping getNextSuitableMapping(int annotationIndex) {
258+
AnnotationTypeMapping mapping;
259+
do {
260+
mapping = getMapping(annotationIndex, this.mappingCursors[annotationIndex]);
261+
if (mapping != null && isMappingForType(mapping, this.requiredType)) {
262+
return mapping;
263+
}
264+
this.mappingCursors[annotationIndex]++;
265+
}
266+
while (mapping != null);
267+
return null;
268+
}
269+
270+
@Nullable
271+
private AnnotationTypeMapping getMapping(int annotationIndex, int mappingIndex) {
272+
AnnotationTypeMappings mappings = MergedAnnotationsCollection.this.mappings[annotationIndex];
273+
return (mappingIndex < mappings.size() ? mappings.get(mappingIndex) : null);
274+
}
275+
276+
@Nullable
277+
@SuppressWarnings("unchecked")
278+
private MergedAnnotation<A> createMergedAnnotationIfPossible(
279+
int annotationIndex, int mappingIndex) {
280+
MergedAnnotation<?> root = annotations[annotationIndex];
281+
if(mappingIndex == 0) {
282+
return (MergedAnnotation<A>) root;
283+
}
284+
IntrospectionFailureLogger logger = (this.requiredType != null
285+
? IntrospectionFailureLogger.INFO
286+
: IntrospectionFailureLogger.DEBUG);
287+
return TypeMappedAnnotation.createIfPossible(
288+
mappings[annotationIndex].get(mappingIndex), root, logger);
289+
}
290+
291+
@Override
292+
public Spliterator<MergedAnnotation<A>> trySplit() {
293+
return null;
294+
}
295+
296+
@Override
297+
public long estimateSize() {
298+
int size = 0;
299+
for (int i = 0; i < annotations.length; i++) {
300+
AnnotationTypeMappings mappings = MergedAnnotationsCollection.this.mappings[i];
301+
int numberOfMappings = mappings.size();
302+
numberOfMappings -= Math.min(this.mappingCursors[i], mappings.size());
303+
size += numberOfMappings;
304+
}
305+
return size;
306+
}
307+
308+
@Override
309+
public int characteristics() {
310+
return NONNULL | IMMUTABLE;
311+
}
312+
313+
}
314+
315+
}

spring-core/src/main/java/org/springframework/core/annotation/TypeMappedAnnotations.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,10 @@ final class TypeMappedAnnotations implements MergedAnnotations {
4242

4343
private static final AnnotationFilter FILTER_ALL = (annotationType -> true);
4444

45-
private static final MergedAnnotations NONE = new TypeMappedAnnotations(
45+
/**
46+
* Shared instance that can be used when there are no annotations.
47+
*/
48+
static final MergedAnnotations NONE = new TypeMappedAnnotations(
4649
null, new Annotation[0], RepeatableContainers.none(), FILTER_ALL);
4750

4851

0 commit comments

Comments
 (0)