All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.checkerframework.framework.stub.JavaStubifier Maven / Gradle / Ivy

package org.checkerframework.framework.stub;

import com.github.javaparser.ParseResult;
import com.github.javaparser.ast.AccessSpecifier;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.Node;
import com.github.javaparser.ast.NodeList;
import com.github.javaparser.ast.body.AnnotationDeclaration;
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
import com.github.javaparser.ast.body.ConstructorDeclaration;
import com.github.javaparser.ast.body.EnumDeclaration;
import com.github.javaparser.ast.body.FieldDeclaration;
import com.github.javaparser.ast.body.InitializerDeclaration;
import com.github.javaparser.ast.body.MethodDeclaration;
import com.github.javaparser.ast.expr.NormalAnnotationExpr;
import com.github.javaparser.ast.nodeTypes.modifiers.NodeWithAccessModifiers;
import com.github.javaparser.ast.stmt.BlockStmt;
import com.github.javaparser.ast.visitor.ModifierVisitor;
import com.github.javaparser.utils.CollectionStrategy;
import com.github.javaparser.utils.ParserCollectionStrategy;
import com.github.javaparser.utils.ProjectRoot;
import com.github.javaparser.utils.SourceRoot;

import org.checkerframework.framework.util.JavaParserUtil;

import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Optional;

/**
 * Process Java source files in a directory to produce, in-place, minimal stub files.
 *
 * 

To process a file means to remove: * *

    *
  1. everything that is private or package-private, *
  2. all comments, except for an initial copyright header, *
  3. all method bodies, *
  4. all field initializers, *
  5. all initializer blocks, *
  6. attributes to the {@code Deprecated} annotation (to be Java 8 compatible). *
*/ public class JavaStubifier { /** * Processes each provided command-line argument; see class documentation for details. * * @param args command-line arguments: directories to process */ public static void main(String[] args) { if (args.length < 1) { System.err.println("Usage: provide one or more directory names to process"); System.exit(1); } for (String arg : args) { process(arg); } } /** * Process each file in the given directory; see class documentation for details. * * @param dir directory to process */ private static void process(String dir) { Path root = dirnameToPath(dir); MinimizerCallback mc = new MinimizerCallback(); CollectionStrategy strategy = new ParserCollectionStrategy(); // Required to include directories that contain a module-info.java, which don't parse by // default. strategy.getParserConfiguration().setLanguageLevel(JavaParserUtil.DEFAULT_LANGUAGE_LEVEL); ProjectRoot projectRoot = strategy.collect(root); projectRoot .getSourceRoots() .forEach( sourceRoot -> { try { sourceRoot.parse("", mc); } catch (IOException e) { System.err.println("IOException: " + e); } }); } /** * Converts a directory name to a path. It issues a warning and terminates the program if the * argument does not exist or is not a directory. * *

Unlike {@code Paths.get}, it handles "." which means the current directory in Unix. * * @param dir a directory name * @return a path for the directory name */ public static Path dirnameToPath(String dir) { File f = new File(dir); if (!f.exists()) { System.err.printf("Directory %s (%s) does not exist.%n", dir, f); System.exit(1); } if (!f.isDirectory()) { System.err.printf("Not a directory: %s (%s).%n", dir, f); System.exit(1); } String absoluteDir = f.getAbsolutePath(); if (absoluteDir.endsWith("/.")) { absoluteDir = absoluteDir.substring(0, absoluteDir.length() - 2); } return Paths.get(absoluteDir); } /** Callback to process each Java file; see class documentation for details. */ private static class MinimizerCallback implements SourceRoot.Callback { /** The visitor instance. */ private final MinimizerVisitor mv; /** Create a MinimizerCallback instance. */ public MinimizerCallback() { this.mv = new MinimizerVisitor(); } @Override public Result process( Path localPath, Path absolutePath, ParseResult result) { Result res = Result.SAVE; // System.out.printf("Minimizing %s%n", absolutePath); Optional opt = result.getResult(); if (opt.isPresent()) { CompilationUnit cu = opt.get(); // Only remove the "contained" comments so that the copyright comment is not // removed. cu.getAllContainedComments().forEach(Node::remove); mv.visit(cu, null); if (cu.findAll(ClassOrInterfaceDeclaration.class).isEmpty() && cu.findAll(AnnotationDeclaration.class).isEmpty() && cu.findAll(EnumDeclaration.class).isEmpty() && !absolutePath.endsWith("package-info.java")) { // All content is removed, delete this file. new File(absolutePath.toUri()).delete(); res = Result.DONT_SAVE; } } return res; } } /** Visitor to process one compilation unit; see class documentation for details. */ private static class MinimizerVisitor extends ModifierVisitor { /** Whether to consider members implicitly public. */ private boolean implicitlyPublic = false; @Override public ClassOrInterfaceDeclaration visit(ClassOrInterfaceDeclaration cid, Void arg) { boolean prevIP = implicitlyPublic; if (cid.isInterface()) { // All members of interfaces are implicitly public. implicitlyPublic = true; } super.visit(cid, arg); if (cid.isInterface()) { implicitlyPublic = prevIP; } // Do not remove private or package-private classes, because there could // be externally-visible members in externally-visible subclasses. return cid; } @Override public EnumDeclaration visit(EnumDeclaration ed, Void arg) { super.visit(ed, arg); // Enums can't be extended, so it is ok to remove them if they are not externally // visible. removeIfPrivateOrPkgPrivate(ed); return ed; } @Override public ConstructorDeclaration visit(ConstructorDeclaration cd, Void arg) { super.visit(cd, arg); // Constructors cannot be overridden, so it is ok to remove them if they are // not externally visible. if (!removeIfPrivateOrPkgPrivate(cd)) { // ConstructorDeclaration has to have a body cd.setBody(new BlockStmt()); } return cd; } @Override public MethodDeclaration visit(MethodDeclaration md, Void arg) { super.visit(md, arg); // Non-private methods could be overridden with larger visibility. // So it is only safe to remove private methods, which can't be overridden. if (!removeIfPrivate(md)) { md.removeBody(); } return md; } @Override public FieldDeclaration visit(FieldDeclaration fd, Void arg) { super.visit(fd, arg); // It is safe to remove fields that are not externally visible. if (!removeIfPrivateOrPkgPrivate(fd)) { fd.getVariables().forEach(v -> v.getInitializer().ifPresent(Node::remove)); } return fd; } @Override public InitializerDeclaration visit(InitializerDeclaration id, Void arg) { super.visit(id, arg); id.remove(); return id; } @Override public NormalAnnotationExpr visit(NormalAnnotationExpr nae, Void arg) { super.visit(nae, arg); if (nae.getNameAsString().equals("Deprecated")) { nae.setPairs(new NodeList<>()); } return nae; } /** * Remove the whole node if it is private or package private. * * @param node a Node to inspect * @return true if the node was removed */ private boolean removeIfPrivateOrPkgPrivate(NodeWithAccessModifiers node) { if (implicitlyPublic) { return false; } AccessSpecifier as = node.getAccessSpecifier(); if (as == AccessSpecifier.PRIVATE || as == AccessSpecifier.NONE) { ((Node) node).remove(); return true; } return false; } /** * Remove the whole node if it is private. * * @param node a Node to inspect * @return true if the node was removed */ private boolean removeIfPrivate(NodeWithAccessModifiers node) { if (implicitlyPublic) { return false; } AccessSpecifier as = node.getAccessSpecifier(); if (as == AccessSpecifier.PRIVATE) { ((Node) node).remove(); return true; } return false; } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy