1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package io.wcm.qa.glnm.junit.combinatorial;
21
22 import static java.util.Arrays.asList;
23
24 import java.lang.annotation.Annotation;
25 import java.lang.annotation.Inherited;
26 import java.lang.annotation.Repeatable;
27 import java.lang.reflect.AnnotatedElement;
28 import java.lang.reflect.Method;
29 import java.util.Arrays;
30 import java.util.Collection;
31 import java.util.HashSet;
32 import java.util.LinkedHashSet;
33 import java.util.List;
34 import java.util.Optional;
35 import java.util.Set;
36 import java.util.concurrent.ConcurrentHashMap;
37
38 import org.junit.platform.commons.JUnitException;
39 import org.junit.platform.commons.util.Preconditions;
40 import org.junit.platform.commons.util.ReflectionUtils;
41
42 import com.google.common.collect.Lists;
43
44
45 final class ReAnnotationUtils {
46
47 private static final ConcurrentHashMap<Class<? extends Annotation>, Boolean> REPEATABLE_CONTAINER_CACHE =
48 new ConcurrentHashMap<>(16);
49
50 private ReAnnotationUtils() {
51
52 }
53
54 private static <A extends Annotation> Optional<A> findAnnotation(AnnotatedElement element, Class<A> annotationType,
55 boolean inherited, Set<Annotation> visited) {
56
57 Preconditions.notNull(annotationType, "annotationType must not be null");
58
59 if (element == null) {
60 return Optional.empty();
61 }
62
63
64 A annotation = element.getDeclaredAnnotation(annotationType);
65 if (annotation != null) {
66 return Optional.of(annotation);
67 }
68
69
70 Optional<A> directMetaAnnotation = findMetaAnnotation(annotationType, element.getDeclaredAnnotations(),
71 inherited, visited);
72 if (directMetaAnnotation.isPresent()) {
73 return directMetaAnnotation;
74 }
75
76 if (element instanceof Class) {
77 Class<?> clazz = (Class<?>)element;
78
79
80 for (Class<?> ifc : clazz.getInterfaces()) {
81 if (ifc != Annotation.class) {
82 Optional<A> annotationOnInterface = findAnnotation(ifc, annotationType, inherited, visited);
83 if (annotationOnInterface.isPresent()) {
84 return annotationOnInterface;
85 }
86 }
87 }
88
89
90
91 if (inherited) {
92 Class<?> superclass = clazz.getSuperclass();
93 if (superclass != null && superclass != Object.class) {
94 Optional<A> annotationOnSuperclass = findAnnotation(superclass, annotationType, inherited, visited);
95 if (annotationOnSuperclass.isPresent()) {
96 return annotationOnSuperclass;
97 }
98 }
99 }
100 }
101
102
103 return findMetaAnnotation(annotationType, element.getAnnotations(), inherited, visited);
104 }
105
106 private static <A extends Annotation> Optional<A> findMetaAnnotation(Class<A> annotationType,
107 Annotation[] candidates, boolean inherited, Set<Annotation> visited) {
108
109 for (Annotation candidateAnnotation : candidates) {
110 Class<? extends Annotation> candidateAnnotationType = candidateAnnotation.annotationType();
111 if (!isInJavaLangAnnotationPackage(candidateAnnotationType) && visited.add(candidateAnnotation)) {
112 Optional<A> metaAnnotation = findAnnotation(candidateAnnotationType, annotationType, inherited,
113 visited);
114 if (metaAnnotation.isPresent()) {
115 return metaAnnotation;
116 }
117 }
118 }
119 return Optional.empty();
120 }
121
122 @SuppressWarnings("unchecked")
123 private static <A extends Annotation> void findRepeatableAnnotation(Annotation candidate, Class<A> annotationType, Class<? extends Annotation> containerType,
124 boolean inherited, Set<A> found, Set<Annotation> visited) {
125
126 Class<? extends Annotation> candidateAnnotationType = candidate.annotationType();
127 if (!isInJavaLangAnnotationPackage(candidateAnnotationType) && visited.add(candidate)) {
128
129 if (candidateAnnotationType.equals(annotationType)) {
130 found.add(annotationType.cast(candidate));
131 }
132
133 else if (candidateAnnotationType.equals(containerType)) {
134
135
136
137 Method method = ReflectionUtils.tryToGetMethod(containerType, "value").getOrThrow(
138 cause -> new JUnitException(String.format(
139 "Container annotation type '%s' must declare a 'value' attribute of type %s[].",
140 containerType, annotationType), cause));
141
142 Annotation[] containedAnnotations = (Annotation[])ReflectionUtils.invokeMethod(method, candidate);
143 found.addAll((Collection<? extends A>)asList(containedAnnotations));
144 }
145
146 else if (isRepeatableAnnotationContainer(candidateAnnotationType)) {
147 Method method = ReflectionUtils.tryToGetMethod(candidateAnnotationType, "value").toOptional().get();
148 Annotation[] containedAnnotations = (Annotation[])ReflectionUtils.invokeMethod(method, candidate);
149
150 for (Annotation containedAnnotation : containedAnnotations) {
151 findRepeatableAnnotations(containedAnnotation.getClass(), annotationType, containerType,
152 inherited, found, visited);
153 }
154 }
155
156 else {
157 findRepeatableAnnotations(candidateAnnotationType, annotationType, containerType, inherited, found,
158 visited);
159 }
160 }
161 }
162
163 private static <A extends Annotation> void findRepeatableAnnotations(AnnotatedElement element,
164 Class<A> annotationType, Class<? extends Annotation> containerType, boolean inherited, Set<A> found,
165 Set<Annotation> visited) {
166
167 if (element instanceof Class) {
168 Class<?> clazz = (Class<?>)element;
169
170
171 if (inherited) {
172 Class<?> superclass = clazz.getSuperclass();
173 if (superclass != null && superclass != Object.class) {
174 findRepeatableAnnotations(superclass, annotationType, containerType, inherited, found, visited);
175 }
176 }
177
178
179 for (Class<?> ifc : clazz.getInterfaces()) {
180 if (ifc != Annotation.class) {
181 findRepeatableAnnotations(ifc, annotationType, containerType, inherited, found, visited);
182 }
183 }
184 }
185
186
187 findRepeatableAnnotations(element.getDeclaredAnnotations(), annotationType, containerType, inherited, found,
188 visited);
189
190
191 findRepeatableAnnotations(element.getAnnotations(), annotationType, containerType, inherited, found, visited);
192 }
193
194 private static <A extends Annotation> List<A> findRepeatableAnnotations(Annotation candidate, Class<A> annotationType,
195 Class<? extends Annotation> containerType,
196 boolean inherited) {
197 Set<A> found = new LinkedHashSet<>(16);
198
199 findRepeatableAnnotation(candidate, annotationType, containerType, inherited, found, new HashSet<>(16));
200 return Lists.newArrayList(found);
201 }
202
203 private static <A extends Annotation> void findRepeatableAnnotations(Annotation[] candidates,
204 Class<A> annotationType, Class<? extends Annotation> containerType, boolean inherited, Set<A> found,
205 Set<Annotation> visited) {
206
207 for (Annotation candidate : candidates) {
208 findRepeatableAnnotation(candidate, annotationType, containerType, inherited, found, visited);
209 }
210 }
211
212 private static <A extends Annotation> Class<? extends Annotation> getContainerType(Class<A> annotationType) {
213 Preconditions.notNull(annotationType, "annotationType must not be null");
214 Repeatable repeatable = annotationType.getAnnotation(Repeatable.class);
215 Preconditions.notNull(repeatable, () -> annotationType.getName() + " must be @Repeatable");
216 Class<? extends Annotation> containerType = repeatable.value();
217 return containerType;
218 }
219
220 private static boolean isInherited(Class<? extends Annotation> containerType) {
221 return containerType.isAnnotationPresent(Inherited.class);
222 }
223
224 private static boolean isInJavaLangAnnotationPackage(Class<? extends Annotation> annotationType) {
225 return (annotationType != null && annotationType.getName().startsWith("java.lang.annotation"));
226 }
227
228
229
230
231
232
233 private static boolean isRepeatableAnnotationContainer(Class<? extends Annotation> candidateContainerType) {
234 return REPEATABLE_CONTAINER_CACHE.computeIfAbsent(candidateContainerType, candidate -> {
235
236 Repeatable repeatable = Arrays.stream(candidate.getMethods())
237 .filter(attribute -> attribute.getName().equals("value") && attribute.getReturnType().isArray())
238 .findFirst()
239 .map(attribute -> attribute.getReturnType().getComponentType().getAnnotation(Repeatable.class))
240 .orElse(null);
241
242
243 return repeatable != null && candidate.equals(repeatable.value());
244 });
245 }
246
247 static <A extends Annotation> List<A> findRepeatableAnnotations(
248 Annotation candidate,
249 Class<A> annotationType) {
250 Class<? extends Annotation> containerType = getContainerType(annotationType);
251 boolean inherited = isInherited(containerType);
252 return findRepeatableAnnotations(candidate, annotationType, containerType, inherited);
253 }
254
255 }