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         srcPathFiles = new ArrayList<File>();
  65         moduleSourcePathFiles = new ArrayList<File>();
  66         modulePathFiles = new ArrayList<File>();
  67         classPathFiles = new ArrayList<File>();
  68         additionalFiles = new ArrayList<File>();
  69 
  70         findFiles(file, srcFiles);
  71         for (File f: srcFiles) {
  72             parse(f);
  73         }
  74 
  75         if (infoFile == null)
  76             throw new Error("Example " + file + " has no info file");
  77     }
  78 
  79     private void findFiles(File f, List<File> files) {
  80         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-modules=jdk.jdeps");
 240                     pOpts.add("--add-exports=jdk.jdeps/com.sun.tools.classfile=ALL-UNNAMED");
 241                 }
 242             }
 243 
 244             new Jsr199Compiler(verbose).run(null, null, false, pOpts, procFiles);
 245             opts.add("-classpath"); // avoid using -processorpath for now
 246             opts.add(classesDir.getPath());
 247             createAnnotationServicesFile(classesDir, procFiles);
 248         } else if (options != null) {
 249             int i = options.indexOf("-processor");
 250             // check for built-in anno-processor(s)
 251             if (i != -1 && options.get(i + 1).equals("DocCommentProcessor")) {
 252                 opts.add("-classpath");
 253                 opts.add(System.getProperty("test.classes"));
 254             }
 255         }
 256 
 257         List<File> files = srcFiles;
 258 
 259         if (srcPathDir != null) {
 260             opts.add("-sourcepath");
 261             opts.add(srcPathDir.getPath());
 262         }
 263 
 264         if (moduleSourcePathDir != null) {
 265             opts.add("--module-source-path");
 266             opts.add(moduleSourcePathDir.getPath());
 267             files = moduleSourcePathFiles;
 268         }
 269 
 270         if (additionalFiles.size() > 0) {
 271             List<String> sOpts = Arrays.asList("-d", classesDir.getPath());
 272             new Jsr199Compiler(verbose).run(null, null, false, sOpts, additionalFiles);
 273         }
 274 
 275         try {
 276             Compiler c = Compiler.getCompiler(runOpts, verbose);
 277             c.run(out, keys, raw, opts, files);
 278         } catch (IllegalArgumentException e) {
 279             if (out != null) {
 280                 out.println("Invalid value for run tag: " + runOpts);
 281             }
 282         }
 283     }
 284 
 285     void createAnnotationServicesFile(File dir, List<File> procFiles) throws IOException {
 286         File servicesDir = new File(new File(dir, "META-INF"), "services");
 287         servicesDir.mkdirs();
 288         File annoServices = new File(servicesDir, Processor.class.getName());
 289         Writer out = new FileWriter(annoServices);
 290         try {
 291             for (File f: procFiles) {
 292                 out.write(f.getName().toString().replace(".java", ""));
 293             }
 294         } finally {
 295             out.close();
 296         }
 297     }
 298 
 299     @Override
 300     public int compareTo(Example e) {
 301         return file.compareTo(e.file);
 302     }
 303 
 304     @Override
 305     public String toString() {
 306         return file.getPath();
 307     }
 308 
 309     /**
 310      * Read the contents of a file.
 311      */
 312     private String read(File f) throws IOException {
 313         byte[] bytes = new byte[(int) f.length()];
 314         DataInputStream in = new DataInputStream(new FileInputStream(f));
 315         try {
 316             in.readFully(bytes);
 317         } finally {
 318             in.close();
 319         }
 320         return new String(bytes);
 321     }
 322 
 323     /**
 324      * Clean the contents of a directory.
 325      */
 326     boolean clean(File dir) {
 327         boolean ok = true;
 328         for (File f: dir.listFiles()) {
 329             if (f.isDirectory())
 330                 ok &= clean(f);
 331             ok &= f.delete();
 332         }
 333         return ok;
 334     }
 335 
 336     File file;
 337     List<File> srcFiles;
 338     List<File> procFiles;
 339     File srcPathDir;
 340     File moduleSourcePathDir;
 341     File additionalFilesDir;
 342     List<File> srcPathFiles;
 343     List<File> moduleSourcePathFiles;
 344     List<File> modulePathFiles;
 345     List<File> classPathFiles;
 346     List<File> additionalFiles;
 347     File infoFile;
 348     private List<String> runOpts;
 349     private List<String> options;
 350     private Set<String> actualKeys;
 351     private Set<String> declaredKeys;
 352 
 353     static File tempDir = (System.getProperty("test.src") != null) ?
 354             new File(System.getProperty("user.dir")):
 355             new File(System.getProperty("java.io.tmpdir"));
 356 
 357     static void setTempDir(File tempDir) {
 358         Example.tempDir = tempDir;
 359     }
 360 
 361     abstract static class Compiler {
 362         interface Factory {
 363             Compiler getCompiler(List<String> opts, boolean verbose);
 364         }
 365 
 366         static class DefaultFactory implements Factory {
 367             public Compiler getCompiler(List<String> opts, boolean verbose) {
 368                 String first;
 369                 String[] rest;
 370                     if (opts == null || opts.isEmpty()) {
 371                     first = null;
 372                     rest = new String[0];
 373                 } else {
 374                     first = opts.get(0);
 375                     rest = opts.subList(1, opts.size()).toArray(new String[opts.size() - 1]);
 376                 }
 377                 // For more details on the different compilers,
 378                 // see their respective class doc comments.
 379                 // See also README.examples.txt in this directory.
 380                 if (first == null || first.equals("jsr199"))
 381                     return new Jsr199Compiler(verbose, rest);
 382                 else if (first.equals("simple"))
 383                     return new SimpleCompiler(verbose);
 384                 else if (first.equals("backdoor"))
 385                     return new BackdoorCompiler(verbose);
 386                 else if (first.equals("exec"))
 387                     return new ExecCompiler(verbose, rest);
 388                 else
 389                     throw new IllegalArgumentException(first);
 390             }
 391         }
 392 
 393         static Factory factory;
 394 
 395         static Compiler getCompiler(List<String> opts, boolean verbose) {
 396             if (factory == null)
 397                 factory = new DefaultFactory();
 398 
 399             return factory.getCompiler(opts, verbose);
 400         }
 401 
 402         protected Compiler(boolean verbose) {
 403             this.verbose = verbose;
 404         }
 405 
 406         abstract boolean run(PrintWriter out, Set<String> keys, boolean raw,
 407                 List<String> opts,  List<File> files);
 408 
 409         void setSupportClassLoader(ClassLoader cl) {
 410             loader = cl;
 411         }
 412 
 413         protected void close(JavaFileManager fm) {
 414             try {
 415                 fm.close();
 416             } catch (IOException e) {
 417                 throw new Error(e);
 418             }
 419         }
 420 
 421         protected ClassLoader loader;
 422         protected boolean verbose;
 423     }
 424 
 425     /**
 426      * Compile using the JSR 199 API.  The diagnostics generated are
 427      * scanned for resource keys.   Not all diagnostic keys are generated
 428      * via the JSR 199 API -- for example, rich diagnostics are not directly
 429      * accessible, and some diagnostics generated by the file manager may
 430      * not be generated (for example, the JSR 199 file manager does not see
 431      * -Xlint:path).
 432      */
 433     static class Jsr199Compiler extends Compiler {
 434         List<String> fmOpts;
 435 
 436         Jsr199Compiler(boolean verbose, String... args) {
 437             super(verbose);
 438             for (int i = 0; i < args.length; i++) {
 439                 String arg = args[i];
 440                 if (arg.equals("-filemanager") && (i + 1 < args.length)) {
 441                     fmOpts = Arrays.asList(args[++i].split(","));
 442                 } else
 443                     throw new IllegalArgumentException(arg);
 444             }
 445         }
 446 
 447         @Override
 448         boolean run(PrintWriter out, Set<String> keys, boolean raw, List<String> opts, List<File> files) {
 449             if (out != null && keys != null)
 450                 throw new IllegalArgumentException();
 451 
 452             if (verbose)
 453                 System.err.println("run_jsr199: " + opts + " " + files);
 454 
 455             DiagnosticCollector<JavaFileObject> dc = null;
 456             if (keys != null)
 457                 dc = new DiagnosticCollector<JavaFileObject>();
 458 
 459             if (raw) {
 460                 List<String> newOpts = new ArrayList<String>();
 461                 newOpts.add("-XDrawDiagnostics");
 462                 newOpts.addAll(opts);
 463                 opts = newOpts;
 464             }
 465 
 466             JavaCompiler c = ToolProvider.getSystemJavaCompiler();
 467 
 468             StandardJavaFileManager fm = c.getStandardFileManager(dc, null, null);
 469             try {
 470                 if (fmOpts != null)
 471                     fm = new FileManager(fm, fmOpts);
 472 
 473                 Iterable<? extends JavaFileObject> fos = fm.getJavaFileObjectsFromFiles(files);
 474 
 475                 CompilationTask t = c.getTask(out, fm, dc, opts, null, fos);
 476                 Boolean ok = t.call();
 477 
 478                 if (keys != null) {
 479                     for (Diagnostic<? extends JavaFileObject> d: dc.getDiagnostics()) {
 480                         scanForKeys(unwrap(d), keys);
 481                     }
 482                 }
 483 
 484                 return ok;
 485             } finally {
 486                 close(fm);
 487             }
 488         }
 489 
 490         /**
 491          * Scan a diagnostic for resource keys.  This will not detect additional
 492          * sub diagnostics that might be generated by a rich diagnostic formatter.
 493          */
 494         private static void scanForKeys(JCDiagnostic d, Set<String> keys) {
 495             keys.add(d.getCode());
 496             for (Object o: d.getArgs()) {
 497                 if (o instanceof JCDiagnostic) {
 498                     scanForKeys((JCDiagnostic) o, keys);
 499                 }
 500             }
 501             for (JCDiagnostic sd: d.getSubdiagnostics())
 502                 scanForKeys(sd, keys);
 503         }
 504 
 505         private JCDiagnostic unwrap(Diagnostic<? extends JavaFileObject> diagnostic) {
 506             if (diagnostic instanceof JCDiagnostic)
 507                 return (JCDiagnostic) diagnostic;
 508             if (diagnostic instanceof ClientCodeWrapper.DiagnosticSourceUnwrapper)
 509                 return ((ClientCodeWrapper.DiagnosticSourceUnwrapper)diagnostic).d;
 510             throw new IllegalArgumentException();
 511         }
 512     }
 513 
 514     /**
 515      * Run the test using the standard simple entry point.
 516      */
 517     static class SimpleCompiler extends Compiler {
 518         SimpleCompiler(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_simple: " + opts + " " + files);
 529 
 530             List<String> args = new ArrayList<String>();
 531 
 532             if (keys != null || raw)
 533                 args.add("-XDrawDiagnostics");
 534 
 535             args.addAll(opts);
 536             for (File f: files)
 537                 args.add(f.getPath());
 538 
 539             StringWriter sw = null;
 540             PrintWriter pw;
 541             if (keys != null) {
 542                 sw = new StringWriter();
 543                 pw = new PrintWriter(sw);
 544             } else
 545                 pw = out;
 546 
 547             int rc = com.sun.tools.javac.Main.compile(args.toArray(new String[args.size()]), pw);
 548 
 549             if (keys != null) {
 550                 pw.close();
 551                 scanForKeys(sw.toString(), keys);
 552             }
 553 
 554             return (rc == 0);
 555         }
 556 
 557         private static void scanForKeys(String text, Set<String> keys) {
 558             StringTokenizer st = new StringTokenizer(text, " ,\r\n():");
 559             while (st.hasMoreElements()) {
 560                 String t = st.nextToken();
 561                 if (t.startsWith("compiler."))
 562                     keys.add(t);
 563             }
 564         }
 565     }
 566 
 567     /**
 568      * Run the test in a separate process.
 569      */
 570     static class ExecCompiler extends Compiler {
 571         List<String> vmOpts;
 572 
 573         ExecCompiler(boolean verbose, String... args) {
 574             super(verbose);
 575             vmOpts = Arrays.asList(args);
 576         }
 577 
 578         @Override
 579         boolean run(PrintWriter out, Set<String> keys, boolean raw, List<String> opts, List<File> files) {
 580             if (out != null && keys != null)
 581                 throw new IllegalArgumentException();
 582 
 583             if (verbose)
 584                 System.err.println("run_exec: " + vmOpts + " " + opts + " " + files);
 585 
 586             List<String> args = new ArrayList<String>();
 587 
 588             File javaHome = new File(System.getProperty("java.home"));
 589             if (javaHome.getName().equals("jre"))
 590                 javaHome = javaHome.getParentFile();
 591             File javaExe = new File(new File(javaHome, "bin"), "java");
 592             args.add(javaExe.getPath());
 593 
 594             File toolsJar = new File(new File(javaHome, "lib"), "tools.jar");
 595             if (toolsJar.exists()) {
 596                 args.add("-classpath");
 597                 args.add(toolsJar.getPath());
 598             }
 599 
 600             args.addAll(vmOpts);
 601             addOpts(args, "test.vm.opts");
 602             addOpts(args, "test.java.opts");
 603             args.add(com.sun.tools.javac.Main.class.getName());
 604 
 605             if (keys != null || raw)
 606                 args.add("-XDrawDiagnostics");
 607 
 608             args.addAll(opts);
 609             for (File f: files)
 610                 args.add(f.getPath());
 611 
 612             try {
 613                 ProcessBuilder pb = new ProcessBuilder(args);
 614                 pb.redirectErrorStream(true);
 615                 Process p = pb.start();
 616                 BufferedReader in = new BufferedReader(new InputStreamReader(p.getInputStream()));
 617                 String line;
 618                 while ((line = in.readLine()) != null) {
 619                     if (keys != null)
 620                         scanForKeys(line, keys);
 621                 }
 622                 int rc = p.waitFor();
 623 
 624                 return (rc == 0);
 625             } catch (IOException | InterruptedException e) {
 626                 System.err.println("Exception execing javac" + e);
 627                 System.err.println("Command line: " + opts);
 628                 return false;
 629             }
 630         }
 631 
 632         private static void scanForKeys(String text, Set<String> keys) {
 633             StringTokenizer st = new StringTokenizer(text, " ,\r\n():");
 634             while (st.hasMoreElements()) {
 635                 String t = st.nextToken();
 636                 if (t.startsWith("compiler."))
 637                     keys.add(t);
 638             }
 639         }
 640 
 641         private static void addOpts(List<String> args, String propName) {
 642             String propValue = System.getProperty(propName);
 643             if (propValue == null || propValue.isEmpty())
 644                 return;
 645             args.addAll(Arrays.asList(propValue.split(" +", 0)));
 646         }
 647     }
 648 
 649     static class BackdoorCompiler extends Compiler {
 650         BackdoorCompiler(boolean verbose) {
 651             super(verbose);
 652         }
 653 
 654         @Override
 655         boolean run(PrintWriter out, Set<String> keys, boolean raw, List<String> opts, List<File> files) {
 656             if (out != null && keys != null)
 657                 throw new IllegalArgumentException();
 658 
 659             if (verbose)
 660                 System.err.println("run_simple: " + opts + " " + files);
 661 
 662             List<String> args = new ArrayList<String>();
 663 
 664             if (out != null && raw)
 665                 args.add("-XDrawDiagnostics");
 666 
 667             args.addAll(opts);
 668             for (File f: files)
 669                 args.add(f.getPath());
 670 
 671             StringWriter sw = null;
 672             PrintWriter pw;
 673             if (keys != null) {
 674                 sw = new StringWriter();
 675                 pw = new PrintWriter(sw);
 676             } else
 677                 pw = out;
 678 
 679             Context c = new Context();
 680             JavacFileManager.preRegister(c); // can't create it until Log has been set up
 681             MessageTracker.preRegister(c, keys);
 682 
 683             try {
 684                 Main m = new Main("javac", pw);
 685                 Main.Result rc = m.compile(args.toArray(new String[args.size()]), c);
 686 
 687                 if (keys != null) {
 688                     pw.close();
 689                 }
 690 
 691                 return rc.isOK();
 692             } finally {
 693                 close(c.get(JavaFileManager.class));
 694             }
 695         }
 696 
 697         static class MessageTracker extends JavacMessages {
 698 
 699             MessageTracker(Context context) {
 700                 super(context);
 701             }
 702 
 703             static void preRegister(Context c, final Set<String> keys) {
 704                 if (keys != null) {
 705                     c.put(JavacMessages.messagesKey, new Context.Factory<JavacMessages>() {
 706                         public JavacMessages make(Context c) {
 707                             return new MessageTracker(c) {
 708                                 @Override
 709                                 public String getLocalizedString(Locale l, String key, Object... args) {
 710                                     keys.add(key);
 711                                     return super.getLocalizedString(l, key, args);
 712                                 }
 713                             };
 714                         }
 715                     });
 716                 }
 717             }
 718         }
 719 
 720     }
 721 }