jjg@489: /* ohair@554: * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. jjg@489: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. jjg@489: * jjg@489: * This code is free software; you can redistribute it and/or modify it jjg@489: * under the terms of the GNU General Public License version 2 only, as jjg@489: * published by the Free Software Foundation. jjg@489: * jjg@489: * This code is distributed in the hope that it will be useful, but WITHOUT jjg@489: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or jjg@489: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License jjg@489: * version 2 for more details (a copy is included in the LICENSE file that jjg@489: * accompanied this code). jjg@489: * jjg@489: * You should have received a copy of the GNU General Public License version jjg@489: * 2 along with this work; if not, write to the Free Software Foundation, jjg@489: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. jjg@489: * ohair@554: * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA ohair@554: * or visit www.oracle.com if you need additional information or have any ohair@554: * questions. jjg@489: */ jjg@489: jjg@489: import java.io.*; jjg@489: import java.lang.reflect.*; jjg@489: import java.util.*; jjg@489: import javax.tools.*; jjg@489: jjg@489: import com.sun.source.tree.CompilationUnitTree; jjg@679: import com.sun.source.tree.Tree; jjg@489: import com.sun.source.util.JavacTask; jjg@489: import com.sun.tools.javac.api.JavacTool; jjg@679: import com.sun.tools.javac.tree.JCTree; jjg@679: import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; jjg@489: import com.sun.tools.javac.util.List; jjg@489: jjg@679: public abstract class AbstractTreeScannerTest { jjg@489: jjg@489: /** jjg@489: * Run the program. A base directory can be provided for file arguments. jjg@489: * In jtreg mode, the -r option can be given to change the default base jjg@489: * directory to the test root directory. For other options, see usage(). jjg@489: * @param baseDir base directory for any file arguments. jjg@489: * @param args command line args jjg@489: * @return true if successful or in gui mode jjg@489: */ jjg@489: boolean run(File baseDir, String... args) { jjg@489: if (args.length == 0) { jjg@489: usage(System.out); jjg@489: return true; jjg@489: } jjg@489: jjg@489: ArrayList files = new ArrayList(); jjg@489: for (int i = 0; i < args.length; i++) { jjg@489: String arg = args[i]; jjg@489: if (arg.equals("-q")) jjg@489: quiet = true; jjg@489: else if (arg.equals("-v")) jjg@489: verbose = true; jjg@489: else if (arg.equals("-r")) { jjg@489: File d = baseDir; jjg@489: while (!new File(d, "TEST.ROOT").exists()) { jjg@489: d = d.getParentFile(); jjg@489: if (d == null) jjg@489: throw new Error("cannot find TEST.ROOT"); jjg@489: } jjg@489: baseDir = d; jjg@489: } jjg@489: else if (arg.startsWith("-")) jjg@489: throw new Error("unknown option: " + arg); jjg@489: else { jjg@489: while (i < args.length) jjg@489: files.add(new File(baseDir, args[i++])); jjg@489: } jjg@489: } jjg@489: jjg@489: for (File file: files) { jjg@489: if (file.exists()) jjg@489: test(file); jjg@489: else jjg@489: error("File not found: " + file); jjg@489: } jjg@489: jjg@489: if (fileCount != 1) jjg@489: System.err.println(fileCount + " files read"); jjg@679: System.err.println(treeCount + " tree nodes compared"); jjg@489: if (errors > 0) jjg@489: System.err.println(errors + " errors"); jjg@489: jjg@489: return (errors == 0); jjg@489: } jjg@489: jjg@489: /** jjg@489: * Print command line help. jjg@489: * @param out output stream jjg@489: */ jjg@489: void usage(PrintStream out) { jjg@489: out.println("Usage:"); jjg@679: out.println(" java " + getClass().getName() + " options... files..."); jjg@489: out.println(""); jjg@489: out.println("where options include:"); jjg@489: out.println("-q Quiet: don't report on inapplicable files"); jjg@489: out.println("-v Verbose: report on files as they are being read"); jjg@489: out.println(""); jjg@489: out.println("files may be directories or files"); jjg@489: out.println("directories will be scanned recursively"); jjg@489: out.println("non java files, or java files which cannot be parsed, will be ignored"); jjg@489: out.println(""); jjg@489: } jjg@489: jjg@489: /** jjg@489: * Test a file. If the file is a directory, it will be recursively scanned jjg@489: * for java files. jjg@489: * @param file the file or directory to test jjg@489: */ jjg@489: void test(File file) { jjg@489: if (file.isDirectory()) { jjg@489: for (File f: file.listFiles()) { jjg@489: test(f); jjg@489: } jjg@489: return; jjg@489: } jjg@489: jjg@489: if (file.isFile() && file.getName().endsWith(".java")) { jjg@489: try { jjg@489: if (verbose) jjg@489: System.err.println(file); jjg@489: fileCount++; jjg@679: treeCount += test(read(file)); jjg@489: } catch (ParseException e) { jjg@489: if (!quiet) { jjg@489: error("Error parsing " + file + "\n" + e.getMessage()); jjg@489: } jjg@489: } catch (IOException e) { jjg@489: error("Error reading " + file + ": " + e); jjg@489: } jjg@489: return; jjg@489: } jjg@489: jjg@489: if (!quiet) jjg@489: error("File " + file + " ignored"); jjg@489: } jjg@489: jjg@679: abstract int test(JCCompilationUnit t); jjg@679: jjg@489: /** jjg@489: * Read a file. jjg@489: * @param file the file to be read jjg@489: * @return the tree for the content of the file jjg@489: * @throws IOException if any IO errors occur jjg@489: * @throws TreePosTest.ParseException if any errors occur while parsing the file jjg@489: */ jjg@489: JCCompilationUnit read(File file) throws IOException, ParseException { jjg@489: StringWriter sw = new StringWriter(); jjg@489: PrintWriter pw = new PrintWriter(sw); jjg@489: Reporter r = new Reporter(pw); jjg@489: JavacTool tool = JavacTool.create(); jjg@489: StandardJavaFileManager fm = tool.getStandardFileManager(r, null, null); jjg@489: Iterable files = fm.getJavaFileObjects(file); jjg@489: JavacTask task = tool.getTask(pw, fm, r, Collections.emptyList(), null, files); jjg@489: Iterable trees = task.parse(); jjg@489: pw.flush(); jjg@489: if (r.errors > 0) jjg@489: throw new ParseException(sw.toString()); jjg@489: Iterator iter = trees.iterator(); jjg@489: if (!iter.hasNext()) jjg@489: throw new Error("no trees found"); jjg@489: JCCompilationUnit t = (JCCompilationUnit) iter.next(); jjg@489: if (iter.hasNext()) jjg@489: throw new Error("too many trees found"); jjg@489: return t; jjg@489: } jjg@489: jjg@489: /** jjg@489: * Report an error. When the program is complete, the program will either jjg@489: * exit or throw an Error if any errors have been reported. jjg@489: * @param msg the error message jjg@489: */ jjg@489: void error(String msg) { jjg@489: System.err.println(msg); jjg@489: errors++; jjg@489: } jjg@489: jjg@489: /** jjg@683: * Report an error. When the program is complete, the program will either jjg@683: * exit or throw an Error if any errors have been reported. jjg@683: * @param msg the error message jjg@683: */ jjg@683: void error(JavaFileObject file, String msg) { jjg@683: System.err.println(file.getName() + ": " + msg); jjg@683: errors++; jjg@683: } jjg@683: jjg@683: /** jjg@489: * Report an error for a specific tree node. jjg@489: * @param file the source file for the tree jjg@489: * @param t the tree node jjg@489: * @param label an indication of the error jjg@489: */ jjg@679: void error(JavaFileObject file, Tree tree, String msg) { jjg@679: JCTree t = (JCTree) tree; jjg@489: error(file.getName() + ":" + getLine(file, t) + ": " + msg + " " + trim(t, 64)); jjg@489: } jjg@489: jjg@489: /** jjg@489: * Get a trimmed string for a tree node, with normalized white space and limited length. jjg@489: */ jjg@679: String trim(Tree tree, int len) { jjg@679: JCTree t = (JCTree) tree; jjg@683: String s = t.toString().replaceAll("\\s+", " "); jjg@489: return (s.length() < len) ? s : s.substring(0, len); jjg@489: } jjg@489: jjg@489: /** Number of files that have been analyzed. */ jjg@489: int fileCount; jjg@679: /** Number of trees that have been successfully compared. */ jjg@679: int treeCount; jjg@489: /** Number of errors reported. */ jjg@489: int errors; jjg@489: /** Flag: don't report irrelevant files. */ jjg@489: boolean quiet; jjg@489: /** Flag: report files as they are processed. */ jjg@489: boolean verbose; jjg@489: jjg@489: jjg@489: /** jjg@489: * Thrown when errors are found parsing a java file. jjg@489: */ jjg@489: private static class ParseException extends Exception { jjg@489: ParseException(String msg) { jjg@489: super(msg); jjg@489: } jjg@489: } jjg@489: jjg@489: /** jjg@489: * DiagnosticListener to report diagnostics and count any errors that occur. jjg@489: */ jjg@489: private static class Reporter implements DiagnosticListener { jjg@489: Reporter(PrintWriter out) { jjg@489: this.out = out; jjg@489: } jjg@489: jjg@489: public void report(Diagnostic diagnostic) { jjg@489: out.println(diagnostic); jjg@489: switch (diagnostic.getKind()) { jjg@489: case ERROR: jjg@489: errors++; jjg@489: } jjg@489: } jjg@489: int errors; jjg@489: PrintWriter out; jjg@489: } jjg@489: jjg@489: /** jjg@489: * Get the set of fields for a tree node that may contain child tree nodes. jjg@489: * These are the fields that are subtypes of JCTree or List. jjg@489: * The results are cached, based on the tree's tag. jjg@489: */ jjg@489: Set getFields(JCTree tree) { jjg@489: Set fields = map.get(tree.getTag()); jjg@489: if (fields == null) { jjg@489: fields = new HashSet(); jjg@489: for (Field f: tree.getClass().getFields()) { jjg@489: Class fc = f.getType(); jjg@489: if (JCTree.class.isAssignableFrom(fc) || List.class.isAssignableFrom(fc)) jjg@489: fields.add(f); jjg@489: } jjg@489: map.put(tree.getTag(), fields); jjg@489: } jjg@489: return fields; jjg@489: } jjg@489: // where jjg@489: Map> map = new HashMap>(); jjg@489: jjg@489: /** Get the line number for the primary position for a tree. jjg@489: * The code is intended to be simple, although not necessarily efficient. jjg@489: * However, note that a file manager such as JavacFileManager is likely jjg@489: * to cache the results of file.getCharContent, avoiding the need to read jjg@489: * the bits from disk each time this method is called. jjg@489: */ jjg@489: int getLine(JavaFileObject file, JCTree tree) { jjg@489: try { jjg@489: CharSequence cs = file.getCharContent(true); jjg@489: int line = 1; jjg@489: for (int i = 0; i < tree.pos; i++) { jjg@489: if (cs.charAt(i) == '\n') // jtreg tests always use Unix line endings jjg@489: line++; jjg@489: } jjg@489: return line; jjg@489: } catch (IOException e) { jjg@489: return -1; jjg@489: } jjg@489: } jjg@489: }