Thu, 04 Feb 2010 10:14:28 -0800
6923080: TreeScanner.visitNewClass should scan tree.typeargs
Reviewed-by: darcy
jjg@489 | 1 | /* |
jjg@489 | 2 | * Copyright 2010 Sun Microsystems, Inc. All Rights Reserved. |
jjg@489 | 3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
jjg@489 | 4 | * |
jjg@489 | 5 | * This code is free software; you can redistribute it and/or modify it |
jjg@489 | 6 | * under the terms of the GNU General Public License version 2 only, as |
jjg@489 | 7 | * published by the Free Software Foundation. |
jjg@489 | 8 | * |
jjg@489 | 9 | * This code is distributed in the hope that it will be useful, but WITHOUT |
jjg@489 | 10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
jjg@489 | 11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
jjg@489 | 12 | * version 2 for more details (a copy is included in the LICENSE file that |
jjg@489 | 13 | * accompanied this code). |
jjg@489 | 14 | * |
jjg@489 | 15 | * You should have received a copy of the GNU General Public License version |
jjg@489 | 16 | * 2 along with this work; if not, write to the Free Software Foundation, |
jjg@489 | 17 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
jjg@489 | 18 | * |
jjg@489 | 19 | * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, |
jjg@489 | 20 | * CA 95054 USA or visit www.sun.com if you need additional information or |
jjg@489 | 21 | * have any questions. |
jjg@489 | 22 | */ |
jjg@489 | 23 | |
jjg@489 | 24 | |
jjg@489 | 25 | /** |
jjg@489 | 26 | * Utility and test program to check javac's internal TreeScanner class. |
jjg@489 | 27 | * The program can be run standalone, or as a jtreg test. For info on |
jjg@489 | 28 | * command line args, run program with no args. |
jjg@489 | 29 | * |
jjg@489 | 30 | * <p> |
jjg@489 | 31 | * jtreg: Note that by using the -r switch in the test description below, this test |
jjg@489 | 32 | * will process all java files in the langtools/test directory, thus implicitly |
jjg@489 | 33 | * covering any new language features that may be tested in this test suite. |
jjg@489 | 34 | */ |
jjg@489 | 35 | |
jjg@489 | 36 | /* |
jjg@489 | 37 | * @test |
jjg@489 | 38 | * @bug 6923080 |
jjg@489 | 39 | * @summary TreeScanner.visitNewClass should scan tree.typeargs |
jjg@489 | 40 | * @run main TreeScannerTest -q -r . |
jjg@489 | 41 | */ |
jjg@489 | 42 | |
jjg@489 | 43 | import java.io.*; |
jjg@489 | 44 | import java.lang.reflect.*; |
jjg@489 | 45 | import java.util.*; |
jjg@489 | 46 | import javax.tools.*; |
jjg@489 | 47 | |
jjg@489 | 48 | import com.sun.source.tree.CompilationUnitTree; |
jjg@489 | 49 | import com.sun.source.util.JavacTask; |
jjg@489 | 50 | import com.sun.tools.javac.api.JavacTool; |
jjg@489 | 51 | import com.sun.tools.javac.tree.*; |
jjg@489 | 52 | import com.sun.tools.javac.tree.JCTree.*; |
jjg@489 | 53 | import com.sun.tools.javac.util.List; |
jjg@489 | 54 | |
jjg@489 | 55 | public class TreeScannerTest { |
jjg@489 | 56 | /** |
jjg@489 | 57 | * Main entry point. |
jjg@489 | 58 | * If test.src is set, program runs in jtreg mode, and will throw an Error |
jjg@489 | 59 | * if any errors arise, otherwise System.exit will be used. In jtreg mode, |
jjg@489 | 60 | * the default base directory for file args is the value of ${test.src}. |
jjg@489 | 61 | * In jtreg mode, the -r option can be given to change the default base |
jjg@489 | 62 | * directory to the root test directory. |
jjg@489 | 63 | */ |
jjg@489 | 64 | public static void main(String... args) { |
jjg@489 | 65 | String testSrc = System.getProperty("test.src"); |
jjg@489 | 66 | File baseDir = (testSrc == null) ? null : new File(testSrc); |
jjg@489 | 67 | boolean ok = new TreeScannerTest().run(baseDir, args); |
jjg@489 | 68 | if (!ok) { |
jjg@489 | 69 | if (testSrc != null) // jtreg mode |
jjg@489 | 70 | throw new Error("failed"); |
jjg@489 | 71 | else |
jjg@489 | 72 | System.exit(1); |
jjg@489 | 73 | } |
jjg@489 | 74 | } |
jjg@489 | 75 | |
jjg@489 | 76 | /** |
jjg@489 | 77 | * Run the program. A base directory can be provided for file arguments. |
jjg@489 | 78 | * In jtreg mode, the -r option can be given to change the default base |
jjg@489 | 79 | * directory to the test root directory. For other options, see usage(). |
jjg@489 | 80 | * @param baseDir base directory for any file arguments. |
jjg@489 | 81 | * @param args command line args |
jjg@489 | 82 | * @return true if successful or in gui mode |
jjg@489 | 83 | */ |
jjg@489 | 84 | boolean run(File baseDir, String... args) { |
jjg@489 | 85 | if (args.length == 0) { |
jjg@489 | 86 | usage(System.out); |
jjg@489 | 87 | return true; |
jjg@489 | 88 | } |
jjg@489 | 89 | |
jjg@489 | 90 | ArrayList<File> files = new ArrayList<File>(); |
jjg@489 | 91 | for (int i = 0; i < args.length; i++) { |
jjg@489 | 92 | String arg = args[i]; |
jjg@489 | 93 | if (arg.equals("-q")) |
jjg@489 | 94 | quiet = true; |
jjg@489 | 95 | else if (arg.equals("-v")) |
jjg@489 | 96 | verbose = true; |
jjg@489 | 97 | else if (arg.equals("-r")) { |
jjg@489 | 98 | File d = baseDir; |
jjg@489 | 99 | while (!new File(d, "TEST.ROOT").exists()) { |
jjg@489 | 100 | d = d.getParentFile(); |
jjg@489 | 101 | if (d == null) |
jjg@489 | 102 | throw new Error("cannot find TEST.ROOT"); |
jjg@489 | 103 | } |
jjg@489 | 104 | baseDir = d; |
jjg@489 | 105 | } |
jjg@489 | 106 | else if (arg.startsWith("-")) |
jjg@489 | 107 | throw new Error("unknown option: " + arg); |
jjg@489 | 108 | else { |
jjg@489 | 109 | while (i < args.length) |
jjg@489 | 110 | files.add(new File(baseDir, args[i++])); |
jjg@489 | 111 | } |
jjg@489 | 112 | } |
jjg@489 | 113 | |
jjg@489 | 114 | for (File file: files) { |
jjg@489 | 115 | if (file.exists()) |
jjg@489 | 116 | test(file); |
jjg@489 | 117 | else |
jjg@489 | 118 | error("File not found: " + file); |
jjg@489 | 119 | } |
jjg@489 | 120 | |
jjg@489 | 121 | if (fileCount != 1) |
jjg@489 | 122 | System.err.println(fileCount + " files read"); |
jjg@489 | 123 | if (errors > 0) |
jjg@489 | 124 | System.err.println(errors + " errors"); |
jjg@489 | 125 | |
jjg@489 | 126 | return (errors == 0); |
jjg@489 | 127 | } |
jjg@489 | 128 | |
jjg@489 | 129 | /** |
jjg@489 | 130 | * Print command line help. |
jjg@489 | 131 | * @param out output stream |
jjg@489 | 132 | */ |
jjg@489 | 133 | void usage(PrintStream out) { |
jjg@489 | 134 | out.println("Usage:"); |
jjg@489 | 135 | out.println(" java TreeScannerTest options... files..."); |
jjg@489 | 136 | out.println(""); |
jjg@489 | 137 | out.println("where options include:"); |
jjg@489 | 138 | out.println("-q Quiet: don't report on inapplicable files"); |
jjg@489 | 139 | out.println("-v Verbose: report on files as they are being read"); |
jjg@489 | 140 | out.println(""); |
jjg@489 | 141 | out.println("files may be directories or files"); |
jjg@489 | 142 | out.println("directories will be scanned recursively"); |
jjg@489 | 143 | out.println("non java files, or java files which cannot be parsed, will be ignored"); |
jjg@489 | 144 | out.println(""); |
jjg@489 | 145 | } |
jjg@489 | 146 | |
jjg@489 | 147 | /** |
jjg@489 | 148 | * Test a file. If the file is a directory, it will be recursively scanned |
jjg@489 | 149 | * for java files. |
jjg@489 | 150 | * @param file the file or directory to test |
jjg@489 | 151 | */ |
jjg@489 | 152 | void test(File file) { |
jjg@489 | 153 | if (file.isDirectory()) { |
jjg@489 | 154 | for (File f: file.listFiles()) { |
jjg@489 | 155 | test(f); |
jjg@489 | 156 | } |
jjg@489 | 157 | return; |
jjg@489 | 158 | } |
jjg@489 | 159 | |
jjg@489 | 160 | if (file.isFile() && file.getName().endsWith(".java")) { |
jjg@489 | 161 | try { |
jjg@489 | 162 | if (verbose) |
jjg@489 | 163 | System.err.println(file); |
jjg@489 | 164 | fileCount++; |
jjg@489 | 165 | ScanTester t = new ScanTester(); |
jjg@489 | 166 | t.test(read(file)); |
jjg@489 | 167 | } catch (ParseException e) { |
jjg@489 | 168 | if (!quiet) { |
jjg@489 | 169 | error("Error parsing " + file + "\n" + e.getMessage()); |
jjg@489 | 170 | } |
jjg@489 | 171 | } catch (IOException e) { |
jjg@489 | 172 | error("Error reading " + file + ": " + e); |
jjg@489 | 173 | } |
jjg@489 | 174 | return; |
jjg@489 | 175 | } |
jjg@489 | 176 | |
jjg@489 | 177 | if (!quiet) |
jjg@489 | 178 | error("File " + file + " ignored"); |
jjg@489 | 179 | } |
jjg@489 | 180 | |
jjg@489 | 181 | /** |
jjg@489 | 182 | * Read a file. |
jjg@489 | 183 | * @param file the file to be read |
jjg@489 | 184 | * @return the tree for the content of the file |
jjg@489 | 185 | * @throws IOException if any IO errors occur |
jjg@489 | 186 | * @throws TreePosTest.ParseException if any errors occur while parsing the file |
jjg@489 | 187 | */ |
jjg@489 | 188 | JCCompilationUnit read(File file) throws IOException, ParseException { |
jjg@489 | 189 | StringWriter sw = new StringWriter(); |
jjg@489 | 190 | PrintWriter pw = new PrintWriter(sw); |
jjg@489 | 191 | Reporter r = new Reporter(pw); |
jjg@489 | 192 | JavacTool tool = JavacTool.create(); |
jjg@489 | 193 | StandardJavaFileManager fm = tool.getStandardFileManager(r, null, null); |
jjg@489 | 194 | Iterable<? extends JavaFileObject> files = fm.getJavaFileObjects(file); |
jjg@489 | 195 | JavacTask task = tool.getTask(pw, fm, r, Collections.<String>emptyList(), null, files); |
jjg@489 | 196 | Iterable<? extends CompilationUnitTree> trees = task.parse(); |
jjg@489 | 197 | pw.flush(); |
jjg@489 | 198 | if (r.errors > 0) |
jjg@489 | 199 | throw new ParseException(sw.toString()); |
jjg@489 | 200 | Iterator<? extends CompilationUnitTree> iter = trees.iterator(); |
jjg@489 | 201 | if (!iter.hasNext()) |
jjg@489 | 202 | throw new Error("no trees found"); |
jjg@489 | 203 | JCCompilationUnit t = (JCCompilationUnit) iter.next(); |
jjg@489 | 204 | if (iter.hasNext()) |
jjg@489 | 205 | throw new Error("too many trees found"); |
jjg@489 | 206 | return t; |
jjg@489 | 207 | } |
jjg@489 | 208 | |
jjg@489 | 209 | /** |
jjg@489 | 210 | * Report an error. When the program is complete, the program will either |
jjg@489 | 211 | * exit or throw an Error if any errors have been reported. |
jjg@489 | 212 | * @param msg the error message |
jjg@489 | 213 | */ |
jjg@489 | 214 | void error(String msg) { |
jjg@489 | 215 | System.err.println(msg); |
jjg@489 | 216 | errors++; |
jjg@489 | 217 | } |
jjg@489 | 218 | |
jjg@489 | 219 | /** |
jjg@489 | 220 | * Report an error for a specific tree node. |
jjg@489 | 221 | * @param file the source file for the tree |
jjg@489 | 222 | * @param t the tree node |
jjg@489 | 223 | * @param label an indication of the error |
jjg@489 | 224 | */ |
jjg@489 | 225 | void error(JavaFileObject file, JCTree t, String msg) { |
jjg@489 | 226 | error(file.getName() + ":" + getLine(file, t) + ": " + msg + " " + trim(t, 64)); |
jjg@489 | 227 | } |
jjg@489 | 228 | |
jjg@489 | 229 | /** |
jjg@489 | 230 | * Get a trimmed string for a tree node, with normalized white space and limited length. |
jjg@489 | 231 | */ |
jjg@489 | 232 | String trim(JCTree t, int len) { |
jjg@489 | 233 | String s = t.toString().replaceAll("[\r\n]+", " ").replaceAll(" +", " "); |
jjg@489 | 234 | return (s.length() < len) ? s : s.substring(0, len); |
jjg@489 | 235 | } |
jjg@489 | 236 | |
jjg@489 | 237 | /** Number of files that have been analyzed. */ |
jjg@489 | 238 | int fileCount; |
jjg@489 | 239 | /** Number of errors reported. */ |
jjg@489 | 240 | int errors; |
jjg@489 | 241 | /** Flag: don't report irrelevant files. */ |
jjg@489 | 242 | boolean quiet; |
jjg@489 | 243 | /** Flag: report files as they are processed. */ |
jjg@489 | 244 | boolean verbose; |
jjg@489 | 245 | |
jjg@489 | 246 | /** |
jjg@489 | 247 | * Main class for testing operation of tree scanner. |
jjg@489 | 248 | * The set of nodes found by the scanner are compared |
jjg@489 | 249 | * against the set of nodes found by reflection. |
jjg@489 | 250 | */ |
jjg@489 | 251 | private class ScanTester extends TreeScanner { |
jjg@489 | 252 | /** Main entry method for the class. */ |
jjg@489 | 253 | void test(JCCompilationUnit tree) { |
jjg@489 | 254 | sourcefile = tree.sourcefile; |
jjg@489 | 255 | found = new HashSet<JCTree>(); |
jjg@489 | 256 | scan(tree); |
jjg@489 | 257 | expect = new HashSet<JCTree>(); |
jjg@489 | 258 | reflectiveScan(tree); |
jjg@489 | 259 | if (found.equals(expect)) |
jjg@489 | 260 | return; |
jjg@489 | 261 | |
jjg@489 | 262 | error("Differences found for " + tree.sourcefile.getName()); |
jjg@489 | 263 | |
jjg@489 | 264 | if (found.size() != expect.size()) |
jjg@489 | 265 | error("Size mismatch; found: " + found.size() + ", expected: " + expect.size()); |
jjg@489 | 266 | |
jjg@489 | 267 | Set<JCTree> missing = new HashSet<JCTree>(); |
jjg@489 | 268 | missing.addAll(expect); |
jjg@489 | 269 | missing.removeAll(found); |
jjg@489 | 270 | for (JCTree t: missing) |
jjg@489 | 271 | error(tree.sourcefile, t, "missing"); |
jjg@489 | 272 | |
jjg@489 | 273 | Set<JCTree> excess = new HashSet<JCTree>(); |
jjg@489 | 274 | excess.addAll(found); |
jjg@489 | 275 | excess.removeAll(expect); |
jjg@489 | 276 | for (JCTree t: excess) |
jjg@489 | 277 | error(tree.sourcefile, t, "unexpected"); |
jjg@489 | 278 | } |
jjg@489 | 279 | |
jjg@489 | 280 | /** Record all tree nodes found by scanner. */ |
jjg@489 | 281 | @Override |
jjg@489 | 282 | public void scan(JCTree tree) { |
jjg@489 | 283 | if (tree == null) |
jjg@489 | 284 | return; |
jjg@489 | 285 | System.err.println("FOUND: " + tree.getTag() + " " + trim(tree, 64)); |
jjg@489 | 286 | found.add(tree); |
jjg@489 | 287 | super.scan(tree); |
jjg@489 | 288 | } |
jjg@489 | 289 | |
jjg@489 | 290 | /** record all tree nodes found by reflection. */ |
jjg@489 | 291 | public void reflectiveScan(Object o) { |
jjg@489 | 292 | if (o == null) |
jjg@489 | 293 | return; |
jjg@489 | 294 | if (o instanceof JCTree) { |
jjg@489 | 295 | JCTree tree = (JCTree) o; |
jjg@489 | 296 | System.err.println("EXPECT: " + tree.getTag() + " " + trim(tree, 64)); |
jjg@489 | 297 | expect.add(tree); |
jjg@489 | 298 | for (Field f: getFields(tree)) { |
jjg@489 | 299 | try { |
jjg@489 | 300 | //System.err.println("FIELD: " + f.getName()); |
jjg@489 | 301 | reflectiveScan(f.get(tree)); |
jjg@489 | 302 | } catch (IllegalAccessException e) { |
jjg@489 | 303 | error(e.toString()); |
jjg@489 | 304 | } |
jjg@489 | 305 | } |
jjg@489 | 306 | } else if (o instanceof List) { |
jjg@489 | 307 | List<?> list = (List<?>) o; |
jjg@489 | 308 | for (Object item: list) |
jjg@489 | 309 | reflectiveScan(item); |
jjg@489 | 310 | } else |
jjg@489 | 311 | error("unexpected item: " + o); |
jjg@489 | 312 | } |
jjg@489 | 313 | |
jjg@489 | 314 | JavaFileObject sourcefile; |
jjg@489 | 315 | Set<JCTree> found; |
jjg@489 | 316 | Set<JCTree> expect; |
jjg@489 | 317 | } |
jjg@489 | 318 | |
jjg@489 | 319 | /** |
jjg@489 | 320 | * Thrown when errors are found parsing a java file. |
jjg@489 | 321 | */ |
jjg@489 | 322 | private static class ParseException extends Exception { |
jjg@489 | 323 | ParseException(String msg) { |
jjg@489 | 324 | super(msg); |
jjg@489 | 325 | } |
jjg@489 | 326 | } |
jjg@489 | 327 | |
jjg@489 | 328 | /** |
jjg@489 | 329 | * DiagnosticListener to report diagnostics and count any errors that occur. |
jjg@489 | 330 | */ |
jjg@489 | 331 | private static class Reporter implements DiagnosticListener<JavaFileObject> { |
jjg@489 | 332 | Reporter(PrintWriter out) { |
jjg@489 | 333 | this.out = out; |
jjg@489 | 334 | } |
jjg@489 | 335 | |
jjg@489 | 336 | public void report(Diagnostic<? extends JavaFileObject> diagnostic) { |
jjg@489 | 337 | out.println(diagnostic); |
jjg@489 | 338 | switch (diagnostic.getKind()) { |
jjg@489 | 339 | case ERROR: |
jjg@489 | 340 | errors++; |
jjg@489 | 341 | } |
jjg@489 | 342 | } |
jjg@489 | 343 | int errors; |
jjg@489 | 344 | PrintWriter out; |
jjg@489 | 345 | } |
jjg@489 | 346 | |
jjg@489 | 347 | /** |
jjg@489 | 348 | * Get the set of fields for a tree node that may contain child tree nodes. |
jjg@489 | 349 | * These are the fields that are subtypes of JCTree or List. |
jjg@489 | 350 | * The results are cached, based on the tree's tag. |
jjg@489 | 351 | */ |
jjg@489 | 352 | Set<Field> getFields(JCTree tree) { |
jjg@489 | 353 | Set<Field> fields = map.get(tree.getTag()); |
jjg@489 | 354 | if (fields == null) { |
jjg@489 | 355 | fields = new HashSet<Field>(); |
jjg@489 | 356 | for (Field f: tree.getClass().getFields()) { |
jjg@489 | 357 | Class<?> fc = f.getType(); |
jjg@489 | 358 | if (JCTree.class.isAssignableFrom(fc) || List.class.isAssignableFrom(fc)) |
jjg@489 | 359 | fields.add(f); |
jjg@489 | 360 | } |
jjg@489 | 361 | map.put(tree.getTag(), fields); |
jjg@489 | 362 | } |
jjg@489 | 363 | return fields; |
jjg@489 | 364 | } |
jjg@489 | 365 | // where |
jjg@489 | 366 | Map<Integer, Set<Field>> map = new HashMap<Integer,Set<Field>>(); |
jjg@489 | 367 | |
jjg@489 | 368 | /** Get the line number for the primary position for a tree. |
jjg@489 | 369 | * The code is intended to be simple, although not necessarily efficient. |
jjg@489 | 370 | * However, note that a file manager such as JavacFileManager is likely |
jjg@489 | 371 | * to cache the results of file.getCharContent, avoiding the need to read |
jjg@489 | 372 | * the bits from disk each time this method is called. |
jjg@489 | 373 | */ |
jjg@489 | 374 | int getLine(JavaFileObject file, JCTree tree) { |
jjg@489 | 375 | try { |
jjg@489 | 376 | CharSequence cs = file.getCharContent(true); |
jjg@489 | 377 | int line = 1; |
jjg@489 | 378 | for (int i = 0; i < tree.pos; i++) { |
jjg@489 | 379 | if (cs.charAt(i) == '\n') // jtreg tests always use Unix line endings |
jjg@489 | 380 | line++; |
jjg@489 | 381 | } |
jjg@489 | 382 | return line; |
jjg@489 | 383 | } catch (IOException e) { |
jjg@489 | 384 | return -1; |
jjg@489 | 385 | } |
jjg@489 | 386 | } |
jjg@489 | 387 | } |