| package org.checkerframework.common.util.count; |
| |
| import com.sun.source.tree.AnnotationTree; |
| import com.sun.source.tree.ArrayTypeTree; |
| import com.sun.source.tree.ClassTree; |
| import com.sun.source.tree.InstanceOfTree; |
| import com.sun.source.tree.MethodInvocationTree; |
| import com.sun.source.tree.MethodTree; |
| import com.sun.source.tree.NewArrayTree; |
| import com.sun.source.tree.NewClassTree; |
| import com.sun.source.tree.ParameterizedTypeTree; |
| import com.sun.source.tree.Tree; |
| import com.sun.source.tree.TypeCastTree; |
| import com.sun.source.tree.TypeParameterTree; |
| import com.sun.source.tree.VariableTree; |
| import com.sun.source.tree.WildcardTree; |
| import com.sun.source.util.TreePath; |
| import com.sun.tools.javac.tree.JCTree.JCAnnotation; |
| import com.sun.tools.javac.util.Log; |
| import java.util.HashMap; |
| import java.util.Map; |
| import java.util.TreeSet; |
| import javax.annotation.processing.SupportedSourceVersion; |
| import javax.lang.model.SourceVersion; |
| import javax.lang.model.element.Name; |
| import org.checkerframework.framework.source.SourceChecker; |
| import org.checkerframework.framework.source.SourceVisitor; |
| import org.checkerframework.framework.source.SupportedOptions; |
| import org.checkerframework.javacutil.AnnotationProvider; |
| |
| /** |
| * An annotation processor for listing the potential locations of annotations. To invoke it, use |
| * |
| * <pre> |
| * javac -proc:only -processor org.checkerframework.common.util.count.AnnotationStatistics <em>MyFile.java ...</em> |
| * </pre> |
| * |
| * <p>You probably want to pipe the output through another program: |
| * |
| * <ul> |
| * <li>Total annotation count: {@code ... | wc}. |
| * <li>Breakdown by location type: {@code ... | sort | uniq -c} |
| * <li>Count for only certain location types: use {@code grep} |
| * </ul> |
| * |
| * <p>By default, this utility displays annotation locations only. The following two options may be |
| * used to adjust the output: |
| * |
| * <ul> |
| * <li>{@code -Aannotations}: prints information about the annotations |
| * <li>{@code -Anolocations}: suppresses location output; only makes sense in conjunction with |
| * {@code -Aannotations} |
| * <li>{@code -Aannotationsummaryonly}: with both of the obove, only outputs a summary |
| * </ul> |
| * |
| * @see JavaCodeStatistics |
| */ |
| /* |
| * TODO: add an option to only list declaration or type annotations. |
| * This e.g. influences the output of "method return", which is only valid |
| * for type annotations for non-void methods. |
| */ |
| @SupportedOptions({"nolocations", "annotations", "annotationsummaryonly"}) |
| @SupportedSourceVersion(SourceVersion.RELEASE_8) |
| public class AnnotationStatistics extends SourceChecker { |
| |
| /** |
| * Map from annotation name (as the toString() of its Name representation) to number of times the |
| * annotation was written in source code. |
| */ |
| final Map<String, Integer> annotationCount = new HashMap<>(); |
| |
| /** Creates an AnnotationStatistics. */ |
| public AnnotationStatistics() { |
| // This checker never issues any warnings, so don't warn about |
| // @SuppressWarnings("allcheckers:..."). |
| this.useAllcheckersPrefix = false; |
| } |
| |
| @Override |
| public void typeProcessingOver() { |
| Log log = getCompilerLog(); |
| if (log.nerrors != 0) { |
| System.out.println("Not counting annotations, because compilation issued an error."); |
| } else if (annotationCount.isEmpty()) { |
| System.out.println("No annotations found."); |
| } else { |
| System.out.println("Found annotations: "); |
| for (String key : new TreeSet<>(annotationCount.keySet())) { |
| System.out.println(key + "\t" + annotationCount.get(key)); |
| } |
| } |
| super.typeProcessingOver(); |
| } |
| |
| /** Increment the number of times annotation with name {@code annoName} has appeared. */ |
| protected void incrementCount(Name annoName) { |
| String annoString = annoName.toString(); |
| if (!annotationCount.containsKey(annoString)) { |
| annotationCount.put(annoString, 1); |
| } else { |
| annotationCount.put(annoString, annotationCount.get(annoString) + 1); |
| } |
| } |
| |
| @Override |
| protected SourceVisitor<?, ?> createSourceVisitor() { |
| return new Visitor(this); |
| } |
| |
| class Visitor extends SourceVisitor<Void, Void> { |
| |
| /** Whether annotation locations should be printed. */ |
| private final boolean locations; |
| |
| /** Whether annotation details should be printed. */ |
| private final boolean annotations; |
| |
| /** Whether only a summary should be printed. */ |
| private final boolean annotationsummaryonly; |
| |
| /** |
| * Create a new Visitor. |
| * |
| * @param l the AnnotationStatistics object, used for obtaining command-line arguments |
| */ |
| public Visitor(AnnotationStatistics l) { |
| super(l); |
| |
| locations = !l.hasOption("nolocations"); |
| annotations = l.hasOption("annotations"); |
| annotationsummaryonly = l.hasOption("annotationsummaryonly"); |
| } |
| |
| @Override |
| public Void visitAnnotation(AnnotationTree tree, Void p) { |
| if (annotations) { |
| Name annoName = ((JCAnnotation) tree).annotationType.type.tsym.getQualifiedName(); |
| incrementCount(annoName); |
| |
| // An annotation is a body annotation if, while ascending the AST from the annotation to the |
| // root, we find a block immediately enclosed by a method. |
| // |
| // If an annotation is not a body annotation, it's a signature (declaration) annotation. |
| |
| boolean isBodyAnnotation = false; |
| TreePath path = getCurrentPath(); |
| Tree prev = null; |
| for (Tree t : path) { |
| if (prev != null |
| && prev.getKind() == Tree.Kind.BLOCK |
| && t.getKind() == Tree.Kind.METHOD) { |
| isBodyAnnotation = true; |
| break; |
| } |
| prev = t; |
| } |
| |
| if (!annotationsummaryonly) { |
| System.out.printf( |
| ":annotation %s %s %s %s%n", |
| tree.getAnnotationType(), |
| tree, |
| root.getSourceFile().getName(), |
| (isBodyAnnotation ? "body" : "sig")); |
| } |
| } |
| return super.visitAnnotation(tree, p); |
| } |
| |
| @Override |
| public Void visitArrayType(ArrayTypeTree tree, Void p) { |
| if (locations) { |
| System.out.println("array type"); |
| } |
| return super.visitArrayType(tree, p); |
| } |
| |
| @Override |
| public Void visitClass(ClassTree tree, Void p) { |
| if (shouldSkipDefs(tree)) { |
| // Not "return super.visitClass(classTree, p);" because that would recursively call visitors |
| // on subtrees; we want to skip the class entirely. |
| return null; |
| } |
| if (locations) { |
| System.out.println("class"); |
| if (tree.getExtendsClause() != null) { |
| System.out.println("class extends"); |
| } |
| for (@SuppressWarnings("unused") Tree t : tree.getImplementsClause()) { |
| System.out.println("class implements"); |
| } |
| } |
| return super.visitClass(tree, p); |
| } |
| |
| @Override |
| public Void visitMethod(MethodTree tree, Void p) { |
| if (locations) { |
| System.out.println("method return"); |
| System.out.println("method receiver"); |
| for (@SuppressWarnings("unused") Tree t : tree.getThrows()) { |
| System.out.println("method throws"); |
| } |
| for (@SuppressWarnings("unused") Tree t : tree.getParameters()) { |
| System.out.println("method param"); |
| } |
| } |
| return super.visitMethod(tree, p); |
| } |
| |
| @Override |
| public Void visitVariable(VariableTree tree, Void p) { |
| if (locations) { |
| System.out.println("variable"); |
| } |
| return super.visitVariable(tree, p); |
| } |
| |
| @Override |
| public Void visitMethodInvocation(MethodInvocationTree tree, Void p) { |
| if (locations) { |
| for (@SuppressWarnings("unused") Tree t : tree.getTypeArguments()) { |
| System.out.println("method invocation type argument"); |
| } |
| } |
| return super.visitMethodInvocation(tree, p); |
| } |
| |
| @Override |
| public Void visitNewClass(NewClassTree tree, Void p) { |
| if (locations) { |
| System.out.println("new class"); |
| for (@SuppressWarnings("unused") Tree t : tree.getTypeArguments()) { |
| System.out.println("new class type argument"); |
| } |
| } |
| return super.visitNewClass(tree, p); |
| } |
| |
| @Override |
| public Void visitNewArray(NewArrayTree tree, Void p) { |
| if (locations) { |
| System.out.println("new array"); |
| for (@SuppressWarnings("unused") Tree t : tree.getDimensions()) { |
| System.out.println("new array dimension"); |
| } |
| } |
| return super.visitNewArray(tree, p); |
| } |
| |
| @Override |
| public Void visitTypeCast(TypeCastTree tree, Void p) { |
| if (locations) { |
| System.out.println("typecast"); |
| } |
| return super.visitTypeCast(tree, p); |
| } |
| |
| @Override |
| public Void visitInstanceOf(InstanceOfTree tree, Void p) { |
| if (locations) { |
| System.out.println("instanceof"); |
| } |
| return super.visitInstanceOf(tree, p); |
| } |
| |
| @Override |
| public Void visitParameterizedType(ParameterizedTypeTree tree, Void p) { |
| if (locations) { |
| for (@SuppressWarnings("unused") Tree t : tree.getTypeArguments()) { |
| System.out.println("parameterized type"); |
| } |
| } |
| return super.visitParameterizedType(tree, p); |
| } |
| |
| @Override |
| public Void visitTypeParameter(TypeParameterTree tree, Void p) { |
| if (locations) { |
| for (@SuppressWarnings("unused") Tree t : tree.getBounds()) { |
| System.out.println("type parameter bound"); |
| } |
| } |
| return super.visitTypeParameter(tree, p); |
| } |
| |
| @Override |
| public Void visitWildcard(WildcardTree tree, Void p) { |
| if (locations) { |
| System.out.println("wildcard"); |
| } |
| return super.visitWildcard(tree, p); |
| } |
| } |
| |
| @Override |
| public AnnotationProvider getAnnotationProvider() { |
| throw new UnsupportedOperationException( |
| "getAnnotationProvider is not implemented for this class."); |
| } |
| } |