package checkers.util.stub; import java.io.PrintStream; import java.util.Collections; import java.util.List; import java.util.StringTokenizer; import javax.annotation.processing.ProcessingEnvironment; import javax.annotation.processing.Processor; import javax.lang.model.element.*; import javax.lang.model.type.*; import javax.lang.model.util.*; import checkers.util.TypesUtils; import com.sun.tools.javac.processing.JavacProcessingEnvironment; import com.sun.tools.javac.util.Context; /** * 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. * For details, see the <a * href="http://types.cs.washington.edu/checker-framework/#stub-creating-and-using">Checker * Framework Manual</a>. */ public class StubGenerator { /** The used indention for the class */ private final static String INDENTION = " "; /** The output stream */ private final PrintStream out; /** the current indention for the line being processsed */ private String currentIndention = ""; /** the package of the class being processed */ // As an optimization, it is ended with a '.' 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; } /** * Generate the skeleton file for all the classes within the provided * package. */ public void skeletonFromPackage(PackageElement packageElement) { currentPackage = packageElement.getQualifiedName().toString(); indent(); out.print("package "); out.print(currentPackage); out.println(";"); currentPackage += "."; for (TypeElement element : ElementFilter.typesIn(packageElement.getEnclosedElements())) { if (isPublicOrProtected(element)) { out.println(); printClass((TypeElement)element); } } } /** * Generate the skeleton file for provided class. The generated file * includes the package name. */ public void skeletonFromType(TypeElement typeElement) { // only output skeleton for classes or interfaces. not enums if (typeElement.getKind() != ElementKind.CLASS && typeElement.getKind() != ElementKind.INTERFACE) return; String fullClassName = typeElement.getQualifiedName().toString(); if (fullClassName.indexOf('.') != -1) { int index = fullClassName.lastIndexOf('.'); currentPackage = fullClassName.substring(0, index); indent(); out.print("package "); out.print(currentPackage); out.println(";"); out.println(); currentPackage += "."; } printClass(typeElement); } /** * helper method that outputs the index for the provided class. * * @param typeElement */ private void printClass(TypeElement typeElement) { indent(); if (typeElement.getKind() == ElementKind.INTERFACE) out.print("interface"); else if (typeElement.getKind() == ElementKind.CLASS) out.print("class"); else return; out.print(' '); 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()); currentIndention = tempIndention; indent(); out.println("}"); } /** * 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) { for (Element element : members) { if (isPublicOrProtected(element)) printMember(element); } } /** * Helper method that outputs the declaration of the member */ private void printMember(Element member) { if (member.getKind().isField()) printFieldDecl((VariableElement)member); else if (member instanceof ExecutableElement) printMethodDecl((ExecutableElement)member); else if (member instanceof TypeElement) printClass((TypeElement)member); } /** * Helper method that outputs the field declaration for the given field. * * It indicates whether the field is {@code protected}. */ private void printFieldDecl(VariableElement field) { indent(); // if protected, indicate that, but not public if (field.getModifiers().contains(Modifier.PROTECTED)) out.print("protected "); out.print(formatType(field.asType())); out.print(" "); out.print(field.getSimpleName()); out.println(';'); } /** * Helper method that outputs the method declaration for the given method * * 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 "); // 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, ... * </code> * * instead of the default representation, * <code> * [item1, item2, item3, ...] * </code> * */ 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 = new JavacProcessingEnvironment(context, Collections.<Processor>emptyList()); StubGenerator generator = new StubGenerator(); if (env.getElementUtils().getPackageElement(args[0]) != null) generator.skeletonFromPackage(env.getElementUtils().getPackageElement(args[0])); else if (env.getElementUtils().getTypeElement(args[0]) != null) generator.skeletonFromType(env.getElementUtils().getTypeElement(args[0])); else System.err.println("Couldn't find a package or a class named " + args[0]); } }