Skip to content

Commit 0023b90

Browse files
Artem LabazinArtem Labazin
authored andcommitted
Created AnnotationUtils helper class
1 parent 3930bbb commit 0023b90

4 files changed

Lines changed: 411 additions & 3 deletions

File tree

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,16 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
1212
- Add more tests.
1313
- Add `JavaDoc`.
1414

15+
## [1.7.0](https://github.com/appulse-projects/utils-java/releases/tag/1.7.0) - 2018-05-24
16+
17+
Created AnnotationUtils helper class.
18+
19+
### Added
20+
21+
- `AnnotationUtils` helper class.
22+
- `AnnotationUtils`.`findAnnotation` method for deep searching annotations.
23+
- `AnnotationUtils`.`isInJavaLangAnnotationPackage` method for determining user's/java annotations.
24+
1525
## [1.6.0](https://github.com/appulse-projects/utils-java/releases/tag/1.6.0) - 2018-05-22
1626

1727
Created ExceptionUtils helper class.

pom.xml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ limitations under the License.
2424

2525
<groupId>io.appulse</groupId>
2626
<artifactId>utils-java</artifactId>
27-
<version>1.6.0</version>
27+
<version>1.7.0</version>
2828
<packaging>jar</packaging>
2929

3030
<properties>
@@ -62,7 +62,7 @@ limitations under the License.
6262
<url>https://github.com/appulse-projects/utils-java</url>
6363
<connection>scm:git:https://github.com/appulse-projects/utils-java.git</connection>
6464
<developerConnection>scm:git:https://github.com/appulse-projects/utils-java.git</developerConnection>
65-
<tag>1.5.1</tag>
65+
<tag>1.7.0</tag>
6666
</scm>
6767

