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