Example.java revision 3875:f94e974fe589
1219820Sjeff/* 2219820Sjeff * Copyright (c) 2010, 2016, Oracle and/or its affiliates. All rights reserved. 3219820Sjeff * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4219820Sjeff * 5328653Shselasky * This code is free software; you can redistribute it and/or modify it 6219820Sjeff * under the terms of the GNU General Public License version 2 only, as 7219820Sjeff * published by the Free Software Foundation. 8219820Sjeff * 9219820Sjeff * This code is distributed in the hope that it will be useful, but WITHOUT 10219820Sjeff * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11219820Sjeff * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12219820Sjeff * version 2 for more details (a copy is included in the LICENSE file that 13219820Sjeff * accompanied this code). 14219820Sjeff * 15219820Sjeff * You should have received a copy of the GNU General Public License version 16219820Sjeff * 2 along with this work; if not, write to the Free Software Foundation, 17219820Sjeff * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18219820Sjeff * 19219820Sjeff * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20219820Sjeff * or visit www.oracle.com if you need additional information or have any 21219820Sjeff * questions. 22219820Sjeff */ 23219820Sjeff 24219820Sjeffimport java.io.*; 25219820Sjeffimport java.net.URL; 26219820Sjeffimport java.net.URLClassLoader; 27219820Sjeffimport java.util.*; 28289644Shselaskyimport java.util.regex.*; 29289644Shselaskyimport javax.annotation.processing.Processor; 30219820Sjeffimport javax.tools.Diagnostic; 31219820Sjeffimport javax.tools.DiagnosticCollector; 32219820Sjeffimport javax.tools.JavaCompiler; 33219820Sjeffimport javax.tools.JavaCompiler.CompilationTask; 34219820Sjeffimport javax.tools.JavaFileManager; 35219820Sjeffimport javax.tools.JavaFileObject; 36270710Shselaskyimport javax.tools.StandardJavaFileManager; 37270710Shselaskyimport javax.tools.ToolProvider; 38219820Sjeff 39219820Sjeff// The following two classes are both used, but cannot be imported directly 40219820Sjeff// import com.sun.tools.javac.Main 41270710Shselasky// import com.sun.tools.javac.main.Main 42270710Shselasky 43270710Shselaskyimport com.sun.tools.javac.api.ClientCodeWrapper; 44270710Shselaskyimport com.sun.tools.javac.file.JavacFileManager; 45270710Shselaskyimport com.sun.tools.javac.main.Main; 46228443Smdfimport com.sun.tools.javac.util.Context; 47270710Shselaskyimport com.sun.tools.javac.util.JavacMessages; 48219820Sjeffimport com.sun.tools.javac.util.JCDiagnostic; 49270710Shselasky 50270710Shselasky/** 51270710Shselasky * Class to handle example code designed to illustrate javac diagnostic messages. 52270710Shselasky */ 53270710Shselaskyclass Example implements Comparable<Example> { 54270710Shselasky /* Create an Example from the files found at path. 55255932Salfred * The head of the file, up to the first Java code, is scanned 56219820Sjeff * for information about the test, such as what resource keys it 57219820Sjeff * generates when run, what options are required to run it, and so on. 58219820Sjeff */ 59219820Sjeff Example(File file) { 60328653Shselasky this.file = file; 61328653Shselasky declaredKeys = new TreeSet<String>(); 62219820Sjeff srcFiles = new ArrayList<File>(); 63270710Shselasky procFiles = new ArrayList<File>(); 64270710Shselasky srcPathFiles = new ArrayList<File>(); 65335434Shselasky moduleSourcePathFiles = new ArrayList<File>(); 66335434Shselasky modulePathFiles = new ArrayList<File>(); 67219820Sjeff classPathFiles = new ArrayList<File>(); 68219820Sjeff additionalFiles = new ArrayList<File>(); 69219820Sjeff 70328653Shselasky findFiles(file, srcFiles); 71328653Shselasky for (File f: srcFiles) { 72328653Shselasky parse(f); 73328653Shselasky } 74328653Shselasky 75328653Shselasky if (infoFile == null) 76328653Shselasky throw new Error("Example " + file + " has no info file"); 77328653Shselasky } 78328653Shselasky 79328653Shselasky private void findFiles(File f, List<File> files) { 80219820Sjeff if (f.isDirectory()) { 81 for (File c: f.listFiles()) { 82 if (files == srcFiles && c.getName().equals("processors")) 83 findFiles(c, procFiles); 84 else if (files == srcFiles && c.getName().equals("sourcepath")) { 85 srcPathDir = c; 86 findFiles(c, srcPathFiles); 87 } else if (files == srcFiles && c.getName().equals("modulesourcepath")) { 88 moduleSourcePathDir = c; 89 findFiles(c, moduleSourcePathFiles); 90 } else if (files == srcFiles && c.getName().equals("additional")) { 91 additionalFilesDir = c; 92 findFiles(c, additionalFiles); 93 } else if (files == srcFiles && c.getName().equals("modulepath")) { 94 findFiles(c, modulePathFiles); 95 } else if (files == srcFiles && c.getName().equals("classpath")) { 96 findFiles(c, classPathFiles); 97 } else { 98 findFiles(c, files); 99 } 100 } 101 } else if (f.isFile()) { 102 if (f.getName().endsWith(".java")) { 103 files.add(f); 104 } else if (f.getName().equals("modulesourcepath")) { 105 moduleSourcePathDir = f; 106 } 107 } 108 } 109 110 private void parse(File f) { 111 Pattern keyPat = Pattern.compile(" *// *key: *([^ ]+) *"); 112 Pattern optPat = Pattern.compile(" *// *options: *(.*)"); 113 Pattern runPat = Pattern.compile(" *// *run: *(.*)"); 114 Pattern javaPat = Pattern.compile(" *@?[A-Za-z].*"); 115 try { 116 String[] lines = read(f).split("[\r\n]+"); 117 for (String line: lines) { 118 Matcher keyMatch = keyPat.matcher(line); 119 if (keyMatch.matches()) { 120 foundInfo(f); 121 declaredKeys.add(keyMatch.group(1)); 122 continue; 123 } 124 Matcher optMatch = optPat.matcher(line); 125 if (optMatch.matches()) { 126 foundInfo(f); 127 options = Arrays.asList(optMatch.group(1).trim().split(" +")); 128 continue; 129 } 130 Matcher runMatch = runPat.matcher(line); 131 if (runMatch.matches()) { 132 foundInfo(f); 133 runOpts = Arrays.asList(runMatch.group(1).trim().split(" +")); 134 } 135 if (javaPat.matcher(line).matches()) 136 break; 137 } 138 } catch (IOException e) { 139 throw new Error(e); 140 } 141 } 142 143 private void foundInfo(File file) { 144 if (infoFile != null && !infoFile.equals(file)) 145 throw new Error("multiple info files found: " + infoFile + ", " + file); 146 infoFile = file; 147 } 148 149 String getName() { 150 return file.getName(); 151 } 152 153 /** 154 * Get the set of resource keys that this test declares it will generate 155 * when it is run. 156 */ 157 Set<String> getDeclaredKeys() { 158 return declaredKeys; 159 } 160 161 /** 162 * Get the set of resource keys that this test generates when it is run. 163 * The test will be run if it has not already been run. 164 */ 165 Set<String> getActualKeys() { 166 if (actualKeys == null) 167 actualKeys = run(false); 168 return actualKeys; 169 } 170 171 /** 172 * Run the test. Information in the test header is used to determine 173 * how to run the test. 174 */ 175 void run(PrintWriter out, boolean raw, boolean verbose) { 176 if (out == null) 177 throw new NullPointerException(); 178 try { 179 run(out, null, raw, verbose); 180 } catch (IOException e) { 181 e.printStackTrace(out); 182 } 183 } 184 185 Set<String> run(boolean verbose) { 186 Set<String> keys = new TreeSet<String>(); 187 try { 188 run(null, keys, true, verbose); 189 } catch (IOException e) { 190 e.printStackTrace(System.err); 191 } 192 return keys; 193 } 194 195 /** 196 * Run the test. Information in the test header is used to determine 197 * how to run the test. 198 */ 199 private void run(PrintWriter out, Set<String> keys, boolean raw, boolean verbose) 200 throws IOException { 201 List<String> opts = new ArrayList<String>(); 202 if (!modulePathFiles.isEmpty()) { 203 File modulepathDir = new File(tempDir, "modulepath"); 204 modulepathDir.mkdirs(); 205 clean(modulepathDir); 206 List<String> sOpts = Arrays.asList("-d", modulepathDir.getPath(), 207 "--module-source-path", new File(file, "modulepath").getAbsolutePath()); 208 new Jsr199Compiler(verbose).run(null, null, false, sOpts, modulePathFiles); 209 opts.add("--module-path"); 210 opts.add(modulepathDir.getAbsolutePath()); 211 } 212 213 if (!classPathFiles.isEmpty()) { 214 File classpathDir = new File(tempDir, "classpath"); 215 classpathDir.mkdirs(); 216 clean(classpathDir); 217 List<String> sOpts = Arrays.asList("-d", classpathDir.getPath()); 218 new Jsr199Compiler(verbose).run(null, null, false, sOpts, classPathFiles); 219 opts.add("--class-path"); 220 opts.add(classpathDir.getAbsolutePath()); 221 } 222 223 File classesDir = new File(tempDir, "classes"); 224 classesDir.mkdirs(); 225 clean(classesDir); 226 227 opts.add("-d"); 228 opts.add(classesDir.getPath()); 229 if (options != null) 230 opts.addAll(options); 231 232 if (procFiles.size() > 0) { 233 List<String> pOpts = new ArrayList<>(Arrays.asList("-d", classesDir.getPath())); 234 235 // hack to automatically add exports; a better solution would be to grep the 236 // source for import statements or a magic comment 237 for (File pf: procFiles) { 238 if (pf.getName().equals("CreateBadClassFile.java")) { 239 pOpts.add("--add-exports=jdk.jdeps/com.sun.tools.classfile=ALL-UNNAMED"); 240 } 241 } 242 243 new Jsr199Compiler(verbose).run(null, null, false, pOpts, procFiles); 244 opts.add("-classpath"); // avoid using -processorpath for now 245 opts.add(classesDir.getPath()); 246 createAnnotationServicesFile(classesDir, procFiles); 247 } else if (options != null) { 248 int i = options.indexOf("-processor"); 249 // check for built-in anno-processor(s) 250 if (i != -1 && options.get(i + 1).equals("DocCommentProcessor")) { 251 opts.add("-classpath"); 252 opts.add(System.getProperty("test.classes")); 253 } 254 } 255 256 List<File> files = srcFiles; 257 258 if (srcPathDir != null) { 259 opts.add("-sourcepath"); 260 opts.add(srcPathDir.getPath()); 261 } 262 263 if (moduleSourcePathDir != null) { 264 opts.add("--module-source-path"); 265 opts.add(moduleSourcePathDir.getPath()); 266 files = moduleSourcePathFiles; 267 } 268 269 if (additionalFiles.size() > 0) { 270 List<String> sOpts = Arrays.asList("-d", classesDir.getPath()); 271 new Jsr199Compiler(verbose).run(null, null, false, sOpts, additionalFiles); 272 } 273 274 try { 275 Compiler c = Compiler.getCompiler(runOpts, verbose); 276 c.run(out, keys, raw, opts, files); 277 } catch (IllegalArgumentException e) { 278 if (out != null) { 279 out.println("Invalid value for run tag: " + runOpts); 280 } 281 } 282 } 283 284 void createAnnotationServicesFile(File dir, List<File> procFiles) throws IOException { 285 File servicesDir = new File(new File(dir, "META-INF"), "services"); 286 servicesDir.mkdirs(); 287 File annoServices = new File(servicesDir, Processor.class.getName()); 288 Writer out = new FileWriter(annoServices); 289 try { 290 for (File f: procFiles) { 291 out.write(f.getName().toString().replace(".java", "")); 292 } 293 } finally { 294 out.close(); 295 } 296 } 297 298 @Override 299 public int compareTo(Example e) { 300 return file.compareTo(e.file); 301 } 302 303 @Override 304 public String toString() { 305 return file.getPath(); 306 } 307 308 /** 309 * Read the contents of a file. 310 */ 311 private String read(File f) throws IOException { 312 byte[] bytes = new byte[(int) f.length()]; 313 DataInputStream in = new DataInputStream(new FileInputStream(f)); 314 try { 315 in.readFully(bytes); 316 } finally { 317 in.close(); 318 } 319 return new String(bytes); 320 } 321 322 /** 323 * Clean the contents of a directory. 324 */ 325 boolean clean(File dir) { 326 boolean ok = true; 327 for (File f: dir.listFiles()) { 328 if (f.isDirectory()) 329 ok &= clean(f); 330 ok &= f.delete(); 331 } 332 return ok; 333 } 334 335 File file; 336 List<File> srcFiles; 337 List<File> procFiles; 338 File srcPathDir; 339 File moduleSourcePathDir; 340 File additionalFilesDir; 341 List<File> srcPathFiles; 342 List<File> moduleSourcePathFiles; 343 List<File> modulePathFiles; 344 List<File> classPathFiles; 345 List<File> additionalFiles; 346 File infoFile; 347 private List<String> runOpts; 348 private List<String> options; 349 private Set<String> actualKeys; 350 private Set<String> declaredKeys; 351 352 static File tempDir = (System.getProperty("test.src") != null) ? 353 new File(System.getProperty("user.dir")): 354 new File(System.getProperty("java.io.tmpdir")); 355 356 static void setTempDir(File tempDir) { 357 Example.tempDir = tempDir; 358 } 359 360 abstract static class Compiler { 361 interface Factory { 362 Compiler getCompiler(List<String> opts, boolean verbose); 363 } 364 365 static class DefaultFactory implements Factory { 366 public Compiler getCompiler(List<String> opts, boolean verbose) { 367 String first; 368 String[] rest; 369 if (opts == null || opts.isEmpty()) { 370 first = null; 371 rest = new String[0]; 372 } else { 373 first = opts.get(0); 374 rest = opts.subList(1, opts.size()).toArray(new String[opts.size() - 1]); 375 } 376 // For more details on the different compilers, 377 // see their respective class doc comments. 378 // See also README.examples.txt in this directory. 379 if (first == null || first.equals("jsr199")) 380 return new Jsr199Compiler(verbose, rest); 381 else if (first.equals("simple")) 382 return new SimpleCompiler(verbose); 383 else if (first.equals("backdoor")) 384 return new BackdoorCompiler(verbose); 385 else if (first.equals("exec")) 386 return new ExecCompiler(verbose, rest); 387 else 388 throw new IllegalArgumentException(first); 389 } 390 } 391 392 static Factory factory; 393 394 static Compiler getCompiler(List<String> opts, boolean verbose) { 395 if (factory == null) 396 factory = new DefaultFactory(); 397 398 return factory.getCompiler(opts, verbose); 399 } 400 401 protected Compiler(boolean verbose) { 402 this.verbose = verbose; 403 } 404 405 abstract boolean run(PrintWriter out, Set<String> keys, boolean raw, 406 List<String> opts, List<File> files); 407 408 void setSupportClassLoader(ClassLoader cl) { 409 loader = cl; 410 } 411 412 protected void close(JavaFileManager fm) { 413 try { 414 fm.close(); 415 } catch (IOException e) { 416 throw new Error(e); 417 } 418 } 419 420 protected ClassLoader loader; 421 protected boolean verbose; 422 } 423 424 /** 425 * Compile using the JSR 199 API. The diagnostics generated are 426 * scanned for resource keys. Not all diagnostic keys are generated 427 * via the JSR 199 API -- for example, rich diagnostics are not directly 428 * accessible, and some diagnostics generated by the file manager may 429 * not be generated (for example, the JSR 199 file manager does not see 430 * -Xlint:path). 431 */ 432 static class Jsr199Compiler extends Compiler { 433 List<String> fmOpts; 434 435 Jsr199Compiler(boolean verbose, String... args) { 436 super(verbose); 437 for (int i = 0; i < args.length; i++) { 438 String arg = args[i]; 439 if (arg.equals("-filemanager") && (i + 1 < args.length)) { 440 fmOpts = Arrays.asList(args[++i].split(",")); 441 } else 442 throw new IllegalArgumentException(arg); 443 } 444 } 445 446 @Override 447 boolean run(PrintWriter out, Set<String> keys, boolean raw, List<String> opts, List<File> files) { 448 if (out != null && keys != null) 449 throw new IllegalArgumentException(); 450 451 if (verbose) 452 System.err.println("run_jsr199: " + opts + " " + files); 453 454 DiagnosticCollector<JavaFileObject> dc = null; 455 if (keys != null) 456 dc = new DiagnosticCollector<JavaFileObject>(); 457 458 if (raw) { 459 List<String> newOpts = new ArrayList<String>(); 460 newOpts.add("-XDrawDiagnostics"); 461 newOpts.addAll(opts); 462 opts = newOpts; 463 } 464 465 JavaCompiler c = ToolProvider.getSystemJavaCompiler(); 466 467 StandardJavaFileManager fm = c.getStandardFileManager(dc, null, null); 468 try { 469 if (fmOpts != null) 470 fm = new FileManager(fm, fmOpts); 471 472 Iterable<? extends JavaFileObject> fos = fm.getJavaFileObjectsFromFiles(files); 473 474 CompilationTask t = c.getTask(out, fm, dc, opts, null, fos); 475 Boolean ok = t.call(); 476 477 if (keys != null) { 478 for (Diagnostic<? extends JavaFileObject> d: dc.getDiagnostics()) { 479 scanForKeys(unwrap(d), keys); 480 } 481 } 482 483 return ok; 484 } finally { 485 close(fm); 486 } 487 } 488 489 /** 490 * Scan a diagnostic for resource keys. This will not detect additional 491 * sub diagnostics that might be generated by a rich diagnostic formatter. 492 */ 493 private static void scanForKeys(JCDiagnostic d, Set<String> keys) { 494 keys.add(d.getCode()); 495 for (Object o: d.getArgs()) { 496 if (o instanceof JCDiagnostic) { 497 scanForKeys((JCDiagnostic) o, keys); 498 } 499 } 500 for (JCDiagnostic sd: d.getSubdiagnostics()) 501 scanForKeys(sd, keys); 502 } 503 504 private JCDiagnostic unwrap(Diagnostic<? extends JavaFileObject> diagnostic) { 505 if (diagnostic instanceof JCDiagnostic) 506 return (JCDiagnostic) diagnostic; 507 if (diagnostic instanceof ClientCodeWrapper.DiagnosticSourceUnwrapper) 508 return ((ClientCodeWrapper.DiagnosticSourceUnwrapper)diagnostic).d; 509 throw new IllegalArgumentException(); 510 } 511 } 512 513 /** 514 * Run the test using the standard simple entry point. 515 */ 516 static class SimpleCompiler extends Compiler { 517 SimpleCompiler(boolean verbose) { 518 super(verbose); 519 } 520 521 @Override 522 boolean run(PrintWriter out, Set<String> keys, boolean raw, List<String> opts, List<File> files) { 523 if (out != null && keys != null) 524 throw new IllegalArgumentException(); 525 526 if (verbose) 527 System.err.println("run_simple: " + opts + " " + files); 528 529 List<String> args = new ArrayList<String>(); 530 531 if (keys != null || raw) 532 args.add("-XDrawDiagnostics"); 533 534 args.addAll(opts); 535 for (File f: files) 536 args.add(f.getPath()); 537 538 StringWriter sw = null; 539 PrintWriter pw; 540 if (keys != null) { 541 sw = new StringWriter(); 542 pw = new PrintWriter(sw); 543 } else 544 pw = out; 545 546 int rc = com.sun.tools.javac.Main.compile(args.toArray(new String[args.size()]), pw); 547 548 if (keys != null) { 549 pw.close(); 550 scanForKeys(sw.toString(), keys); 551 } 552 553 return (rc == 0); 554 } 555 556 private static void scanForKeys(String text, Set<String> keys) { 557 StringTokenizer st = new StringTokenizer(text, " ,\r\n():"); 558 while (st.hasMoreElements()) { 559 String t = st.nextToken(); 560 if (t.startsWith("compiler.")) 561 keys.add(t); 562 } 563 } 564 } 565 566 /** 567 * Run the test in a separate process. 568 */ 569 static class ExecCompiler extends Compiler { 570 List<String> vmOpts; 571 572 ExecCompiler(boolean verbose, String... args) { 573 super(verbose); 574 vmOpts = Arrays.asList(args); 575 } 576 577 @Override 578 boolean run(PrintWriter out, Set<String> keys, boolean raw, List<String> opts, List<File> files) { 579 if (out != null && keys != null) 580 throw new IllegalArgumentException(); 581 582 if (verbose) 583 System.err.println("run_exec: " + vmOpts + " " + opts + " " + files); 584 585 List<String> args = new ArrayList<String>(); 586 587 File javaHome = new File(System.getProperty("java.home")); 588 if (javaHome.getName().equals("jre")) 589 javaHome = javaHome.getParentFile(); 590 File javaExe = new File(new File(javaHome, "bin"), "java"); 591 args.add(javaExe.getPath()); 592 593 File toolsJar = new File(new File(javaHome, "lib"), "tools.jar"); 594 if (toolsJar.exists()) { 595 args.add("-classpath"); 596 args.add(toolsJar.getPath()); 597 } 598 599 args.addAll(vmOpts); 600 addOpts(args, "test.vm.opts"); 601 addOpts(args, "test.java.opts"); 602 args.add(com.sun.tools.javac.Main.class.getName()); 603 604 if (keys != null || raw) 605 args.add("-XDrawDiagnostics"); 606 607 args.addAll(opts); 608 for (File f: files) 609 args.add(f.getPath()); 610 611 try { 612 ProcessBuilder pb = new ProcessBuilder(args); 613 pb.redirectErrorStream(true); 614 Process p = pb.start(); 615 BufferedReader in = new BufferedReader(new InputStreamReader(p.getInputStream())); 616 String line; 617 while ((line = in.readLine()) != null) { 618 if (keys != null) 619 scanForKeys(line, keys); 620 } 621 int rc = p.waitFor(); 622 623 return (rc == 0); 624 } catch (IOException | InterruptedException e) { 625 System.err.println("Exception execing javac" + e); 626 System.err.println("Command line: " + opts); 627 return false; 628 } 629 } 630 631 private static void scanForKeys(String text, Set<String> keys) { 632 StringTokenizer st = new StringTokenizer(text, " ,\r\n():"); 633 while (st.hasMoreElements()) { 634 String t = st.nextToken(); 635 if (t.startsWith("compiler.")) 636 keys.add(t); 637 } 638 } 639 640 private static void addOpts(List<String> args, String propName) { 641 String propValue = System.getProperty(propName); 642 if (propValue == null || propValue.isEmpty()) 643 return; 644 args.addAll(Arrays.asList(propValue.split(" +", 0))); 645 } 646 } 647 648 static class BackdoorCompiler extends Compiler { 649 BackdoorCompiler(boolean verbose) { 650 super(verbose); 651 } 652 653 @Override 654 boolean run(PrintWriter out, Set<String> keys, boolean raw, List<String> opts, List<File> files) { 655 if (out != null && keys != null) 656 throw new IllegalArgumentException(); 657 658 if (verbose) 659 System.err.println("run_simple: " + opts + " " + files); 660 661 List<String> args = new ArrayList<String>(); 662 663 if (out != null && raw) 664 args.add("-XDrawDiagnostics"); 665 666 args.addAll(opts); 667 for (File f: files) 668 args.add(f.getPath()); 669 670 StringWriter sw = null; 671 PrintWriter pw; 672 if (keys != null) { 673 sw = new StringWriter(); 674 pw = new PrintWriter(sw); 675 } else 676 pw = out; 677 678 Context c = new Context(); 679 JavacFileManager.preRegister(c); // can't create it until Log has been set up 680 MessageTracker.preRegister(c, keys); 681 682 try { 683 Main m = new Main("javac", pw); 684 Main.Result rc = m.compile(args.toArray(new String[args.size()]), c); 685 686 if (keys != null) { 687 pw.close(); 688 } 689 690 return rc.isOK(); 691 } finally { 692 close(c.get(JavaFileManager.class)); 693 } 694 } 695 696 static class MessageTracker extends JavacMessages { 697 698 MessageTracker(Context context) { 699 super(context); 700 } 701 702 static void preRegister(Context c, final Set<String> keys) { 703 if (keys != null) { 704 c.put(JavacMessages.messagesKey, new Context.Factory<JavacMessages>() { 705 public JavacMessages make(Context c) { 706 return new MessageTracker(c) { 707 @Override 708 public String getLocalizedString(Locale l, String key, Object... args) { 709 keys.add(key); 710 return super.getLocalizedString(l, key, args); 711 } 712 }; 713 } 714 }); 715 } 716 } 717 } 718 719 } 720} 721