001package daikon.tools.jtb;
002
003import static java.nio.charset.StandardCharsets.UTF_8;
004
005import java.io.File;
006import java.io.Reader;
007import java.io.StringReader;
008import java.io.StringWriter;
009import java.io.Writer;
010import java.nio.file.Files;
011import java.nio.file.Paths;
012import java.util.ArrayList;
013import java.util.List;
014import jtb.*;
015import jtb.syntaxtree.*;
016import jtb.visitor.*;
017import org.checkerframework.checker.lock.qual.GuardSatisfied;
018import org.checkerframework.dataflow.qual.SideEffectFree;
019
020/**
021 * The wrapped result of parsing a .java source file. The packageName and className arguments can be
022 * obtained from root, but they are returned here for convenience.
023 */
024public class ParseResults {
025  public String packageName;
026
027  public String fileName;
028
029  public List<TypeDeclaration> roots = new ArrayList<>();
030
031  public CompilationUnit compilationUnit;
032
033  private ParseResults(String packageName, String fileName, CompilationUnit compilationUnit) {
034    this.packageName = packageName;
035    this.fileName = fileName;
036    this.compilationUnit = compilationUnit;
037  }
038
039  @SideEffectFree
040  @Override
041  public String toString(@GuardSatisfied ParseResults this) {
042    return "package name: " + packageName + ", file name: " + fileName;
043  }
044
045  /** If one of the files declares an interfaces, an error will occur. */
046  public static List<ParseResults> parse(List<String> javaFileNames) {
047    return parse(javaFileNames, false);
048  }
049
050  public static List<ParseResults> parse(List<String> javaFileNames, boolean discardComments) {
051
052    List<ParseResults> retval = new ArrayList<>();
053
054    for (String javaFileName : javaFileNames) {
055      ParseResults results = parse(javaFileName, discardComments);
056      retval.add(results);
057    }
058
059    return retval;
060  }
061
062  public static ParseResults parse(String javaFileName) {
063    return parse(javaFileName, false);
064  }
065
066  public static ParseResults parse(String javaFileName, boolean discardComments) {
067
068    CompilationUnit compilationUnit;
069
070    System.out.println("Parsing file " + javaFileName);
071
072    File file = new File(javaFileName);
073    String fileName = file.getName();
074    assert fileName.endsWith(".java")
075        : "Found a java-file argument that doesn't end in .java: " + file;
076
077    try (Reader input = Files.newBufferedReader(Paths.get(javaFileName), UTF_8)) {
078      JavaParser parser = new JavaParser(input);
079      compilationUnit = parser.CompilationUnit();
080    } catch (Exception e) {
081      e.printStackTrace();
082      throw new Error(e);
083    }
084
085    try {
086      // To discard comments, we dump the AST without special
087      // tokens, and then we read it again in the same way as
088      // before.
089      if (discardComments) {
090        Writer output = new StringWriter();
091        TreeDumper dumper = new TreeDumper(output);
092        dumper.printSpecials(false); // Do not print specials <==> discard comments
093        compilationUnit.accept(new TreeFormatter());
094        compilationUnit.accept(dumper);
095        output.close();
096
097        try (Reader input = new StringReader(output.toString())) {
098          JavaParser parser = new JavaParser(input);
099          compilationUnit = parser.CompilationUnit();
100        }
101      }
102
103    } catch (Exception e) {
104      e.printStackTrace();
105      throw new Error(e);
106    }
107
108    // Construct the package name.
109    String packageNameString;
110    // CompilationUnit:
111    // f0 -> [ PackageDeclaration() ]
112    // f1 -> ( ImportDeclaration() )*
113    // f2 -> ( TypeDeclaration() )*
114    // f3 -> ( <"\u001a"> )?
115    // f4 -> ( <STUFF_TO_IGNORE: ~[]> )?
116    // f5 -> <EOF>
117    // PackageDeclaration:
118    // f0 -> Modifiers()
119    // f1 -> "package"
120    // f2 -> Name()
121    // f3 -> ";"
122    NodeOptional packageDeclarationMaybe = compilationUnit.f0;
123    if (packageDeclarationMaybe.present()) {
124      PackageDeclaration packageDeclaration = (PackageDeclaration) packageDeclarationMaybe.node;
125      Name packageName = packageDeclaration.f2;
126      StringWriter stringWriter = new StringWriter();
127      TreeDumper dumper = new TreeDumper(stringWriter);
128      dumper.visit(packageName);
129      packageNameString = stringWriter.toString().trim();
130    } else {
131      packageNameString = "";
132    }
133
134    ParseResults results = new ParseResults(packageNameString, fileName, compilationUnit);
135
136    // Find the class name.
137    NodeListOptional typeDeclarationMaybe = compilationUnit.f2;
138    for (int j = 0; j < typeDeclarationMaybe.size(); j++) {
139
140      TypeDeclaration typeDeclaration = (TypeDeclaration) typeDeclarationMaybe.elementAt(j);
141
142      // {
143      // NodeSequence sequence = (NodeSequence) typeDeclaration.f0.choice;
144      // NodeChoice nodeChoice = (NodeChoice) sequence.elementAt(1);
145      // ClassOrInterfaceDeclaration decl = (ClassOrInterfaceDeclaration) nodeChoice.choice;
146      // assert !Ast.isInterface(decl)
147      //                   : "Do not give .java files that declare interfaces "
148      //                   + "to the instrumenter: " + javaFileName;
149      // }
150
151      results.roots.add(typeDeclaration);
152    }
153
154    return results;
155  }
156}