package org.checkerframework.framework.stub;
import com.sun.tools.javac.processing.JavacProcessingEnvironment;
import com.sun.tools.javac.util.Context;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeKind;
import javax.lang.model.util.ElementFilter;
import org.checkerframework.javacutil.ElementUtils;
import org.checkerframework.javacutil.TypesUtils;
/**
* Generates a stub file from a single class or an entire package.
*
* <p>A stub file can be used to add annotations to methods of classes, that are only available in
* binary or the source of which cannot be edited.
*
* @checker_framework.manual #stub-creating-and-using Using stub classes
*/
public class StubGenerator {
/** The indentation for the class */
private static final String INDENTION = " ";
/** The output stream */
private final PrintStream out;
/** the current indentation for the line being processed */
private String currentIndention = "";
/** the package of the class being processed */
private String currentPackage = null;
/** Constructs an instanceof {@code IndexGenerator} that outputs to {@code System.out}. */
public StubGenerator() {
this(System.out);
}
/**
* Constructs an instance of {@code IndexGenerator} that outputs to the provided output stream.
*
* @param out the output stream
*/
public StubGenerator(PrintStream out) {
this.out = out;
}
/**
* Constructs an instance of {@code IndexGenerator} that outputs to the provided output stream.
*
* @param out the output stream
*/
public StubGenerator(OutputStream out) {
this.out = new PrintStream(out);
}
/** Generate the stub file for all the classes within the provided package. */
public void stubFromField(Element elt) {
if (!(elt.getKind() == ElementKind.FIELD)) {
return;
}
String pkg = ElementUtils.getVerboseName((ElementUtils.enclosingPackage(elt)));
if (!"".equals(pkg)) {
currentPackage = pkg;
currentIndention = " ";
indent();
}
VariableElement field = (VariableElement) elt;
printFieldDecl(field);
}
/** Generate the stub file for all the classes within the provided package. */
public void stubFromPackage(PackageElement packageElement) {
currentPackage = packageElement.getQualifiedName().toString();
indent();
out.print("package ");
out.print(currentPackage);
out.println(";");
for (TypeElement element : ElementFilter.typesIn(packageElement.getEnclosedElements())) {
if (isPublicOrProtected(element)) {
out.println();
printClass(element);
}
}
}
/** Generate the stub file for all the classes within the provided package. */
public void stubFromMethod(Element elt) {
if (!(elt.getKind() == ElementKind.CONSTRUCTOR || elt.getKind() == ElementKind.METHOD)) {
return;
}
String newPackage = ElementUtils.getVerboseName(ElementUtils.enclosingPackage(elt));
if (!newPackage.equals("")) {
currentPackage = newPackage;
currentIndention = " ";
indent();
}
ExecutableElement method = (ExecutableElement) elt;
printMethodDecl(method);
}
/** Generate the stub file for provided class. The generated file includes the package name. */
public void stubFromType(TypeElement typeElement) {
// only output stub for classes or interfaces. not enums
if (typeElement.getKind() != ElementKind.CLASS
&& typeElement.getKind() != ElementKind.INTERFACE) return;
String newPackageName =
ElementUtils.getVerboseName(ElementUtils.enclosingPackage(typeElement));
boolean newPackage = !newPackageName.equals(currentPackage);
currentPackage = newPackageName;
if (newPackage) {
indent();
out.print("package ");
out.print(currentPackage);
out.println(";");
out.println();
}
String fullClassName = ElementUtils.getQualifiedClassName(typeElement).toString();
String className =
fullClassName.substring(
fullClassName
//+1 because currentPackage doesn't include
// the . between the package name and the classname
.indexOf(currentPackage)
+ currentPackage.length()
+ 1);
int index = className.lastIndexOf('.');
if (index == -1) {
printClass(typeElement);
} else {
String outer = className.substring(0, index);
printClass(typeElement, outer.replace('.', '$'));
}
}
/** helper method that outputs the index for the provided class. */
private void printClass(TypeElement typeElement) {
printClass(typeElement, null);
}
/** helper method that outputs the index for the provided class. */
private void printClass(TypeElement typeElement, String outerClass) {
List<TypeElement> innerClass = new ArrayList<TypeElement>();
indent();
if (typeElement.getKind() == ElementKind.INTERFACE) {
out.print("interface");
} else if (typeElement.getKind() == ElementKind.CLASS) {
out.print("class");
} else {
return;
}
out.print(' ');
if (outerClass != null) {
out.print(outerClass + "$");
}
out.print(typeElement.getSimpleName());
// Type parameters
if (!typeElement.getTypeParameters().isEmpty()) {
out.print('<');
out.print(formatList(typeElement.getTypeParameters()));
out.print('>');
}
// Extends
if (typeElement.getSuperclass().getKind() != TypeKind.NONE
&& !TypesUtils.isObject(typeElement.getSuperclass())) {
out.print(" extends ");
out.print(formatType(typeElement.getSuperclass()));
}
// implements
if (!typeElement.getInterfaces().isEmpty()) {
final boolean isInterface = typeElement.getKind() == ElementKind.INTERFACE;
out.print(isInterface ? " extends " : " implements ");
out.print(formatType(formatList(typeElement.getInterfaces())));
}
out.println(" {");
String tempIndention = currentIndention;
currentIndention = currentIndention + INDENTION;
printTypeMembers(typeElement.getEnclosedElements(), innerClass);
currentIndention = tempIndention;
indent();
out.println("}");
for (TypeElement element : innerClass) {
printClass(element, typeElement.getSimpleName().toString());
}
}
/**
* Helper method that outputs the public or protected inner members of a class.
*
* @param members list of the class members
*/
private void printTypeMembers(List<? extends Element> members, List<TypeElement> innerClass) {
for (Element element : members) {
if (isPublicOrProtected(element)) {
printMember(element, innerClass);
}
}
}
/** Helper method that outputs the declaration of the member */
private void printMember(Element member, List<TypeElement> innerClass) {
if (member.getKind().isField()) {
printFieldDecl((VariableElement) member);
} else if (member instanceof ExecutableElement) {
printMethodDecl((ExecutableElement) member);
} else if (member instanceof TypeElement) {
innerClass.add((TypeElement) member);
}
}
/**
* Helper method that outputs the field declaration for the given field.
*
* <p>It indicates whether the field is {@code protected}.
*/
private void printFieldDecl(VariableElement field) {
if ("class".equals(field.getSimpleName().toString())) {
error("Cannot write class literals in stub files.");
return;
}
indent();
// if protected, indicate that, but not public
if (field.getModifiers().contains(Modifier.PROTECTED)) {
out.print("protected ");
}
if (field.getModifiers().contains(Modifier.STATIC)) {
out.print("static ");
}
if (field.getModifiers().contains(Modifier.FINAL)) {
out.print("final ");
}
out.print(formatType(field.asType()));
out.print(" ");
out.print(field.getSimpleName());
out.println(';');
}
/**
* Helper method that outputs the method declaration for the given method
*
* <p>IT indicates whether the field is {@code protected}.
*/
private void printMethodDecl(ExecutableElement method) {
indent();
// if protected, indicate that, but not public
if (method.getModifiers().contains(Modifier.PROTECTED)) {
out.print("protected ");
}
if (method.getModifiers().contains(Modifier.STATIC)) {
out.print("static ");
}
// print Generic arguments
if (!method.getTypeParameters().isEmpty()) {
out.print('<');
out.print(formatList(method.getTypeParameters()));
out.print("> ");
}
// not return type for constructors
if (method.getKind() != ElementKind.CONSTRUCTOR) {
out.print(formatType(method.getReturnType()));
out.print(" ");
out.print(method.getSimpleName());
} else {
out.print(method.getEnclosingElement().getSimpleName());
}
out.print('(');
boolean isFirst = true;
for (VariableElement param : method.getParameters()) {
if (!isFirst) out.print(", ");
out.print(formatType(param.asType()));
out.print(' ');
out.print(param.getSimpleName());
isFirst = false;
}
out.print(')');
if (!method.getThrownTypes().isEmpty()) {
out.print(" throws ");
out.print(formatType(method.getThrownTypes()));
}
out.println(';');
}
/** Indent the current line */
private void indent() {
out.print(currentIndention);
}
/**
* Return a string representation of the list in the form of {@code item1, item2, item3, ...}
*
* <p>instead of the default representation, {@code [item1, item2, item3, ...]}
*/
private String formatList(List<?> lst) {
StringBuilder sb = new StringBuilder();
boolean isFirst = true;
for (Object o : lst) {
if (!isFirst) sb.append(", ");
sb.append(o);
isFirst = false;
}
return sb.toString();
}
/** Returns true if the element is public or protected element */
private boolean isPublicOrProtected(Element element) {
return element.getModifiers().contains(Modifier.PUBLIC)
|| element.getModifiers().contains(Modifier.PROTECTED);
}
/** outputs the simple name of the type */
private String formatType(Object typeRep) {
StringTokenizer tokenizer = new StringTokenizer(typeRep.toString(), "()<>[], ", true);
StringBuilder sb = new StringBuilder();
while (tokenizer.hasMoreTokens()) {
String token = tokenizer.nextToken();
if (token.length() == 1 || token.lastIndexOf('.') == -1) sb.append(token);
else {
int index = token.lastIndexOf('.');
sb.append(token.substring(index + 1));
}
}
return sb.toString();
}
public static void main(String[] args) {
if (args.length != 1) {
System.out.println("Usage:");
System.out.println(" java IndexGenerator [class or package name]");
return;
}
Context context = new Context();
ProcessingEnvironment env = JavacProcessingEnvironment.instance(context);
StubGenerator generator = new StubGenerator();
if (env.getElementUtils().getPackageElement(args[0]) != null) {
generator.stubFromPackage(env.getElementUtils().getPackageElement(args[0]));
} else if (env.getElementUtils().getTypeElement(args[0]) != null) {
generator.stubFromType(env.getElementUtils().getTypeElement(args[0]));
} else {
error("Couldn't find a package or a class named " + args[0]);
}
}
private static void error(String string) {
System.err.println("StubGenerator: " + string);
}
}