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