Example.java revision 2734:b96d74fa60aa
1/* 2 * Copyright (c) 2010, 2014, 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 24import java.io.*; 25import java.net.URL; 26import java.net.URLClassLoader; 27import java.util.*; 28import java.util.regex.*; 29import javax.annotation.processing.Processor; 30import javax.tools.Diagnostic; 31import javax.tools.DiagnosticCollector; 32import javax.tools.JavaCompiler; 33import javax.tools.JavaCompiler.CompilationTask; 34import javax.tools.JavaFileManager; 35import javax.tools.JavaFileObject; 36import javax.tools.StandardJavaFileManager; 37import javax.tools.ToolProvider; 38 39// The following two classes are both used, but cannot be imported directly 40// import com.sun.tools.javac.Main 41// import com.sun.tools.javac.main.Main 42 43import com.sun.tools.javac.api.ClientCodeWrapper; 44import com.sun.tools.javac.file.JavacFileManager; 45import com.sun.tools.javac.main.Main; 46import com.sun.tools.javac.util.Context; 47import com.sun.tools.javac.util.JavacMessages; 48import com.sun.tools.javac.util.JCDiagnostic; 49 50/** 51 * Class to handle example code designed to illustrate javac diagnostic messages. 52 */ 53class Example implements Comparable<Example> { 54 /* Create an Example from the files found at path. 55 * The head of the file, up to the first Java code, is scanned 56 * for information about the test, such as what resource keys it 57 * generates when run, what options are required to run it, and so on. 58 */ 59 Example(File file) { 60 this.file = file; 61 declaredKeys = new TreeSet<String>(); 62 srcFiles = new ArrayList<File>(); 63 procFiles = new ArrayList<File>(); 64 supportFiles = new ArrayList<File>(); 65 srcPathFiles = new ArrayList<File>(); 66 67 findFiles(file, srcFiles); 68 for (File f: srcFiles) { 69 parse(f); 70 } 71 72 if (infoFile == null) 73 throw new Error("Example " + file + " has no info file"); 74 } 75 76 private void findFiles(File f, List<File> files) { 77 if (f.isDirectory()) { 78 for (File c: f.listFiles()) { 79 if (files == srcFiles && c.getName().equals("processors")) 80 findFiles(c, procFiles); 81 else if (files == srcFiles && c.getName().equals("sourcepath")) { 82 srcPathDir = c; 83 findFiles(c, srcPathFiles); 84 } else if (files == srcFiles && c.getName().equals("support")) 85 findFiles(c, supportFiles); 86 else 87 findFiles(c, files); 88 } 89 } else if (f.isFile() && f.getName().endsWith(".java")) { 90 files.add(f); 91 } 92 } 93 94 private void parse(File f) { 95 Pattern keyPat = Pattern.compile(" *// *key: *([^ ]+) *"); 96 Pattern optPat = Pattern.compile(" *// *options: *(.*)"); 97 Pattern runPat = Pattern.compile(" *// *run: *(.*)"); 98 Pattern javaPat = Pattern.compile(" *@?[A-Za-z].*"); 99 try { 100 String[] lines = read(f).split("[\r\n]+"); 101 for (String line: lines) { 102 Matcher keyMatch = keyPat.matcher(line); 103 if (keyMatch.matches()) { 104 foundInfo(f); 105 declaredKeys.add(keyMatch.group(1)); 106 continue; 107 } 108 Matcher optMatch = optPat.matcher(line); 109 if (optMatch.matches()) { 110 foundInfo(f); 111 options = Arrays.asList(optMatch.group(1).trim().split(" +")); 112 continue; 113 } 114 Matcher runMatch = runPat.matcher(line); 115 if (runMatch.matches()) { 116 foundInfo(f); 117 runOpts = Arrays.asList(runMatch.group(1).trim().split(" +")); 118 } 119 if (javaPat.matcher(line).matches()) 120 break; 121 } 122 } catch (IOException e) { 123 throw new Error(e); 124 } 125 } 126 127 private void foundInfo(File file) { 128 if (infoFile != null && !infoFile.equals(file)) 129 throw new Error("multiple info files found: " + infoFile + ", " + file); 130 infoFile = file; 131 } 132 133 String getName() { 134 return file.getName(); 135 } 136 137 /** 138 * Get the set of resource keys that this test declares it will generate 139 * when it is run. 140 */ 141 Set<String> getDeclaredKeys() { 142 return declaredKeys; 143 } 144 145 /** 146 * Get the set of resource keys that this test generates when it is run. 147 * The test will be run if it has not already been run. 148 */ 149 Set<String> getActualKeys() { 150 if (actualKeys == null) 151 actualKeys = run(false); 152 return actualKeys; 153 } 154 155 /** 156 * Run the test. Information in the test header is used to determine 157 * how to run the test. 158 */ 159 void run(PrintWriter out, boolean raw, boolean verbose) { 160 if (out == null) 161 throw new NullPointerException(); 162 try { 163 run(out, null, raw, verbose); 164 } catch (IOException e) { 165 e.printStackTrace(out); 166 } 167 } 168 169 Set<String> run(boolean verbose) { 170 Set<String> keys = new TreeSet<String>(); 171 try { 172 run(null, keys, true, verbose); 173 } catch (IOException e) { 174 e.printStackTrace(System.err); 175 } 176 return keys; 177 } 178 179 /** 180 * Run the test. Information in the test header is used to determine 181 * how to run the test. 182 */ 183 private void run(PrintWriter out, Set<String> keys, boolean raw, boolean verbose) 184 throws IOException { 185 ClassLoader loader = getClass().getClassLoader(); 186 if (supportFiles.size() > 0) { 187 File supportDir = new File(tempDir, "support"); 188 supportDir.mkdirs(); 189 clean(supportDir); 190 List<String> sOpts = Arrays.asList("-d", supportDir.getPath()); 191 new Jsr199Compiler(verbose).run(null, null, false, sOpts, procFiles); 192 URLClassLoader ucl = 193 new URLClassLoader(new URL[] { supportDir.toURI().toURL() }, loader); 194 loader = ucl; 195 } 196 197 File classesDir = new File(tempDir, "classes"); 198 classesDir.mkdirs(); 199 clean(classesDir); 200 201 List<String> opts = new ArrayList<String>(); 202 opts.add("-d"); 203 opts.add(classesDir.getPath()); 204 if (options != null) 205 opts.addAll(options); 206 207 if (procFiles.size() > 0) { 208 List<String> pOpts = Arrays.asList("-d", classesDir.getPath()); 209 new Jsr199Compiler(verbose).run(null, null, false, pOpts, procFiles); 210 opts.add("-classpath"); // avoid using -processorpath for now 211 opts.add(classesDir.getPath()); 212 createAnnotationServicesFile(classesDir, procFiles); 213 } else if (options != null) { 214 int i = options.indexOf("-processor"); 215 // check for built-in anno-processor(s) 216 if (i != -1 && options.get(i + 1).equals("DocCommentProcessor")) { 217 opts.add("-classpath"); 218 opts.add(System.getProperty("test.classes")); 219 } 220 } 221 222 if (srcPathDir != null) { 223 opts.add("-sourcepath"); 224 opts.add(srcPathDir.getPath()); 225 } 226 227 try { 228 Compiler c = Compiler.getCompiler(runOpts, verbose); 229 c.run(out, keys, raw, opts, srcFiles); 230 } catch (IllegalArgumentException e) { 231 if (out != null) { 232 out.println("Invalid value for run tag: " + runOpts); 233 } 234 } 235 } 236 237 void createAnnotationServicesFile(File dir, List<File> procFiles) throws IOException { 238 File servicesDir = new File(new File(dir, "META-INF"), "services"); 239 servicesDir.mkdirs(); 240 File annoServices = new File(servicesDir, Processor.class.getName()); 241 Writer out = new FileWriter(annoServices); 242 try { 243 for (File f: procFiles) { 244 out.write(f.getName().toString().replace(".java", "")); 245 } 246 } finally { 247 out.close(); 248 } 249 } 250 251 @Override 252 public int compareTo(Example e) { 253 return file.compareTo(e.file); 254 } 255 256 @Override 257 public String toString() { 258 return file.getPath(); 259 } 260 261 /** 262 * Read the contents of a file. 263 */ 264 private String read(File f) throws IOException { 265 byte[] bytes = new byte[(int) f.length()]; 266 DataInputStream in = new DataInputStream(new FileInputStream(f)); 267 try { 268 in.readFully(bytes); 269 } finally { 270 in.close(); 271 } 272 return new String(bytes); 273 } 274 275 /** 276 * Clean the contents of a directory. 277 */ 278 boolean clean(File dir) { 279 boolean ok = true; 280 for (File f: dir.listFiles()) { 281 if (f.isDirectory()) 282 ok &= clean(f); 283 ok &= f.delete(); 284 } 285 return ok; 286 } 287 288 File file; 289 List<File> srcFiles; 290 List<File> procFiles; 291 File srcPathDir; 292 List<File> srcPathFiles; 293 List<File> supportFiles; 294 File infoFile; 295 private List<String> runOpts; 296 private List<String> options; 297 private Set<String> actualKeys; 298 private Set<String> declaredKeys; 299 300 static File tempDir = (System.getProperty("test.src") != null) ? 301 new File(System.getProperty("user.dir")): 302 new File(System.getProperty("java.io.tmpdir")); 303 304 static void setTempDir(File tempDir) { 305 Example.tempDir = tempDir; 306 } 307 308 abstract static class Compiler { 309 interface Factory { 310 Compiler getCompiler(List<String> opts, boolean verbose); 311 } 312 313 static class DefaultFactory implements Factory { 314 public Compiler getCompiler(List<String> opts, boolean verbose) { 315 String first; 316 String[] rest; 317 if (opts == null || opts.isEmpty()) { 318 first = null; 319 rest = new String[0]; 320 } else { 321 first = opts.get(0); 322 rest = opts.subList(1, opts.size()).toArray(new String[opts.size() - 1]); 323 } 324 // For more details on the different compilers, 325 // see their respective class doc comments. 326 // See also README.examples.txt in this directory. 327 if (first == null || first.equals("jsr199")) 328 return new Jsr199Compiler(verbose, rest); 329 else if (first.equals("simple")) 330 return new SimpleCompiler(verbose); 331 else if (first.equals("backdoor")) 332 return new BackdoorCompiler(verbose); 333 else if (first.equals("exec")) 334 return new ExecCompiler(verbose); 335 else 336 throw new IllegalArgumentException(first); 337 } 338 } 339 340 static Factory factory; 341 342 static Compiler getCompiler(List<String> opts, boolean verbose) { 343 if (factory == null) 344 factory = new DefaultFactory(); 345 346 return factory.getCompiler(opts, verbose); 347 } 348 349 protected Compiler(boolean verbose) { 350 this.verbose = verbose; 351 } 352 353 abstract boolean run(PrintWriter out, Set<String> keys, boolean raw, 354 List<String> opts, List<File> files); 355 356 void setSupportClassLoader(ClassLoader cl) { 357 loader = cl; 358 } 359 360 protected void close(JavaFileManager fm) { 361 try { 362 fm.close(); 363 } catch (IOException e) { 364 throw new Error(e); 365 } 366 } 367 368 protected ClassLoader loader; 369 protected boolean verbose; 370 } 371 372 /** 373 * Compile using the JSR 199 API. The diagnostics generated are 374 * scanned for resource keys. Not all diagnostic keys are generated 375 * via the JSR 199 API -- for example, rich diagnostics are not directly 376 * accessible, and some diagnostics generated by the file manager may 377 * not be generated (for example, the JSR 199 file manager does not see 378 * -Xlint:path). 379 */ 380 static class Jsr199Compiler extends Compiler { 381 List<String> fmOpts; 382 383 Jsr199Compiler(boolean verbose, String... args) { 384 super(verbose); 385 for (int i = 0; i < args.length; i++) { 386 String arg = args[i]; 387 if (arg.equals("-filemanager") && (i + 1 < args.length)) { 388 fmOpts = Arrays.asList(args[++i].split(",")); 389 } else 390 throw new IllegalArgumentException(arg); 391 } 392 } 393 394 @Override 395 boolean run(PrintWriter out, Set<String> keys, boolean raw, List<String> opts, List<File> files) { 396 if (out != null && keys != null) 397 throw new IllegalArgumentException(); 398 399 if (verbose) 400 System.err.println("run_jsr199: " + opts + " " + files); 401 402 DiagnosticCollector<JavaFileObject> dc = null; 403 if (keys != null) 404 dc = new DiagnosticCollector<JavaFileObject>(); 405 406 if (raw) { 407 List<String> newOpts = new ArrayList<String>(); 408 newOpts.add("-XDrawDiagnostics"); 409 newOpts.addAll(opts); 410 opts = newOpts; 411 } 412 413 JavaCompiler c = ToolProvider.getSystemJavaCompiler(); 414 415 StandardJavaFileManager fm = c.getStandardFileManager(dc, null, null); 416 try { 417 if (fmOpts != null) 418 fm = new FileManager(fm, fmOpts); 419 420 Iterable<? extends JavaFileObject> fos = fm.getJavaFileObjectsFromFiles(files); 421 422 CompilationTask t = c.getTask(out, fm, dc, opts, null, fos); 423 Boolean ok = t.call(); 424 425 if (keys != null) { 426 for (Diagnostic<? extends JavaFileObject> d: dc.getDiagnostics()) { 427 scanForKeys(unwrap(d), keys); 428 } 429 } 430 431 return ok; 432 } finally { 433 close(fm); 434 } 435 } 436 437 /** 438 * Scan a diagnostic for resource keys. This will not detect additional 439 * sub diagnostics that might be generated by a rich diagnostic formatter. 440 */ 441 private static void scanForKeys(JCDiagnostic d, Set<String> keys) { 442 keys.add(d.getCode()); 443 for (Object o: d.getArgs()) { 444 if (o instanceof JCDiagnostic) { 445 scanForKeys((JCDiagnostic) o, keys); 446 } 447 } 448 for (JCDiagnostic sd: d.getSubdiagnostics()) 449 scanForKeys(sd, keys); 450 } 451 452 private JCDiagnostic unwrap(Diagnostic<? extends JavaFileObject> diagnostic) { 453 if (diagnostic instanceof JCDiagnostic) 454 return (JCDiagnostic) diagnostic; 455 if (diagnostic instanceof ClientCodeWrapper.DiagnosticSourceUnwrapper) 456 return ((ClientCodeWrapper.DiagnosticSourceUnwrapper)diagnostic).d; 457 throw new IllegalArgumentException(); 458 } 459 } 460 461 /** 462 * Run the test using the standard simple entry point. 463 */ 464 static class SimpleCompiler extends Compiler { 465 SimpleCompiler(boolean verbose) { 466 super(verbose); 467 } 468 469 @Override 470 boolean run(PrintWriter out, Set<String> keys, boolean raw, List<String> opts, List<File> files) { 471 if (out != null && keys != null) 472 throw new IllegalArgumentException(); 473 474 if (verbose) 475 System.err.println("run_simple: " + opts + " " + files); 476 477 List<String> args = new ArrayList<String>(); 478 479 if (keys != null || raw) 480 args.add("-XDrawDiagnostics"); 481 482 args.addAll(opts); 483 for (File f: files) 484 args.add(f.getPath()); 485 486 StringWriter sw = null; 487 PrintWriter pw; 488 if (keys != null) { 489 sw = new StringWriter(); 490 pw = new PrintWriter(sw); 491 } else 492 pw = out; 493 494 int rc = com.sun.tools.javac.Main.compile(args.toArray(new String[args.size()]), pw); 495 496 if (keys != null) { 497 pw.close(); 498 scanForKeys(sw.toString(), keys); 499 } 500 501 return (rc == 0); 502 } 503 504 private static void scanForKeys(String text, Set<String> keys) { 505 StringTokenizer st = new StringTokenizer(text, " ,\r\n():"); 506 while (st.hasMoreElements()) { 507 String t = st.nextToken(); 508 if (t.startsWith("compiler.")) 509 keys.add(t); 510 } 511 } 512 } 513 514 /** 515 * Run the test in a separate process. 516 */ 517 static class ExecCompiler extends Compiler { 518 ExecCompiler(boolean verbose) { 519 super(verbose); 520 } 521 522 @Override 523 boolean run(PrintWriter out, Set<String> keys, boolean raw, List<String> opts, List<File> files) { 524 if (out != null && keys != null) 525 throw new IllegalArgumentException(); 526 527 if (verbose) 528 System.err.println("run_exec: " + opts + " " + files); 529 530 List<String> args = new ArrayList<String>(); 531 532 File javaHome = new File(System.getProperty("java.home")); 533 if (javaHome.getName().equals("jre")) 534 javaHome = javaHome.getParentFile(); 535 File javaExe = new File(new File(javaHome, "bin"), "java"); 536 args.add(javaExe.getPath()); 537 538 File toolsJar = new File(new File(javaHome, "lib"), "tools.jar"); 539 if (toolsJar.exists()) { 540 args.add("-classpath"); 541 args.add(toolsJar.getPath()); 542 } 543 544 addOpts(args, "test.vm.opts"); 545 addOpts(args, "test.java.opts"); 546 args.add(com.sun.tools.javac.Main.class.getName()); 547 548 if (keys != null || raw) 549 args.add("-XDrawDiagnostics"); 550 551 args.addAll(opts); 552 for (File f: files) 553 args.add(f.getPath()); 554 555 try { 556 ProcessBuilder pb = new ProcessBuilder(args); 557 pb.redirectErrorStream(true); 558 Process p = pb.start(); 559 BufferedReader in = new BufferedReader(new InputStreamReader(p.getInputStream())); 560 String line; 561 while ((line = in.readLine()) != null) { 562 if (keys != null) 563 scanForKeys(line, keys); 564 } 565 int rc = p.waitFor(); 566 567 return (rc == 0); 568 } catch (IOException | InterruptedException e) { 569 System.err.println("Exception execing javac" + e); 570 System.err.println("Command line: " + opts); 571 return false; 572 } 573 } 574 575 private static void scanForKeys(String text, Set<String> keys) { 576 StringTokenizer st = new StringTokenizer(text, " ,\r\n():"); 577 while (st.hasMoreElements()) { 578 String t = st.nextToken(); 579 if (t.startsWith("compiler.")) 580 keys.add(t); 581 } 582 } 583 584 private static void addOpts(List<String> args, String propName) { 585 String propValue = System.getProperty(propName); 586 if (propValue == null || propValue.isEmpty()) 587 return; 588 args.addAll(Arrays.asList(propValue.split(" +", 0))); 589 } 590 } 591 592 static class BackdoorCompiler extends Compiler { 593 BackdoorCompiler(boolean verbose) { 594 super(verbose); 595 } 596 597 @Override 598 boolean run(PrintWriter out, Set<String> keys, boolean raw, List<String> opts, List<File> files) { 599 if (out != null && keys != null) 600 throw new IllegalArgumentException(); 601 602 if (verbose) 603 System.err.println("run_simple: " + opts + " " + files); 604 605 List<String> args = new ArrayList<String>(); 606 607 if (out != null && raw) 608 args.add("-XDrawDiagnostics"); 609 610 args.addAll(opts); 611 for (File f: files) 612 args.add(f.getPath()); 613 614 StringWriter sw = null; 615 PrintWriter pw; 616 if (keys != null) { 617 sw = new StringWriter(); 618 pw = new PrintWriter(sw); 619 } else 620 pw = out; 621 622 Context c = new Context(); 623 JavacFileManager.preRegister(c); // can't create it until Log has been set up 624 MessageTracker.preRegister(c, keys); 625 626 try { 627 Main m = new Main("javac", pw); 628 Main.Result rc = m.compile(args.toArray(new String[args.size()]), c); 629 630 if (keys != null) { 631 pw.close(); 632 } 633 634 return rc.isOK(); 635 } finally { 636 close(c.get(JavaFileManager.class)); 637 } 638 } 639 640 static class MessageTracker extends JavacMessages { 641 642 MessageTracker(Context context) { 643 super(context); 644 } 645 646 static void preRegister(Context c, final Set<String> keys) { 647 if (keys != null) { 648 c.put(JavacMessages.messagesKey, new Context.Factory<JavacMessages>() { 649 public JavacMessages make(Context c) { 650 return new MessageTracker(c) { 651 @Override 652 public String getLocalizedString(Locale l, String key, Object... args) { 653 keys.add(key); 654 return super.getLocalizedString(l, key, args); 655 } 656 }; 657 } 658 }); 659 } 660 } 661 } 662 663 } 664} 665