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