blob: ed230fe1ce332404a4ea88244bdd1ab2801bb7c1 [file] [log] [blame]
package org.checkerframework.framework.stub;
import com.sun.tools.javac.main.JavaCompiler;
import com.sun.tools.javac.main.Option;
import com.sun.tools.javac.processing.JavacProcessingEnvironment;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.Options;
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.AnnotationMirror;
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.type.TypeMirror;
import javax.lang.model.util.ElementFilter;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.javacutil.ElementUtils;
import org.checkerframework.javacutil.SystemUtil;
import org.checkerframework.javacutil.TypesUtils;
import org.plumelib.util.CollectionsPlume;
import org.plumelib.util.StringsPlume;
/**
* Generates a stub file from a single class or an entire package.
*
* <p>TODO: StubGenerator needs to be reimplemented, because it no longer works due to changes in
* JDK 9.
*
* @checker_framework.manual #stub 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 a {@code StubGenerator} that outputs to {@code System.out}. */
public StubGenerator() {
this(System.out);
}
/**
* Constructs a {@code StubGenerator} that outputs to the provided output stream.
*
* @param out the output stream
*/
public StubGenerator(PrintStream out) {
this.out = out;
}
/**
* Constructs a {@code StubGenerator} 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.getQualifiedName(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.getQualifiedName(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.getQualifiedName(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.indexOf(currentPackage)
+ currentPackage.length()
// +1 because currentPackage doesn't include
// the . between the package name and the classname
+ 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 prints the stub file for the provided class.
*
* @param typeElement the class to output
* @param outerClass the outer class of the class, or null if {@code typeElement} is a top-level
* class
*/
private void printClass(TypeElement typeElement, @Nullable String outerClass) {
indent();
List<? extends AnnotationMirror> teannos = typeElement.getAnnotationMirrors();
if (teannos != null && !teannos.isEmpty()) {
for (AnnotationMirror am : teannos) {
out.println(am);
}
}
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 ");
List<String> ls =
CollectionsPlume.mapList(StubGenerator::formatType, typeElement.getInterfaces());
out.print(formatList(ls));
}
out.println(" {");
String tempIndention = currentIndention;
currentIndention = currentIndention + INDENTION;
// Inner classes, which the stub generator prints later.
List<TypeElement> innerClass = new ArrayList<>();
// side-effects innerClass
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();
List<? extends AnnotationMirror> veannos = field.getAnnotationMirrors();
if (veannos != null && !veannos.isEmpty()) {
for (AnnotationMirror am : veannos) {
out.println(am);
}
}
// 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();
List<? extends AnnotationMirror> eeannos = method.getAnnotationMirrors();
if (eeannos != null && !eeannos.isEmpty()) {
for (AnnotationMirror am : eeannos) {
out.println(am);
}
}
// 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 ");
List<String> ltt =
CollectionsPlume.mapList(StubGenerator::formatType, method.getThrownTypes());
out.print(formatList(ltt));
}
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, ...},
* without surrounding square brackets as the default representation has.
*
* @param lst a list to format
* @return a string representation of the list, without surrounding square brackets
*/
private String formatList(List<?> lst) {
return StringsPlume.join(", ", lst);
}
/** 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);
}
/**
* Returns the simple name of the type.
*
* @param typeRep a type
* @return the simple name of the type
*/
private static String formatType(TypeMirror 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();
}
/**
* The main entry point to StubGenerator.
*
* @param args command-line arguments
*/
@SuppressWarnings("signature") // User-supplied arguments to main
public static void main(String[] args) {
if (args.length != 1) {
System.out.println("Usage:");
System.out.println(" java StubGenerator [class or package name]");
return;
}
Context context = new Context();
Options options = Options.instance(context);
if (SystemUtil.getJreVersion() == 8) {
options.put(Option.SOURCE, "8");
options.put(Option.TARGET, "8");
}
JavaCompiler javac = JavaCompiler.instance(context);
javac.initModules(com.sun.tools.javac.util.List.nil());
javac.enterDone();
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);
}
}