6868
<distributionManagement>
@@ -161,7 +161,7 @@ limitations under the License.
161161
<plugin>
162162
<groupId>org.apache.maven.plugins</groupId>
163163
<artifactId>maven-javadoc-plugin</artifactId>
164-
<version>3.0.0</version>
164+
<version>2.10.4</version>
165165
<configuration>
166166
<additionalOptions>-Xdoclint:none</additionalOptions>
167167
<additionalparam>-Xdoclint:none</additionalparam>
Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
/*
2+
* Copyright 2018 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+
* http://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 io.appulse.utils;
18+
19+
import static java.util.Optional.ofNullable;
20+
21+
import java.lang.annotation.Annotation;
22+
import java.lang.reflect.AnnotatedElement;
23+
import java.lang.reflect.Method;
24+
import java.util.Arrays;
25+
import java.util.HashSet;
26+
import java.util.Objects;
27+
import java.util.Optional;
28+
import java.util.Set;
29+
import java.util.stream.Stream;
30+
31+
/**
32+
* Different annotation helpers.
33+
*
34+
* @author Artem Labazin
35+
* @since 1.7.0
36+
*/
37+
public final class AnnotationUtils {
38+
39+
/**
40+
* Find a single {@link Annotation} of {@code annotationType} on the
41+
* supplied {@link Class}, traversing its interfaces, annotations, and
42+
* superclasses if the annotation is not <em>directly present</em> on
43+
* the given class itself.
44+
* <p>
45+
* This method explicitly handles class-level annotations which are not
46+
* declared as {@link java.lang.annotation.Inherited inherited} <em>as well
47+
* as meta-annotations and annotations on interfaces</em>.
48+
* <p>
49+
* The algorithm operates as follows:
50+
* <ol>
51+
* <li>Search for the annotation on the given class and return it if found.
52+
* <li>Recursively search through all annotations that the given class declares.
53+
* <li>Recursively search through all interfaces that the given class declares.
54+
* <li>Recursively search through the superclass hierarchy of the given class.
55+
* </ol>
56+
* <p>
57+
* Note: in this context, the term <em>recursively</em> means that the search
58+
* process continues by returning to step #1 with the current interface,
59+
* annotation, or superclass as the class to look for annotations on.
60+
*
61+
* @param clazz the class to look for annotations on
62+
*
63+
* @param annotationType the annotation type to look for, both locally and as a meta-annotation
64+
*
65+
* @return the first matching annotation
66+
*
67+
* @since 1.7.0
68+
*/
69+
public static <T extends Annotation> Optional<T> findAnnotation (Class<?> clazz, Class<T> annotationType) {
70+
T result = findAnnotation(clazz, annotationType, new HashSet<>());
71+
return ofNullable(result);
72+
}
73+
74+
/**
75+
* Find a single {@link Annotation} of {@code annotationType} on the
76+
* supplied {@link AnnotatedElement}.
77+
* <p>
78+
* Meta-annotations will be searched if the annotation is not
79+
* <em>directly present</em> on the supplied element.
80+
* <p>
81+
* <strong>Warning</strong>: this method operates generically on
82+
* annotated elements. In other words, this method does not execute
83+
* specialized search algorithms for classes or methods. If you require
84+
* the more specific semantics of {@link #findAnnotation(Class, Class)}
85+
* or {@link #findAnnotation(Method, Class)}, invoke one of those methods
86+
* instead.
87+
*
88+
* @param annotatedElement the {@code AnnotatedElement} on which to find the annotation
89+
*
90+
* @param annotationType the annotation type to look for, both locally and as a meta-annotation
91+
*
92+
* @return the first matching annotation
93+
*
94+
* @since 1.7.0
95+
*/
96+
public static <T extends Annotation> Optional<T> findAnnotation (AnnotatedElement annotatedElement,
97+
Class<T> annotationType
98+
) {
99+
T result = findAnnotation(annotatedElement, annotationType, new HashSet<>());
100+
return ofNullable(result);
101+
}
102+
103+
/**
104+
* Find a single {@link Annotation} of {@code annotationType} on the supplied
105+
* {@link Method}, traversing its super methods (i.e., from superclasses and
106+
* interfaces) if the annotation is not <em>directly present</em> on the given
107+
* method itself.
108+
* <p>
109+
* Correctly handles bridge {@link Method Methods} generated by the compiler.
110+
* <p>
111+
* Meta-annotations will be searched if the annotation is not
112+
* <em>directly present</em> on the method.
113+
* <p>
114+
* Annotations on methods are not inherited by default, so we need to handle
115+
* this explicitly.
116+
*
117+
* @param method the method to look for annotations on
118+
*
119+
* @param annotationType the annotation type to look for
120+
*
121+
* @return the first matching annotation
122+
*
123+
* @since 1.7.0
124+
*/
125+
public static <T extends Annotation> Optional<T> findAnnotation (Method method, Class<T> annotationType) {
126+
T result = findAnnotation(method, annotationType, new HashSet<>());
127+
return ofNullable(result);
128+
}
129+
130+
/**
131+
* Determine if the supplied {@link Annotation} is defined in the core JDK {@code java.lang.annotation} package.
132+
*
133+
* @param annotation the annotation to check
134+
*
135+
* @return {@code true} if the annotation is in the {@code java.lang.annotation} package
136+
*
137+
* @since 1.7.0
138+
*/
139+
public static boolean isInJavaLangAnnotationPackage (Annotation annotation) {
140+
return annotation != null && isInJavaLangAnnotationPackage(annotation.annotationType());
141+
}
142+
143+
/**
144+
* Determine if the {@link Annotation} with the supplied name is defined in the core JDK {@code java.lang.annotation} package.
145+
*
146+
* @param annotationType the annotation type to check
147+
*
148+
* @return {@code true} if the annotation is in the {@code java.lang.annotation} package
149+
*
150+
* @since 1.7.0
151+
*/
152+
public static boolean isInJavaLangAnnotationPackage (Class<? extends Annotation> annotationType) {
153+
return annotationType != null && isInJavaLangAnnotationPackage(annotationType.getName());
154+
}
155+
156+
/**
157+
* Determine if the {@link Annotation} with the supplied name is defined in the core JDK {@code java.lang.annotation} package.
158+
*
159+
* @param annotationType the name of the annotation type to check
160+
*
161+
* @return {@code true} if the annotation is in the {@code java.lang.annotation} package
162+
*
163+
* @since 1.7.0
164+
*/
165+
public static boolean isInJavaLangAnnotationPackage (String annotationType) {
166+
return annotationType != null && annotationType.startsWith("java.lang.annotation");
167+
}
168+
169+
private static <T extends Annotation> T findAnnotation (Method method,
170+
Class<T> annotationType,
171+
Set<Annotation> visited
172+
) {
173+
T result = findAnnotation((AnnotatedElement) method, annotationType, visited);
174+
if (result != null) {
175+
return result;
176+
}
177+
Class<?> declaringClass = method.getDeclaringClass();
178+
return ofNullable(declaringClass.getSuperclass())
179+
.filter(it -> it != Object.class)
180+
.map(it -> findAnnotation(it, method, annotationType, visited))
181+
.filter(Objects::nonNull)
182+
.orElseGet(() -> Stream.of(declaringClass.getInterfaces())
183+
.map(it -> findAnnotation(it, method, annotationType, visited))
184+
.filter(Objects::nonNull)
185+
.findAny()
186+
.orElse(null));
187+
}
188+
189+
private static <T extends Annotation> T findAnnotation (Class<?> clazz,
190+
Method method,
191+
Class<T> annotationType,
192+
Set<Annotation> visited
193+
) {
194+
return Stream.of(clazz.getDeclaredMethods())
195+
.filter(it -> method.getName().equals(it.getName()))
196+
.filter(it -> Arrays.equals(method.getParameterTypes(), it.getParameterTypes()))
197+
.findAny()
198+
.map(it -> findAnnotation(it, annotationType, visited))
199+
.orElse(null);
200+
}
201+
202+
private static <T extends Annotation> T findAnnotation (Class<?> clazz,
203+
Class<T> annotationType,
204+
Set<Annotation> visited
205+
) {
206+
T result = findAnnotation((AnnotatedElement) clazz, annotationType, visited);
207+
if (result != null) {
208+
return result;
209+
}
210+
for (Class<?> type : clazz.getInterfaces()) {
211+
T annotation = findAnnotation(type, annotationType, visited);
212+
if (annotation != null) {
213+
return annotation;
214+
}
215+
}
216+
Class<?> superclass = clazz.getSuperclass();
217+
if (superclass == null || Object.class == superclass) {
218+
return null;
219+
}
220+
return findAnnotation(superclass, annotationType, visited);
221+
}
222+
223+
@SuppressWarnings("unchecked")
224+
private static <T extends Annotation> T findAnnotation (AnnotatedElement annotatedElement,
225+
Class<T> annotationType,
226+
Set<Annotation> visited
227+
) {
228+
Annotation[] declaredAnnotations = annotatedElement.getDeclaredAnnotations();
229+
for (Annotation annotation : declaredAnnotations) {
230+
if (annotation.annotationType() == annotationType) {
231+
return (T) annotation;
232+
}
233+
}
234+
for (Annotation annotation : declaredAnnotations) {
235+
if (isInJavaLangAnnotationPackage(annotation) || !visited.add(annotation)) {
236+
continue;
237+
}
238+
T result = findAnnotation(annotation.annotationType(), annotationType, visited);
239+
if (result != null) {
240+
return result;
241+
}
242+
}
243+
return null;
244+
}
245+
246+
private AnnotationUtils () {
247+
}
248+
}

0 commit comments

Comments
 (0)