1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.grouplens.grapht.annotation;
21
22 import javax.annotation.processing.AbstractProcessor;
23 import javax.annotation.processing.Messager;
24 import javax.annotation.processing.RoundEnvironment;
25 import javax.inject.Qualifier;
26 import javax.lang.model.SourceVersion;
27 import javax.lang.model.element.AnnotationMirror;
28 import javax.lang.model.element.Element;
29 import javax.lang.model.element.Name;
30 import javax.lang.model.element.TypeElement;
31 import javax.tools.Diagnostic;
32
33 import java.lang.annotation.Annotation;
34 import java.lang.annotation.Documented;
35 import java.lang.annotation.Retention;
36 import java.lang.annotation.RetentionPolicy;
37 import java.util.HashSet;
38 import java.util.Set;
39 import java.util.regex.Pattern;
40
41
42
43
44 public class AnnotationValidator extends AbstractProcessor {
45 private static final String DEFAULT_ANNOT_PREFIX = "org.grouplens.grapht.annotation.Default";
46 private static final Pattern DEFAULT_PATTERN = Pattern.compile("^" + Pattern.quote(DEFAULT_ANNOT_PREFIX));
47
48 @Override
49 public SourceVersion getSupportedSourceVersion() {
50
51
52
53 SourceVersion[] versions = SourceVersion.values();
54 SourceVersion v6 = SourceVersion.RELEASE_6;
55 assert v6.ordinal() < versions.length;
56
57 return versions[Math.min(v6.ordinal() + 2, versions.length - 1)];
58 }
59
60 @Override
61 public Set<String> getSupportedAnnotationTypes() {
62 Set<String> atypes = new HashSet<String>();
63 atypes.add(Qualifier.class.getName());
64 atypes.add(Attribute.class.getName());
65 atypes.add(AliasFor.class.getName());
66 return atypes;
67 }
68
69 private void warning(Element e, String fmt, Object... args) {
70 Messager log = processingEnv.getMessager();
71 String msg = String.format(fmt, args);
72 log.printMessage(Diagnostic.Kind.MANDATORY_WARNING, msg, e);
73 }
74
75 private void error(Element e, String fmt, Object... args) {
76 Messager log = processingEnv.getMessager();
77 String msg = String.format(fmt, args);
78 log.printMessage(Diagnostic.Kind.ERROR, msg, e);
79 }
80
81 @Override
82 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
83 analyzeQualifiers(roundEnv);
84 analyzeAttributes(roundEnv);
85 analyzeAliases(roundEnv);
86 return false;
87 }
88
89 private void analyzeQualifiers(RoundEnvironment re) {
90 analyzeAnnotations(re, Qualifier.class, "qualifier");
91 }
92
93 private void analyzeAttributes(RoundEnvironment re) {
94 analyzeAnnotations(re, Attribute.class, "attribute");
95 }
96
97 private void analyzeAnnotations(RoundEnvironment re, Class<? extends Annotation> annotation, String annotationName){
98 Set<? extends Element> elts = re.getElementsAnnotatedWith(annotation);
99 for (Element elt: elts) {
100 if (elt.getAnnotation(Documented.class) == null) {
101 warning(elt, String.format("%s annotation should be @Documented", annotationName));
102 }
103 Retention ret = elt.getAnnotation(Retention.class);
104 if (ret == null || !ret.value().equals(RetentionPolicy.RUNTIME)) {
105 error(elt, String.format("%s annotation must have RUNTIME retention", annotationName));
106 }
107 }
108 }
109
110 private void analyzeAliases(RoundEnvironment re) {
111 Set<? extends Element> elts = re.getElementsAnnotatedWith(AliasFor.class);
112 for (Element elt : elts) {
113 if (elt.getAnnotation(Qualifier.class) == null) {
114 error(elt, "alias annotation must also be a qualifier");
115 }
116 if (elt.getAnnotation(AllowUnqualifiedMatch.class) != null) {
117 error(elt, "alias annotation has @AllowUnqualifiedMatch (should be on alias target)");
118 }
119 for (AnnotationMirror mirror: elt.getAnnotationMirrors()) {
120 Element element = mirror.getAnnotationType().asElement();
121 if (element instanceof TypeElement) {
122 TypeElement type = (TypeElement) element;
123 Name name = type.getQualifiedName();
124 if (DEFAULT_PATTERN.matcher(name).matches()) {
125 warning(elt, "alias annotation has %s, defaults should be on alias target", type.getSimpleName());
126 }
127 }
128 }
129 }
130 }
131 }