1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
|
package foo.zaaarf.routecompass;
import org.springframework.web.bind.annotation.*;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;
import java.lang.annotation.Annotation;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class RouteCompass extends AbstractProcessor {
private final HashSet<Route> foundRoutes = new HashSet<>();
private final HashSet<Class<? extends Annotation>> annotationClasses = new HashSet<>();
public RouteCompass() {
annotationClasses.add(RequestMapping.class);
annotationClasses.add(GetMapping.class);
annotationClasses.add(PostMapping.class);
annotationClasses.add(PutMapping.class);
annotationClasses.add(DeleteMapping.class);
annotationClasses.add(PatchMapping.class);
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {
for(TypeElement annotationType : annotations) {
env.getElementsAnnotatedWith(annotationType)
.stream()
.filter(elem -> elem instanceof ExecutableElement)
.map(elem -> (ExecutableElement) elem)
.forEach(elem -> this.foundRoutes.add(new Route(
elem.getEnclosingElement().asType().toString(),
this.getFullRoute(annotationType, elem),
this.getRequestMethods(annotationType, elem),
this.isDeprecated(elem)
)));
}
//TODO print
return false; //don't claim them, let spring do its job
}
private String getFullRoute(TypeElement annotationType, Element element) {
@SuppressWarnings("OptionalGetWithoutIsPresent") //find matching annotation class
Class<? extends Annotation> annClass = this.annotationClasses.stream()
.filter(c -> annotationType.getQualifiedName().contentEquals(c.getName()))
.findFirst()
.get(); //should never fail
try {
//it can be both path and value
String pathValue = (String) annClass.getField("path").get(element.getAnnotation(annClass));
String route = pathValue == null
? (String) annClass.getField("value").get(element.getAnnotation(annClass))
: pathValue;
return this.parentOrFallback(element, route, (a, e) -> {
String parent = this.getFullRoute(a, e);
StringBuilder sb = new StringBuilder(parent);
if(!parent.endsWith("/")) sb.append("/");
sb.append(route);
return sb.toString();
});
} catch (ReflectiveOperationException ex) {
throw new RuntimeException(ex); //if it fails something went very wrong
}
}
private RequestMethod[] getRequestMethods(TypeElement annotationType, Element element) {
RequestMethod[] methods = annotationType.getQualifiedName().contentEquals(RequestMapping.class.getName())
? element.getAnnotation(RequestMapping.class).method()
: annotationType.getAnnotation(RequestMapping.class).method();
return methods.length == 0
? this.parentOrFallback(element, methods, this::getRequestMethods)
: methods;
}
private boolean isDeprecated(Element elem) {
return elem.getAnnotation(Deprecated.class) != null
|| elem.getEnclosingElement().getAnnotation(Deprecated.class) != null;
}
private <T> T parentOrFallback(Element element, T fallback, BiFunction<TypeElement, Element, T> fun) {
List<Class<? extends Annotation>> found = this.annotationClasses.stream()
.filter(annClass -> element.getEnclosingElement().getAnnotation(annClass) != null)
.collect(Collectors.toList());
if(found.isEmpty()) return fallback;
if(found.size() > 1) this.processingEnv.getMessager().printMessage(
Diagnostic.Kind.WARNING,
"Found multiple mapping annotations on "
+ element.getSimpleName().toString()
+ ", only one of the will be considered!"
);
return fun.apply(
this.processingEnv.getElementUtils()
.getTypeElement(found.get(0).getName()),
element
);
}
@Override
public Set<String> getSupportedAnnotationTypes() {
return annotationClasses.stream().map(Class::getCanonicalName).collect(Collectors.toSet());
}
}
|