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