package checkers.util.debug;
import java.io.PrintStream;
import java.lang.reflect.Constructor;
import java.util.Collections;
import java.util.List;
import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.*;
import javax.lang.model.util.AbstractElementVisitor6;
import checkers.source.*;
import checkers.types.AnnotatedTypeFactory;
import checkers.types.AnnotatedTypeMirror;
import checkers.types.AnnotatedTypeMirror.*;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.util.AbstractTypeProcessor;
import com.sun.source.util.TreePath;
import com.sun.tools.javac.processing.JavacProcessingEnvironment;
import com.sun.tools.javac.util.Context;
/**
* Outputs the method signatures of a class with fully annotated types.
*
* The class determines the effective annotations for a checker in source or
* the classfile. Finding the effective annotations is useful for the
* following purposes:
*
* <ol>
* <li id="1">Debugging annotations in classfile</li>
* <li id="2">Debugging the default annotations that are implicitly added
* by the checker</li>
* </ol>
*
* <p>The class can be used in two possible ways, depending on the type file:
*
* <ol>
* <li id="a">From source: the class is to be used as an annotation processor
* when reading annotations from source. It can be invoked via the command:
* <p><code>javac -processor SignaturePrinter <java files> ...</code>
*
* <li id="b">From classfile: the class is to be used as an independent app
* when reading annotations from classfile. It can be invoked via the command:
* <p><code>java SignaturePrinter <class name></code>
*
* </ol>
*
* By default, only the annotations explicitly written by the user are emitted.
* To view the default and effective annotations in a class that are associated
* with a checker, the fully qualified name of the checker needs to be passed
* as '-Achecker=' argument, e.g.
* <p><code>javac -processor SignaturePrinter
* -Achecker=checkers.nullness.NullnessChecker JavaFile.java</code>
*/
@SupportedSourceVersion(SourceVersion.RELEASE_7)
@SupportedAnnotationTypes("*")
@SupportedOptions("checker")
public class SignaturePrinter extends AbstractTypeProcessor {
private SourceChecker checker;
///////// Initialization /////////////
private void init(ProcessingEnvironment env, String checkerName) {
if (checkerName != null) {
try {
Class<?> checkerClass = Class.forName(checkerName);
Constructor<?> cons = checkerClass.getConstructor();
checker = (SourceChecker)cons.newInstance();
} catch (Exception e) {
throw new RuntimeException(e);
}
} else {
checker = new SourceChecker() {
@Override
protected SourceVisitor<?, ?> createSourceVisitor(
CompilationUnitTree root) {
return null;
}
};
}
checker.init(env);
}
@Override
public void init(ProcessingEnvironment env) {
super.init(env);
String checkerName = env.getOptions().get("checker");
init(env, checkerName);
}
@Override
public void typeProcess(TypeElement element, TreePath p) {
checker.currentPath = p;
CompilationUnitTree root = p != null ? p.getCompilationUnit() : null;
ElementPrinter printer = new ElementPrinter(checker.createFactory(root), System.out);
printer.visit(element);
}
////////// Printer //////////
static class ElementPrinter extends AbstractElementVisitor6<Void, Void> {
private final static String INDENTION = " ";
private PrintStream out;
private String indent = "";
private AnnotatedTypeFactory factory;
public ElementPrinter(AnnotatedTypeFactory factory, PrintStream out) {
this.factory = factory;
this.out = out;
}
public void printTypeParams(List<? extends AnnotatedTypeVariable> params) {
if (params.isEmpty())
return;
out.print("<");
boolean isntFirst = false;
for (AnnotatedTypeMirror param : params) {
if (isntFirst)
out.print(", ");
isntFirst = true;
out.print(param);
}
out.print("> ");
}
public void printParameters(AnnotatedExecutableType type) {
ExecutableElement elem = type.getElement();
out.print("(");
for (int i = 0; i < type.getParameterTypes().size(); ++i) {
if (i != 0)
out.print(", ");
printVariable(type.getParameterTypes().get(i),
elem.getParameters().get(i).getSimpleName());
}
out.print(")");
}
public void printThrows(AnnotatedExecutableType type) {
if (type.getThrownTypes().isEmpty())
return;
out.print(" throws ");
boolean isntFirst = false;
for (AnnotatedTypeMirror thrown : type.getThrownTypes()) {
if (isntFirst)
out.print(", ");
isntFirst = true;
out.print(thrown);
}
}
public void printVariable(AnnotatedTypeMirror type, Name name, boolean isVarArg) {
out.print(type);
if (isVarArg)
out.println("...");
out.print(' ');
out.print(name);
}
public void printVariable(AnnotatedTypeMirror type, Name name) {
printVariable(type, name, false);
}
public void printType(AnnotatedTypeMirror type) {
out.print(type);
out.print(' ');
}
public void printName(CharSequence name) {
out.print(name);
}
@Override
public Void visitExecutable(ExecutableElement e, Void p) {
out.print(indent);
AnnotatedExecutableType type = factory.getAnnotatedType(e);
printTypeParams(type.getTypeVariables());
if (e.getKind() != ElementKind.CONSTRUCTOR) {
printType(type.getReturnType());
}
printName(e.getSimpleName());
printParameters(type);
printThrows(type);
out.println(';');
return null;
}
@Override
public Void visitPackage(PackageElement e, Void p) {
throw new IllegalArgumentException("Cannot process packages");
}
private String typeIdentifier(TypeElement e) {
switch (e.getKind()) {
case INTERFACE: return "interface";
case CLASS: return "class";
case ANNOTATION_TYPE: return "@interface";
case ENUM: return "enum";
default:
throw new IllegalArgumentException("Not a type element: " + e.getKind());
}
}
@Override
public Void visitType(TypeElement e, Void p) {
String prevIndent = indent;
out.print(indent);
out.print(typeIdentifier(e));
out.print(' ');
out.print(e.getSimpleName());
out.print(' ');
AnnotatedDeclaredType dt = factory.getAnnotatedType(e);
printSupers(dt);
out.println("{");
indent += INDENTION;
for (Element enclosed : e.getEnclosedElements())
this.visit(enclosed);
indent = prevIndent;
out.print(indent);
out.println("}");
return null;
}
private void printSupers(AnnotatedDeclaredType dt) {
if (dt.directSuperTypes().isEmpty())
return;
out.print("extends ");
boolean isntFirst = false;
for (AnnotatedDeclaredType st : dt.directSuperTypes()) {
if (isntFirst)
out.print(", ");
isntFirst = true;
out.print(st);
}
out.print(' ');
}
@Override
public Void visitTypeParameter(TypeParameterElement e, Void p) {
throw new IllegalStateException("Shouldn't visit any type parameters");
}
@Override
public Void visitVariable(VariableElement e, Void p) {
if (!e.getKind().isField())
throw new IllegalStateException("can only process fields, received " + e.getKind());
out.print(indent);
AnnotatedTypeMirror type = factory.getAnnotatedType(e);
this.printVariable(type, e.getSimpleName());
out.println(';');
return null;
}
}
public static void printUsage() {
System.out.println(" Usage: java SignaturePrinter [-Achecker=<checkerName>] classname");
}
private static final String CHECKER_ARG = "-Achecker=";
public static void main(String[] args) {
if (!(args.length == 1 && !args[0].startsWith(CHECKER_ARG))
&& !(args.length == 2 && args[0].startsWith(CHECKER_ARG))) {
printUsage();
return;
}
// process arguments
String checkerName = "";
if (args[0].startsWith(CHECKER_ARG))
checkerName = args[0].substring(CHECKER_ARG.length());
// Setup compiler environment
Context context = new Context();
JavacProcessingEnvironment env = new JavacProcessingEnvironment(context, Collections.<Processor>emptyList());
SignaturePrinter printer = new SignaturePrinter();
printer.init(env, checkerName);
String className = args[args.length - 1];
TypeElement elem = env.getElementUtils().getTypeElement(className);
if (elem == null) {
System.err.println("Couldn't find class: " + className);
return;
}
printer.typeProcess(elem, null);
}
}