| 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); |
| } |
| } |