/* * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.processor.compat; import com.google.common.base.Preconditions; import com.google.common.collect.HashBasedTable; import com.google.common.collect.Iterables; import com.google.common.collect.Table; import com.sun.source.tree.CompilationUnitTree; import com.sun.source.tree.LineMap; import com.sun.source.tree.Tree; import com.sun.source.util.SourcePositions; import com.sun.source.util.TreePath; import com.sun.source.util.Trees; import java.util.ArrayList; import java.util.List; import java.util.Set; import javax.annotation.Nullable; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.Messager; import javax.annotation.processing.ProcessingEnvironment; import javax.annotation.processing.RoundEnvironment; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.Element; import javax.lang.model.element.PackageElement; import javax.lang.model.element.QualifiedNameable; import javax.lang.model.element.TypeElement; import javax.lang.model.util.Elements; import javax.lang.model.util.Types; /** * Abstract annotation processor that goes over annotated elements in bulk (per .class file). * *
It expects only one supported annotation, i.e. {@link #getSupportedAnnotationTypes()} must * return one annotation type only. * *
Annotated elements are pre-filtered by {@link #ignoreAnnotatedElement(Element,
* AnnotationMirror)}. Afterwards, the table with package and enclosing element name into list of
* elements is generated and passed to {@link #process(TypeElement, Table)}.
*/
public abstract class SingleAnnotationProcessor extends AbstractProcessor {
protected Elements elements;
protected Messager messager;
protected SourcePositions sourcePositions;
protected Trees trees;
protected Types types;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
this.elements = processingEnv.getElementUtils();
this.messager = processingEnv.getMessager();
this.trees = Trees.instance(processingEnv);
this.types = processingEnv.getTypeUtils();
this.sourcePositions = trees.getSourcePositions();
}
@Override
public boolean process(
Set extends TypeElement> annotations, RoundEnvironment roundEnvironment) {
if (annotations.size() == 0) {
// no annotations to process, doesn't really matter what we return here.
return true;
}
TypeElement annotation = Iterables.getOnlyElement(annotations);
String supportedAnnotation = Iterables.getOnlyElement(getSupportedAnnotationTypes());
Preconditions.checkState(supportedAnnotation.equals(annotation.toString()));
Table We are not using a class to avoid choosing which Java 9 Module to select from, in case
* the annotation is present in base module and unnamed module.
*/
protected final AnnotationMirror getSupportedAnnotationMirror(TypeElement annotation,
Element element) {
for (AnnotationMirror mirror : element.getAnnotationMirrors()) {
if (types.isSameType(annotation.asType(), mirror.getAnnotationType())) {
return mirror;
}
}
return null;
}
/**
* Returns {@link SourcePosition} of an annotation on the given element or null if position is
* not found.
*/
@Nullable
protected final SourcePosition getSourcePosition(Element element,
AnnotationMirror annotationMirror) {
TreePath path = trees.getPath(element, annotationMirror);
if (path == null) {
return null;
}
CompilationUnitTree compilationUnit = path.getCompilationUnit();
Tree tree = path.getLeaf();
long startPosition = sourcePositions.getStartPosition(compilationUnit, tree);
long endPosition = sourcePositions.getEndPosition(compilationUnit, tree);
LineMap lineMap = path.getCompilationUnit().getLineMap();
return new SourcePosition(
compilationUnit.getSourceFile().getName(),
lineMap.getLineNumber(startPosition),
lineMap.getColumnNumber(startPosition),
lineMap.getLineNumber(endPosition),
lineMap.getColumnNumber(endPosition));
}
@Nullable
protected final AnnotationValue getAnnotationValue(
Element element, AnnotationMirror annotation, String propertyName) {
return annotation.getElementValues().keySet().stream()
.filter(key -> propertyName.equals(key.getSimpleName().toString()))
.map(key -> annotation.getElementValues().get(key))
.reduce((a, b) -> {
throw new IllegalStateException(
String.format("Only one %s expected, found %s in %s",
propertyName, annotation, element));
})
.orElse(null);
}
/**
* Returns a name of an enclosing element without the package name.
*
* This would return names of all enclosing classes, e.g. Outer.Inner.Foo
.
*/
private String getEnclosingElementName(Element element) {
String fullQualifiedName =
((QualifiedNameable) element.getEnclosingElement()).getQualifiedName().toString();
String packageName = elements.getPackageOf(element).toString();
return fullQualifiedName.substring(packageName.length() + 1);
}
}