1 /*
2  * Copyright (C) 2020 The Android Open Source Project
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 android.processor.compat;
18 
19 import com.google.common.base.Preconditions;
20 import com.google.common.collect.HashBasedTable;
21 import com.google.common.collect.Iterables;
22 import com.google.common.collect.Table;
23 import com.sun.source.tree.CompilationUnitTree;
24 import com.sun.source.tree.LineMap;
25 import com.sun.source.tree.Tree;
26 import com.sun.source.util.SourcePositions;
27 import com.sun.source.util.TreePath;
28 import com.sun.source.util.Trees;
29 
30 import java.util.ArrayList;
31 import java.util.List;
32 import java.util.Set;
33 
34 import javax.annotation.Nullable;
35 import javax.annotation.processing.AbstractProcessor;
36 import javax.annotation.processing.Messager;
37 import javax.annotation.processing.ProcessingEnvironment;
38 import javax.annotation.processing.RoundEnvironment;
39 import javax.lang.model.element.AnnotationMirror;
40 import javax.lang.model.element.AnnotationValue;
41 import javax.lang.model.element.Element;
42 import javax.lang.model.element.PackageElement;
43 import javax.lang.model.element.QualifiedNameable;
44 import javax.lang.model.element.TypeElement;
45 import javax.lang.model.util.Elements;
46 import javax.lang.model.util.Types;
47 
48 /**
49  * Abstract annotation processor that goes over annotated elements in bulk (per .class file).
50  *
51  * <p>It expects only one supported annotation, i.e. {@link #getSupportedAnnotationTypes()} must
52  * return one annotation type only.
53  *
54  * <p>Annotated elements are pre-filtered by {@link #ignoreAnnotatedElement(Element,
55  * AnnotationMirror)}. Afterwards, the table with package and enclosing element name into list of
56  * elements is generated and passed to {@link #process(TypeElement, Table)}.
57  */
58 public abstract class SingleAnnotationProcessor extends AbstractProcessor {
59 
60     protected Elements elements;
61     protected Messager messager;
62     protected SourcePositions sourcePositions;
63     protected Trees trees;
64     protected Types types;
65 
66     @Override
init(ProcessingEnvironment processingEnv)67     public synchronized void init(ProcessingEnvironment processingEnv) {
68         super.init(processingEnv);
69 
70         this.elements = processingEnv.getElementUtils();
71         this.messager = processingEnv.getMessager();
72         this.trees = Trees.instance(processingEnv);
73         this.types = processingEnv.getTypeUtils();
74 
75         this.sourcePositions = trees.getSourcePositions();
76     }
77 
78     @Override
process( Set<? extends TypeElement> annotations, RoundEnvironment roundEnvironment)79     public boolean process(
80             Set<? extends TypeElement> annotations, RoundEnvironment roundEnvironment) {
81         if (annotations.size() == 0) {
82             // no annotations to process, doesn't really matter what we return here.
83             return true;
84         }
85 
86         TypeElement annotation = Iterables.getOnlyElement(annotations);
87         String supportedAnnotation = Iterables.getOnlyElement(getSupportedAnnotationTypes());
88         Preconditions.checkState(supportedAnnotation.equals(annotation.toString()));
89 
90         Table<PackageElement, String, List<Element>> annotatedElements = HashBasedTable.create();
91         for (Element annotatedElement : roundEnvironment.getElementsAnnotatedWith(annotation)) {
92             AnnotationMirror annotationMirror =
93                     getSupportedAnnotationMirror(annotation, annotatedElement);
94             if (ignoreAnnotatedElement(annotatedElement, annotationMirror)) {
95                 continue;
96             }
97 
98             PackageElement packageElement = elements.getPackageOf(annotatedElement);
99             String enclosingElementName = getEnclosingElementName(annotatedElement);
100             Preconditions.checkNotNull(packageElement);
101             Preconditions.checkNotNull(enclosingElementName);
102 
103             if (!annotatedElements.contains(packageElement, enclosingElementName)) {
104                 annotatedElements.put(packageElement, enclosingElementName, new ArrayList<>());
105             }
106             annotatedElements.get(packageElement, enclosingElementName).add(annotatedElement);
107         }
108 
109         process(annotation, annotatedElements);
110         return true;
111     }
112 
113     /**
114      * Processes a set of elements annotated with supported annotation and not ignored via {@link
115      * #ignoreAnnotatedElement(Element, AnnotationMirror)}.
116      *
117      * @param annotation        {@link TypeElement} of the supported annotation
118      * @param annotatedElements table with {@code package}, {@code enclosing elements name}, and the
119      *                          list of elements
120      */
process(TypeElement annotation, Table<PackageElement, String, List<Element>> annotatedElements)121     protected abstract void process(TypeElement annotation,
122             Table<PackageElement, String, List<Element>> annotatedElements);
123 
124     /** Whether to process given element with the annotation mirror. */
ignoreAnnotatedElement(Element element, AnnotationMirror mirror)125     protected boolean ignoreAnnotatedElement(Element element, AnnotationMirror mirror) {
126         return false;
127     }
128 
129     /**
130      * Returns the annotation mirror for the supported annotation on the given element.
131      *
132      * <p>We are not using a class to avoid choosing which Java 9 Module to select from, in case
133      * the annotation is present in base module and unnamed module.
134      */
getSupportedAnnotationMirror(TypeElement annotation, Element element)135     protected final AnnotationMirror getSupportedAnnotationMirror(TypeElement annotation,
136             Element element) {
137         for (AnnotationMirror mirror : element.getAnnotationMirrors()) {
138             if (types.isSameType(annotation.asType(), mirror.getAnnotationType())) {
139                 return mirror;
140             }
141         }
142         return null;
143     }
144 
145     /**
146      * Returns {@link SourcePosition} of an annotation on the given element or null if position is
147      * not found.
148      */
149     @Nullable
getSourcePosition(Element element, AnnotationMirror annotationMirror)150     protected final SourcePosition getSourcePosition(Element element,
151             AnnotationMirror annotationMirror) {
152         TreePath path = trees.getPath(element, annotationMirror);
153         if (path == null) {
154             return null;
155         }
156         CompilationUnitTree compilationUnit = path.getCompilationUnit();
157         Tree tree = path.getLeaf();
158         long startPosition = sourcePositions.getStartPosition(compilationUnit, tree);
159         long endPosition = sourcePositions.getEndPosition(compilationUnit, tree);
160 
161         LineMap lineMap = path.getCompilationUnit().getLineMap();
162         return new SourcePosition(
163                 compilationUnit.getSourceFile().getName(),
164                 lineMap.getLineNumber(startPosition),
165                 lineMap.getColumnNumber(startPosition),
166                 lineMap.getLineNumber(endPosition),
167                 lineMap.getColumnNumber(endPosition));
168     }
169 
170     @Nullable
getAnnotationValue( Element element, AnnotationMirror annotation, String propertyName)171     protected final AnnotationValue getAnnotationValue(
172             Element element, AnnotationMirror annotation, String propertyName) {
173         return annotation.getElementValues().keySet().stream()
174                 .filter(key -> propertyName.equals(key.getSimpleName().toString()))
175                 .map(key -> annotation.getElementValues().get(key))
176                 .reduce((a, b) -> {
177                     throw new IllegalStateException(
178                             String.format("Only one %s expected, found %s in %s",
179                                     propertyName, annotation, element));
180                 })
181                 .orElse(null);
182     }
183 
184     /**
185      * Returns a name of an enclosing element without the package name.
186      *
187      * <p>This would return names of all enclosing classes, e.g. <code>Outer.Inner.Foo</code>.
188      */
getEnclosingElementName(Element element)189     private String getEnclosingElementName(Element element) {
190         String fullQualifiedName =
191                 ((QualifiedNameable) element.getEnclosingElement()).getQualifiedName().toString();
192         String packageName = elements.getPackageOf(element).toString();
193         return fullQualifiedName.substring(packageName.length() + 1);
194     }
195 
196 }
197