Example.java revision 3976:8be741555fa6
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.util.*;
28import java.util.regex.*;
29import javax.annotation.processing.Processor;
30import javax.tools.Diagnostic;
31import javax.tools.DiagnosticCollector;
32import javax.tools.JavaCompiler;
33import javax.tools.JavaCompiler.CompilationTask;
34import javax.tools.JavaFileManager;
35import javax.tools.JavaFileObject;
36import javax.tools.StandardJavaFileManager;
37import 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
43import com.sun.tools.javac.api.ClientCodeWrapper;
44import com.sun.tools.javac.file.JavacFileManager;
45import com.sun.tools.javac.main.Main;
46import com.sun.tools.javac.util.Context;
47import com.sun.tools.javac.util.JavacMessages;
48import com.sun.tools.javac.util.JCDiagnostic;
49
50/**
51 * Class to handle example code designed to illustrate javac diagnostic messages.
52 */
53class 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}
744