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