View Javadoc

1   /*
2    * Grapht, an open source dependency injector.
3    * Copyright 2014-2015 various contributors (see CONTRIBUTORS.txt)
4    * Copyright 2010-2014 Regents of the University of Minnesota
5    *
6    * This program is free software; you can redistribute it and/or modify
7    * it under the terms of the GNU Lesser General Public License as
8    * published by the Free Software Foundation; either version 2.1 of the
9    * License, or (at your option) any later version.
10   *
11   * This program is distributed in the hope that it will be useful, but WITHOUT
12   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13   * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
14   * details.
15   *
16   * You should have received a copy of the GNU General Public License along with
17   * this program; if not, write to the Free Software Foundation, Inc., 51
18   * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
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   * Annotation processor that checks and validates DI annotations.
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          // support version 6 or 7
51          // we can't compile against RELEASE_7 and maintain Java 6 compatibility, but the
52          // processor is Java 7-compatible. We have not tested against Java 8, however.
53          SourceVersion[] versions = SourceVersion.values();
54          SourceVersion v6 = SourceVersion.RELEASE_6;
55          assert v6.ordinal() < versions.length;
56          // we support up through Java 8
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; // let other processors work too
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 }