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