jjg@610: /* jjg@610: * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. jjg@610: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. jjg@610: * jjg@610: * This code is free software; you can redistribute it and/or modify it jjg@610: * under the terms of the GNU General Public License version 2 only, as jjg@610: * published by the Free Software Foundation. jjg@610: * jjg@610: * This code is distributed in the hope that it will be useful, but WITHOUT jjg@610: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or jjg@610: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License jjg@610: * version 2 for more details (a copy is included in the LICENSE file that jjg@610: * accompanied this code). jjg@610: * jjg@610: * You should have received a copy of the GNU General Public License version jjg@610: * 2 along with this work; if not, write to the Free Software Foundation, jjg@610: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. jjg@610: * jjg@610: * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA jjg@610: * or visit www.oracle.com if you need additional information or have any jjg@610: * questions. jjg@610: */ jjg@610: jjg@610: import com.sun.tools.javac.file.JavacFileManager; jjg@610: import java.io.*; jjg@610: import java.util.*; jjg@610: import java.util.regex.*; jjg@610: import javax.tools.Diagnostic; jjg@610: import javax.tools.DiagnosticCollector; jjg@610: import javax.tools.JavaCompiler; jjg@610: import javax.tools.JavaCompiler.CompilationTask; jjg@610: import javax.tools.JavaFileObject; jjg@610: import javax.tools.StandardJavaFileManager; jjg@610: import javax.tools.ToolProvider; jjg@610: jjg@610: // The following two classes are both used, but cannot be imported directly jjg@610: // import com.sun.tools.javac.Main jjg@610: // import com.sun.tools.javac.main.Main jjg@610: jjg@610: import com.sun.tools.javac.util.Context; jjg@610: import com.sun.tools.javac.util.JavacMessages; jjg@610: import com.sun.tools.javac.util.JCDiagnostic; jjg@610: import java.net.URL; jjg@610: import java.net.URLClassLoader; jjg@610: import javax.annotation.processing.Processor; jjg@610: jjg@610: /** jjg@610: * Class to handle example code designed to illustrate javac diagnostic messages. jjg@610: */ jjg@610: class Example implements Comparable { jjg@610: /* Create an Example from the files found at path. jjg@610: * The head of the file, up to the first Java code, is scanned jjg@610: * for information about the test, such as what resource keys it jjg@610: * generates when run, what options are required to run it, and so on. jjg@610: */ jjg@610: Example(File file) { jjg@610: this.file = file; jjg@610: declaredKeys = new TreeSet(); jjg@610: srcFiles = new ArrayList(); jjg@610: procFiles = new ArrayList(); jjg@610: supportFiles = new ArrayList(); jjg@610: srcPathFiles = new ArrayList(); jjg@610: jjg@610: findFiles(file, srcFiles); jjg@610: for (File f: srcFiles) { jjg@610: parse(f); jjg@610: } jjg@610: jjg@610: if (infoFile == null) jjg@610: throw new Error("Example " + file + " has no info file"); jjg@610: } jjg@610: jjg@610: private void findFiles(File f, List files) { jjg@610: if (f.isDirectory()) { jjg@610: for (File c: f.listFiles()) { jjg@610: if (files == srcFiles && c.getName().equals("processors")) jjg@610: findFiles(c, procFiles); jjg@610: else if (files == srcFiles && c.getName().equals("sourcepath")) { jjg@610: srcPathDir = c; jjg@610: findFiles(c, srcPathFiles); jjg@610: } else if (files == srcFiles && c.getName().equals("support")) jjg@610: findFiles(c, supportFiles); jjg@610: else jjg@610: findFiles(c, files); jjg@610: } jjg@610: } else if (f.isFile() && f.getName().endsWith(".java")) { jjg@610: files.add(f); jjg@610: } jjg@610: } jjg@610: jjg@610: private void parse(File f) { jjg@610: Pattern keyPat = Pattern.compile(" *// *key: *([^ ]+) *"); jjg@610: Pattern optPat = Pattern.compile(" *// *options: *(.*)"); jjg@610: Pattern runPat = Pattern.compile(" *// *run: *(.*)"); jjg@610: Pattern javaPat = Pattern.compile(" *@?[A-Za-z].*"); jjg@610: try { jjg@610: String[] lines = read(f).split("[\r\n]+"); jjg@610: for (String line: lines) { jjg@610: Matcher keyMatch = keyPat.matcher(line); jjg@610: if (keyMatch.matches()) { jjg@610: foundInfo(f); jjg@610: declaredKeys.add(keyMatch.group(1)); jjg@610: continue; jjg@610: } jjg@610: Matcher optMatch = optPat.matcher(line); jjg@610: if (optMatch.matches()) { jjg@610: foundInfo(f); jjg@610: options = Arrays.asList(optMatch.group(1).trim().split(" +")); jjg@610: continue; jjg@610: } jjg@610: Matcher runMatch = runPat.matcher(line); jjg@610: if (runMatch.matches()) { jjg@610: foundInfo(f); jjg@610: runOpts = Arrays.asList(runMatch.group(1).trim().split(" +")); jjg@610: } jjg@610: if (javaPat.matcher(line).matches()) jjg@610: break; jjg@610: } jjg@610: } catch (IOException e) { jjg@610: throw new Error(e); jjg@610: } jjg@610: } jjg@610: jjg@610: private void foundInfo(File file) { jjg@610: if (infoFile != null && !infoFile.equals(file)) jjg@610: throw new Error("multiple info files found: " + infoFile + ", " + file); jjg@610: infoFile = file; jjg@610: } jjg@610: jjg@610: String getName() { jjg@610: return file.getName(); jjg@610: } jjg@610: jjg@610: /** jjg@610: * Get the set of resource keys that this test declares it will generate jjg@610: * when it is run. jjg@610: */ jjg@610: Set getDeclaredKeys() { jjg@610: return declaredKeys; jjg@610: } jjg@610: jjg@610: /** jjg@610: * Get the set of resource keys that this test generates when it is run. jjg@610: * The test will be run if it has not already been run. jjg@610: */ jjg@610: Set getActualKeys() { jjg@610: if (actualKeys == null) jjg@610: actualKeys = run(false); jjg@610: return actualKeys; jjg@610: } jjg@610: jjg@610: /** jjg@610: * Run the test. Information in the test header is used to determine jjg@610: * how to run the test. jjg@610: */ jjg@610: void run(PrintWriter out, boolean raw, boolean verbose) { jjg@610: if (out == null) jjg@610: throw new NullPointerException(); jjg@610: try { jjg@610: run(out, null, raw, verbose); jjg@610: } catch (IOException e) { jjg@610: e.printStackTrace(out); jjg@610: } jjg@610: } jjg@610: jjg@610: Set run(boolean verbose) { jjg@610: Set keys = new TreeSet(); jjg@610: try { jjg@610: run(null, keys, true, verbose); jjg@610: } catch (IOException e) { jjg@610: e.printStackTrace(); jjg@610: } jjg@610: return keys; jjg@610: } jjg@610: jjg@610: /** jjg@610: * Run the test. Information in the test header is used to determine jjg@610: * how to run the test. jjg@610: */ jjg@610: private void run(PrintWriter out, Set keys, boolean raw, boolean verbose) jjg@610: throws IOException { jjg@610: ClassLoader loader = getClass().getClassLoader(); jjg@610: if (supportFiles.size() > 0) { jjg@610: File supportDir = new File(tempDir, "support"); jjg@610: supportDir.mkdirs(); jjg@610: clean(supportDir); jjg@610: List sOpts = Arrays.asList("-d", supportDir.getPath()); jjg@610: new Jsr199Compiler(verbose).run(null, null, false, sOpts, procFiles); jjg@610: URLClassLoader ucl = jjg@610: new URLClassLoader(new URL[] { supportDir.toURI().toURL() }, loader); jjg@610: loader = ucl; jjg@610: } jjg@610: jjg@610: File classesDir = new File(tempDir, "classes"); jjg@610: classesDir.mkdirs(); jjg@610: clean(classesDir); jjg@610: jjg@610: List opts = new ArrayList(); jjg@610: opts.add("-d"); jjg@610: opts.add(classesDir.getPath()); jjg@610: if (options != null) jjg@610: opts.addAll(options); jjg@610: jjg@610: if (procFiles.size() > 0) { jjg@610: List pOpts = Arrays.asList("-d", classesDir.getPath()); jjg@610: new Jsr199Compiler(verbose).run(null, null, false, pOpts, procFiles); jjg@610: opts.add("-classpath"); // avoid using -processorpath for now jjg@610: opts.add(classesDir.getPath()); jjg@610: createAnnotationServicesFile(classesDir, procFiles); jjg@610: } jjg@610: jjg@610: if (srcPathDir != null) { jjg@610: opts.add("-sourcepath"); jjg@610: opts.add(srcPathDir.getPath()); jjg@610: } jjg@610: jjg@610: try { jjg@610: Compiler c = Compiler.getCompiler(runOpts, verbose); jjg@610: c.run(out, keys, raw, opts, srcFiles); jjg@610: } catch (IllegalArgumentException e) { jjg@610: if (out != null) { jjg@610: out.println("Invalid value for run tag: " + runOpts); jjg@610: } jjg@610: } jjg@610: } jjg@610: jjg@610: void createAnnotationServicesFile(File dir, List procFiles) throws IOException { jjg@610: File servicesDir = new File(new File(dir, "META-INF"), "services"); jjg@610: servicesDir.mkdirs(); jjg@610: File annoServices = new File(servicesDir, Processor.class.getName()); jjg@610: Writer out = new FileWriter(annoServices); jjg@610: try { jjg@610: for (File f: procFiles) { jjg@610: out.write(f.getName().toString().replace(".java", "")); jjg@610: } jjg@610: } finally { jjg@610: out.close(); jjg@610: } jjg@610: } jjg@610: jjg@610: @Override jjg@610: public int compareTo(Example e) { jjg@610: return file.compareTo(e.file); jjg@610: } jjg@610: jjg@610: @Override jjg@610: public String toString() { jjg@610: return file.getPath(); jjg@610: } jjg@610: jjg@610: /** jjg@610: * Read the contents of a file. jjg@610: */ jjg@610: private String read(File f) throws IOException { jjg@610: byte[] bytes = new byte[(int) f.length()]; jjg@610: DataInputStream in = new DataInputStream(new FileInputStream(f)); jjg@610: try { jjg@610: in.readFully(bytes); jjg@610: } finally { jjg@610: in.close(); jjg@610: } jjg@610: return new String(bytes); jjg@610: } jjg@610: jjg@610: /** jjg@610: * Clean the contents of a directory. jjg@610: */ jjg@610: boolean clean(File dir) { jjg@610: boolean ok = true; jjg@610: for (File f: dir.listFiles()) { jjg@610: if (f.isDirectory()) jjg@610: ok &= clean(f); jjg@610: ok &= f.delete(); jjg@610: } jjg@610: return ok; jjg@610: } jjg@610: jjg@610: File file; jjg@610: List srcFiles; jjg@610: List procFiles; jjg@610: File srcPathDir; jjg@610: List srcPathFiles; jjg@610: List supportFiles; jjg@610: File infoFile; jjg@610: private List runOpts; jjg@610: private List options; jjg@610: private Set actualKeys; jjg@610: private Set declaredKeys; jjg@610: jjg@610: static File tempDir = new File(System.getProperty("java.io.tmpdir")); jjg@610: static void setTempDir(File tempDir) { jjg@610: Example.tempDir = tempDir; jjg@610: } jjg@610: jjg@610: abstract static class Compiler { jjg@610: static Compiler getCompiler(List opts, boolean verbose) { jjg@610: String first; jjg@610: String[] rest; jjg@610: if (opts == null || opts.size() == 0) { jjg@610: first = null; jjg@610: rest = new String[0]; jjg@610: } else { jjg@610: first = opts.get(0); jjg@610: rest = opts.subList(1, opts.size()).toArray(new String[opts.size() - 1]); jjg@610: } jjg@610: if (first == null || first.equals("jsr199")) jjg@610: return new Jsr199Compiler(verbose, rest); jjg@610: else if (first.equals("simple")) jjg@610: return new SimpleCompiler(verbose); jjg@610: else if (first.equals("backdoor")) jjg@610: return new BackdoorCompiler(verbose); jjg@610: else jjg@610: throw new IllegalArgumentException(first); jjg@610: } jjg@610: jjg@610: protected Compiler(boolean verbose) { jjg@610: this.verbose = verbose; jjg@610: } jjg@610: jjg@610: abstract boolean run(PrintWriter out, Set keys, boolean raw, jjg@610: List opts, List files); jjg@610: jjg@610: void setSupportClassLoader(ClassLoader cl) { jjg@610: loader = cl; jjg@610: } jjg@610: jjg@610: protected ClassLoader loader; jjg@610: protected boolean verbose; jjg@610: } jjg@610: jjg@610: /** jjg@610: * Compile using the JSR 199 API. The diagnostics generated are jjg@610: * scanned for resource keys. Not all diagnostic keys are generated jjg@610: * via the JSR 199 API -- for example, rich diagnostics are not directly jjg@610: * accessible, and some diagnostics generated by the file manager may jjg@610: * not be generated (for example, the JSR 199 file manager does not see jjg@610: * -Xlint:path). jjg@610: */ jjg@610: static class Jsr199Compiler extends Compiler { jjg@610: List fmOpts; jjg@610: jjg@610: Jsr199Compiler(boolean verbose, String... args) { jjg@610: super(verbose); jjg@610: for (int i = 0; i < args.length; i++) { jjg@610: String arg = args[i]; jjg@610: if (arg.equals("-filemanager") && (i + 1 < args.length)) { jjg@610: fmOpts = Arrays.asList(args[++i].split(",")); jjg@610: } else jjg@610: throw new IllegalArgumentException(arg); jjg@610: } jjg@610: } jjg@610: jjg@610: @Override jjg@610: boolean run(PrintWriter out, Set keys, boolean raw, List opts, List files) { jjg@610: if (out != null && keys != null) jjg@610: throw new IllegalArgumentException(); jjg@610: jjg@610: if (verbose) jjg@610: System.err.println("run_jsr199: " + opts + " " + files); jjg@610: jjg@610: DiagnosticCollector dc = null; jjg@610: if (keys != null) jjg@610: dc = new DiagnosticCollector(); jjg@610: jjg@610: if (raw) { jjg@610: List newOpts = new ArrayList(); jjg@610: newOpts.add("-XDrawDiagnostics"); jjg@610: newOpts.addAll(opts); jjg@610: opts = newOpts; jjg@610: } jjg@610: jjg@610: JavaCompiler c = ToolProvider.getSystemJavaCompiler(); jjg@610: jjg@610: StandardJavaFileManager fm = c.getStandardFileManager(dc, null, null); jjg@610: if (fmOpts != null) jjg@610: fm = new FileManager(fm, fmOpts); jjg@610: jjg@610: Iterable fos = fm.getJavaFileObjectsFromFiles(files); jjg@610: jjg@610: CompilationTask t = c.getTask(out, fm, dc, opts, null, fos); jjg@610: Boolean ok = t.call(); jjg@610: jjg@610: if (keys != null) { jjg@610: for (Diagnostic d: dc.getDiagnostics()) { jjg@610: scanForKeys((JCDiagnostic) d, keys); jjg@610: } jjg@610: } jjg@610: jjg@610: return ok; jjg@610: } jjg@610: jjg@610: /** jjg@610: * Scan a diagnostic for resource keys. This will not detect additional jjg@610: * sub diagnostics that might be generated by a rich diagnostic formatter. jjg@610: */ jjg@610: private static void scanForKeys(JCDiagnostic d, Set keys) { jjg@610: keys.add(d.getCode()); jjg@610: for (Object o: d.getArgs()) { jjg@610: if (o instanceof JCDiagnostic) { jjg@610: scanForKeys((JCDiagnostic) o, keys); jjg@610: } jjg@610: } jjg@610: for (JCDiagnostic sd: d.getSubdiagnostics()) jjg@610: scanForKeys(d, keys); jjg@610: } jjg@610: } jjg@610: jjg@610: /** jjg@610: * Run the test using the standard simple entry point. jjg@610: */ jjg@610: static class SimpleCompiler extends Compiler { jjg@610: SimpleCompiler(boolean verbose) { jjg@610: super(verbose); jjg@610: } jjg@610: jjg@610: @Override jjg@610: boolean run(PrintWriter out, Set keys, boolean raw, List opts, List files) { jjg@610: if (out != null && keys != null) jjg@610: throw new IllegalArgumentException(); jjg@610: jjg@610: if (verbose) jjg@610: System.err.println("run_simple: " + opts + " " + files); jjg@610: jjg@636: List args = new ArrayList(); jjg@610: jjg@610: if (keys != null || raw) jjg@610: args.add("-XDrawDiagnostics"); jjg@610: jjg@610: args.addAll(opts); jjg@610: for (File f: files) jjg@610: args.add(f.getPath()); jjg@610: jjg@610: StringWriter sw = null; jjg@610: PrintWriter pw; jjg@610: if (keys != null) { jjg@610: sw = new StringWriter(); jjg@610: pw = new PrintWriter(sw); jjg@610: } else jjg@610: pw = out; jjg@610: jjg@610: int rc = com.sun.tools.javac.Main.compile(args.toArray(new String[args.size()]), pw); jjg@610: jjg@610: if (keys != null) { jjg@610: pw.close(); jjg@610: scanForKeys(sw.toString(), keys); jjg@610: } jjg@610: jjg@610: return (rc == 0); jjg@610: } jjg@610: jjg@610: private static void scanForKeys(String text, Set keys) { jjg@610: StringTokenizer st = new StringTokenizer(text, " ,\r\n():"); jjg@610: while (st.hasMoreElements()) { jjg@610: String t = st.nextToken(); jjg@610: if (t.startsWith("compiler.")) jjg@610: keys.add(t); jjg@610: } jjg@610: } jjg@610: } jjg@610: jjg@610: static class BackdoorCompiler extends Compiler { jjg@610: BackdoorCompiler(boolean verbose) { jjg@610: super(verbose); jjg@610: } jjg@610: jjg@610: @Override jjg@610: boolean run(PrintWriter out, Set keys, boolean raw, List opts, List files) { jjg@610: if (out != null && keys != null) jjg@610: throw new IllegalArgumentException(); jjg@610: jjg@610: if (verbose) jjg@610: System.err.println("run_simple: " + opts + " " + files); jjg@610: jjg@610: List args = new ArrayList(opts); jjg@610: jjg@610: if (out != null && raw) jjg@610: args.add("-XDrawDiagnostics"); jjg@610: jjg@610: args.addAll(opts); jjg@610: for (File f: files) jjg@610: args.add(f.getPath()); jjg@610: jjg@610: StringWriter sw = null; jjg@610: PrintWriter pw; jjg@610: if (keys != null) { jjg@610: sw = new StringWriter(); jjg@610: pw = new PrintWriter(sw); jjg@610: } else jjg@610: pw = out; jjg@610: jjg@610: Context c = new Context(); jjg@610: JavacFileManager.preRegister(c); // can't create it until Log has been set up jjg@610: MessageTracker.preRegister(c, keys); jjg@610: com.sun.tools.javac.main.Main m = new com.sun.tools.javac.main.Main("javac", pw); jjg@610: int rc = m.compile(args.toArray(new String[args.size()]), c); jjg@610: jjg@610: if (keys != null) { jjg@610: pw.close(); jjg@610: } jjg@610: jjg@610: return (rc == 0); jjg@610: } jjg@610: jjg@610: static class MessageTracker extends JavacMessages { mcimadamore@616: mcimadamore@616: MessageTracker(Context context) { mcimadamore@616: super(context); mcimadamore@616: } mcimadamore@616: mcimadamore@616: static void preRegister(final Context c, final Set keys) { jjg@610: if (keys != null) { jjg@610: c.put(JavacMessages.messagesKey, new Context.Factory() { jjg@610: public JavacMessages make() { mcimadamore@616: return new MessageTracker(c) { jjg@610: @Override jjg@610: public String getLocalizedString(Locale l, String key, Object... args) { jjg@610: keys.add(key); jjg@610: return super.getLocalizedString(l, key, args); jjg@610: } jjg@610: }; jjg@610: } jjg@610: }); jjg@610: } jjg@610: } jjg@610: } jjg@610: jjg@610: } jjg@610: }