1 /*
   2  * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 import java.io.*;
  25 import java.lang.reflect.*;
  26 import java.util.*;
  27 import javax.tools.*;
  28 
  29 import com.sun.source.tree.CompilationUnitTree;
  30 import com.sun.source.tree.Tree;
  31 import com.sun.source.util.JavacTask;
  32 import com.sun.tools.javac.api.JavacTool;
  33 import com.sun.tools.javac.tree.JCTree;
  34 import com.sun.tools.javac.tree.JCTree.JCCompilationUnit;
  35 import com.sun.tools.javac.util.List;
  36 
  37 public abstract class AbstractTreeScannerTest {
  38 
  39     /**
  40      * Run the program. A base directory can be provided for file arguments.
  41      * In jtreg mode, the -r option can be given to change the default base
  42      * directory to the test root directory. For other options, see usage().
  43      * @param baseDir base directory for any file arguments.
  44      * @param args command line args
  45      * @return true if successful or in gui mode
  46      */
  47     boolean run(File baseDir, String... args) {
  48         if (args.length == 0) {
  49             usage(System.out);
  50             return true;
  51         }
  52 
  53         ArrayList<File> files = new ArrayList<File>();
  54         for (int i = 0; i < args.length; i++) {
  55             String arg = args[i];
  56             if (arg.equals("-q"))
  57                 quiet = true;
  58             else if (arg.equals("-v"))
  59                 verbose = true;
  60             else if (arg.equals("-r")) {
  61                 File d = baseDir;
  62                 while (!new File(d, "TEST.ROOT").exists()) {
  63                     d = d.getParentFile();
  64                     if (d == null)
  65                         throw new Error("cannot find TEST.ROOT");
  66                 }
  67                 baseDir = d;
  68             }
  69             else if (arg.startsWith("-"))
  70                 throw new Error("unknown option: " + arg);
  71             else {
  72                 while (i < args.length)
  73                     files.add(new File(baseDir, args[i++]));
  74             }
  75         }
  76 
  77         for (File file: files) {
  78             if (file.exists())
  79                 test(file);
  80             else
  81                 error("File not found: " + file);
  82         }
  83 
  84         if (fileCount != 1)
  85             System.err.println(fileCount + " files read");
  86         System.err.println(treeCount + " tree nodes compared");
  87         if (errors > 0)
  88             System.err.println(errors + " errors");
  89 
  90         return (errors == 0);
  91     }
  92 
  93     /**
  94      * Print command line help.
  95      * @param out output stream
  96      */
  97     void usage(PrintStream out) {
  98         out.println("Usage:");
  99         out.println("  java " + getClass().getName() + " options... files...");
 100         out.println("");
 101         out.println("where options include:");
 102         out.println("-q        Quiet: don't report on inapplicable files");
 103         out.println("-v        Verbose: report on files as they are being read");
 104         out.println("");
 105         out.println("files may be directories or files");
 106         out.println("directories will be scanned recursively");
 107         out.println("non java files, or java files which cannot be parsed, will be ignored");
 108         out.println("");
 109     }
 110 
 111     /**
 112      * Test a file. If the file is a directory, it will be recursively scanned
 113      * for java files.
 114      * @param file the file or directory to test
 115      */
 116     void test(File file) {
 117         if (file.isDirectory()) {
 118             for (File f: file.listFiles()) {
 119                 test(f);
 120             }
 121             return;
 122         }
 123 
 124         if (file.isFile() && file.getName().endsWith(".java")) {
 125             try {
 126                 if (verbose)
 127                     System.err.println(file);
 128                 fileCount++;
 129                 treeCount += test(read(file));
 130             } catch (ParseException e) {
 131                 if (!quiet) {
 132                     error("Error parsing " + file + "\n" + e.getMessage());
 133                 }
 134             } catch (IOException e) {
 135                 error("Error reading " + file + ": " + e);
 136             }
 137             return;
 138         }
 139 
 140         if (!quiet)
 141             error("File " + file + " ignored");
 142     }
 143 
 144     abstract int test(JCCompilationUnit t);
 145 
 146     // See CR:  6982992 Tests CheckAttributedTree.java, JavacTreeScannerTest.java, and SourceTreeeScannerTest.java timeout
 147     StringWriter sw = new StringWriter();
 148     PrintWriter pw = new PrintWriter(sw);
 149     Reporter r = new Reporter(pw);
 150     JavacTool tool = JavacTool.create();
 151     StandardJavaFileManager fm = tool.getStandardFileManager(r, null, null);
 152 
 153     /**
 154      * Read a file.
 155      * @param file the file to be read
 156      * @return the tree for the content of the file
 157      * @throws IOException if any IO errors occur
 158      * @throws TreePosTest.ParseException if any errors occur while parsing the file
 159      */
 160     JCCompilationUnit read(File file) throws IOException, ParseException {
 161         JavacTool tool = JavacTool.create();
 162         r.errors = 0;
 163         Iterable<? extends JavaFileObject> files = fm.getJavaFileObjects(file);
 164         JavacTask task = tool.getTask(pw, fm, r, Collections.<String>emptyList(), null, files);
 165         Iterable<? extends CompilationUnitTree> trees = task.parse();
 166         pw.flush();
 167         if (r.errors > 0)
 168             throw new ParseException(sw.toString());
 169         Iterator<? extends CompilationUnitTree> iter = trees.iterator();
 170         if (!iter.hasNext())
 171             throw new Error("no trees found");
 172         JCCompilationUnit t = (JCCompilationUnit) iter.next();
 173         if (iter.hasNext())
 174             throw new Error("too many trees found");
 175         return t;
 176     }
 177 
 178     /**
 179      * Report an error. When the program is complete, the program will either
 180      * exit or throw an Error if any errors have been reported.
 181      * @param msg the error message
 182      */
 183     void error(String msg) {
 184         System.err.println(msg);
 185         errors++;
 186     }
 187 
 188     /**
 189      * Report an error. When the program is complete, the program will either
 190      * exit or throw an Error if any errors have been reported.
 191      * @param msg the error message
 192      */
 193     void error(JavaFileObject file, String msg) {
 194         System.err.println(file.getName() + ": " + msg);
 195         errors++;
 196     }
 197 
 198     /**
 199      *  Report an error for a specific tree node.
 200      *  @param file the source file for the tree
 201      *  @param t    the tree node
 202      *  @param label an indication of the error
 203      */
 204     void error(JavaFileObject file, Tree tree, String msg) {
 205         JCTree t = (JCTree) tree;
 206         error(file.getName() + ":" + getLine(file, t) + ": " + msg + " " + trim(t, 64));
 207     }
 208 
 209     /**
 210      * Get a trimmed string for a tree node, with normalized white space and limited length.
 211      */
 212     String trim(Tree tree, int len) {
 213         JCTree t = (JCTree) tree;
 214         String s = t.toString().replaceAll("\\s+", " ");
 215         return (s.length() < len) ? s : s.substring(0, len);
 216     }
 217 
 218     /** Number of files that have been analyzed. */
 219     int fileCount;
 220     /** Number of trees that have been successfully compared. */
 221     int treeCount;
 222     /** Number of errors reported. */
 223     int errors;
 224     /** Flag: don't report irrelevant files. */
 225     boolean quiet;
 226     /** Flag: report files as they are processed. */
 227     boolean verbose;
 228 
 229 
 230     /**
 231      * Thrown when errors are found parsing a java file.
 232      */
 233     private static class ParseException extends Exception {
 234         ParseException(String msg) {
 235             super(msg);
 236         }
 237     }
 238 
 239     /**
 240      * DiagnosticListener to report diagnostics and count any errors that occur.
 241      */
 242     private static class Reporter implements DiagnosticListener<JavaFileObject> {
 243         Reporter(PrintWriter out) {
 244             this.out = out;
 245         }
 246 
 247         public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
 248             out.println(diagnostic);
 249             switch (diagnostic.getKind()) {
 250                 case ERROR:
 251                     errors++;
 252             }
 253         }
 254         int errors;
 255         PrintWriter out;
 256     }
 257 
 258     /**
 259      * Get the set of fields for a tree node that may contain child tree nodes.
 260      * These are the fields that are subtypes of JCTree or List.
 261      * The results are cached, based on the tree's tag.
 262      */
 263     Set<Field> getFields(JCTree tree) {
 264         Set<Field> fields = map.get(tree.getTag());
 265         if (fields == null) {
 266             fields = new HashSet<Field>();
 267             for (Field f: tree.getClass().getFields()) {
 268                 Class<?> fc = f.getType();
 269                 if (JCTree.class.isAssignableFrom(fc) || List.class.isAssignableFrom(fc))
 270                     fields.add(f);
 271             }
 272             map.put(tree.getTag(), fields);
 273         }
 274         return fields;
 275     }
 276     // where
 277     Map<JCTree.Tag, Set<Field>> map = new HashMap<JCTree.Tag,Set<Field>>();
 278 
 279     /** Get the line number for the primary position for a tree.
 280      * The code is intended to be simple, although not necessarily efficient.
 281      * However, note that a file manager such as JavacFileManager is likely
 282      * to cache the results of file.getCharContent, avoiding the need to read
 283      * the bits from disk each time this method is called.
 284      */
 285     int getLine(JavaFileObject file, JCTree tree) {
 286         try {
 287             CharSequence cs = file.getCharContent(true);
 288             int line = 1;
 289             for (int i = 0; i < tree.pos; i++) {
 290                 if (cs.charAt(i) == '\n') // jtreg tests always use Unix line endings
 291                     line++;
 292             }
 293             return line;
 294         } catch (IOException e) {
 295             return -1;
 296         }
 297     }
 298 }