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