Wed, 31 Aug 2011 15:39:00 -0700
7074416: Regression: JSR199: javac doesn't unwrap clientcodewrapper objects
Reviewed-by: mcimadamore
1 /*
2 * Copyright (c) 2010, 2011, 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 */
24 import java.io.*;
25 import java.net.URL;
26 import java.net.URLClassLoader;
27 import java.util.*;
28 import java.util.regex.*;
29 import javax.annotation.processing.Processor;
30 import javax.tools.Diagnostic;
31 import javax.tools.DiagnosticCollector;
32 import javax.tools.JavaCompiler;
33 import javax.tools.JavaCompiler.CompilationTask;
34 import javax.tools.JavaFileObject;
35 import javax.tools.StandardJavaFileManager;
36 import javax.tools.ToolProvider;
38 // The following two classes are both used, but cannot be imported directly
39 // import com.sun.tools.javac.Main
40 // import com.sun.tools.javac.main.Main
42 import com.sun.tools.javac.api.ClientCodeWrapper;
43 import com.sun.tools.javac.file.JavacFileManager;
44 import com.sun.tools.javac.util.Context;
45 import com.sun.tools.javac.util.JavacMessages;
46 import com.sun.tools.javac.util.JCDiagnostic;
48 /**
49 * Class to handle example code designed to illustrate javac diagnostic messages.
50 */
51 class Example implements Comparable<Example> {
52 /* Create an Example from the files found at path.
53 * The head of the file, up to the first Java code, is scanned
54 * for information about the test, such as what resource keys it
55 * generates when run, what options are required to run it, and so on.
56 */
57 Example(File file) {
58 this.file = file;
59 declaredKeys = new TreeSet<String>();
60 srcFiles = new ArrayList<File>();
61 procFiles = new ArrayList<File>();
62 supportFiles = new ArrayList<File>();
63 srcPathFiles = new ArrayList<File>();
65 findFiles(file, srcFiles);
66 for (File f: srcFiles) {
67 parse(f);
68 }
70 if (infoFile == null)
71 throw new Error("Example " + file + " has no info file");
72 }
74 private void findFiles(File f, List<File> files) {
75 if (f.isDirectory()) {
76 for (File c: f.listFiles()) {
77 if (files == srcFiles && c.getName().equals("processors"))
78 findFiles(c, procFiles);
79 else if (files == srcFiles && c.getName().equals("sourcepath")) {
80 srcPathDir = c;
81 findFiles(c, srcPathFiles);
82 } else if (files == srcFiles && c.getName().equals("support"))
83 findFiles(c, supportFiles);
84 else
85 findFiles(c, files);
86 }
87 } else if (f.isFile() && f.getName().endsWith(".java")) {
88 files.add(f);
89 }
90 }
92 private void parse(File f) {
93 Pattern keyPat = Pattern.compile(" *// *key: *([^ ]+) *");
94 Pattern optPat = Pattern.compile(" *// *options: *(.*)");
95 Pattern runPat = Pattern.compile(" *// *run: *(.*)");
96 Pattern javaPat = Pattern.compile(" *@?[A-Za-z].*");
97 try {
98 String[] lines = read(f).split("[\r\n]+");
99 for (String line: lines) {
100 Matcher keyMatch = keyPat.matcher(line);
101 if (keyMatch.matches()) {
102 foundInfo(f);
103 declaredKeys.add(keyMatch.group(1));
104 continue;
105 }
106 Matcher optMatch = optPat.matcher(line);
107 if (optMatch.matches()) {
108 foundInfo(f);
109 options = Arrays.asList(optMatch.group(1).trim().split(" +"));
110 continue;
111 }
112 Matcher runMatch = runPat.matcher(line);
113 if (runMatch.matches()) {
114 foundInfo(f);
115 runOpts = Arrays.asList(runMatch.group(1).trim().split(" +"));
116 }
117 if (javaPat.matcher(line).matches())
118 break;
119 }
120 } catch (IOException e) {
121 throw new Error(e);
122 }
123 }
125 private void foundInfo(File file) {
126 if (infoFile != null && !infoFile.equals(file))
127 throw new Error("multiple info files found: " + infoFile + ", " + file);
128 infoFile = file;
129 }
131 String getName() {
132 return file.getName();
133 }
135 /**
136 * Get the set of resource keys that this test declares it will generate
137 * when it is run.
138 */
139 Set<String> getDeclaredKeys() {
140 return declaredKeys;
141 }
143 /**
144 * Get the set of resource keys that this test generates when it is run.
145 * The test will be run if it has not already been run.
146 */
147 Set<String> getActualKeys() {
148 if (actualKeys == null)
149 actualKeys = run(false);
150 return actualKeys;
151 }
153 /**
154 * Run the test. Information in the test header is used to determine
155 * how to run the test.
156 */
157 void run(PrintWriter out, boolean raw, boolean verbose) {
158 if (out == null)
159 throw new NullPointerException();
160 try {
161 run(out, null, raw, verbose);
162 } catch (IOException e) {
163 e.printStackTrace(out);
164 }
165 }
167 Set<String> run(boolean verbose) {
168 Set<String> keys = new TreeSet<String>();
169 try {
170 run(null, keys, true, verbose);
171 } catch (IOException e) {
172 e.printStackTrace(System.err);
173 }
174 return keys;
175 }
177 /**
178 * Run the test. Information in the test header is used to determine
179 * how to run the test.
180 */
181 private void run(PrintWriter out, Set<String> keys, boolean raw, boolean verbose)
182 throws IOException {
183 ClassLoader loader = getClass().getClassLoader();
184 if (supportFiles.size() > 0) {
185 File supportDir = new File(tempDir, "support");
186 supportDir.mkdirs();
187 clean(supportDir);
188 List<String> sOpts = Arrays.asList("-d", supportDir.getPath());
189 new Jsr199Compiler(verbose).run(null, null, false, sOpts, procFiles);
190 URLClassLoader ucl =
191 new URLClassLoader(new URL[] { supportDir.toURI().toURL() }, loader);
192 loader = ucl;
193 }
195 File classesDir = new File(tempDir, "classes");
196 classesDir.mkdirs();
197 clean(classesDir);
199 List<String> opts = new ArrayList<String>();
200 opts.add("-d");
201 opts.add(classesDir.getPath());
202 if (options != null)
203 opts.addAll(options);
205 if (procFiles.size() > 0) {
206 List<String> pOpts = Arrays.asList("-d", classesDir.getPath());
207 new Jsr199Compiler(verbose).run(null, null, false, pOpts, procFiles);
208 opts.add("-classpath"); // avoid using -processorpath for now
209 opts.add(classesDir.getPath());
210 createAnnotationServicesFile(classesDir, procFiles);
211 }
213 if (srcPathDir != null) {
214 opts.add("-sourcepath");
215 opts.add(srcPathDir.getPath());
216 }
218 try {
219 Compiler c = Compiler.getCompiler(runOpts, verbose);
220 c.run(out, keys, raw, opts, srcFiles);
221 } catch (IllegalArgumentException e) {
222 if (out != null) {
223 out.println("Invalid value for run tag: " + runOpts);
224 }
225 }
226 }
228 void createAnnotationServicesFile(File dir, List<File> procFiles) throws IOException {
229 File servicesDir = new File(new File(dir, "META-INF"), "services");
230 servicesDir.mkdirs();
231 File annoServices = new File(servicesDir, Processor.class.getName());
232 Writer out = new FileWriter(annoServices);
233 try {
234 for (File f: procFiles) {
235 out.write(f.getName().toString().replace(".java", ""));
236 }
237 } finally {
238 out.close();
239 }
240 }
242 @Override
243 public int compareTo(Example e) {
244 return file.compareTo(e.file);
245 }
247 @Override
248 public String toString() {
249 return file.getPath();
250 }
252 /**
253 * Read the contents of a file.
254 */
255 private String read(File f) throws IOException {
256 byte[] bytes = new byte[(int) f.length()];
257 DataInputStream in = new DataInputStream(new FileInputStream(f));
258 try {
259 in.readFully(bytes);
260 } finally {
261 in.close();
262 }
263 return new String(bytes);
264 }
266 /**
267 * Clean the contents of a directory.
268 */
269 boolean clean(File dir) {
270 boolean ok = true;
271 for (File f: dir.listFiles()) {
272 if (f.isDirectory())
273 ok &= clean(f);
274 ok &= f.delete();
275 }
276 return ok;
277 }
279 File file;
280 List<File> srcFiles;
281 List<File> procFiles;
282 File srcPathDir;
283 List<File> srcPathFiles;
284 List<File> supportFiles;
285 File infoFile;
286 private List<String> runOpts;
287 private List<String> options;
288 private Set<String> actualKeys;
289 private Set<String> declaredKeys;
291 static File tempDir = new File(System.getProperty("java.io.tmpdir"));
292 static void setTempDir(File tempDir) {
293 Example.tempDir = tempDir;
294 }
296 abstract static class Compiler {
297 interface Factory {
298 Compiler getCompiler(List<String> opts, boolean verbose);
299 }
301 static class DefaultFactory implements Factory {
302 public Compiler getCompiler(List<String> opts, boolean verbose) {
303 String first;
304 String[] rest;
305 if (opts == null || opts.isEmpty()) {
306 first = null;
307 rest = new String[0];
308 } else {
309 first = opts.get(0);
310 rest = opts.subList(1, opts.size()).toArray(new String[opts.size() - 1]);
311 }
312 if (first == null || first.equals("jsr199"))
313 return new Jsr199Compiler(verbose, rest);
314 else if (first.equals("simple"))
315 return new SimpleCompiler(verbose);
316 else if (first.equals("backdoor"))
317 return new BackdoorCompiler(verbose);
318 else
319 throw new IllegalArgumentException(first);
320 }
321 }
323 static Factory factory;
325 static Compiler getCompiler(List<String> opts, boolean verbose) {
326 if (factory == null)
327 factory = new DefaultFactory();
329 return factory.getCompiler(opts, verbose);
330 }
332 protected Compiler(boolean verbose) {
333 this.verbose = verbose;
334 }
336 abstract boolean run(PrintWriter out, Set<String> keys, boolean raw,
337 List<String> opts, List<File> files);
339 void setSupportClassLoader(ClassLoader cl) {
340 loader = cl;
341 }
343 protected ClassLoader loader;
344 protected boolean verbose;
345 }
347 /**
348 * Compile using the JSR 199 API. The diagnostics generated are
349 * scanned for resource keys. Not all diagnostic keys are generated
350 * via the JSR 199 API -- for example, rich diagnostics are not directly
351 * accessible, and some diagnostics generated by the file manager may
352 * not be generated (for example, the JSR 199 file manager does not see
353 * -Xlint:path).
354 */
355 static class Jsr199Compiler extends Compiler {
356 List<String> fmOpts;
358 Jsr199Compiler(boolean verbose, String... args) {
359 super(verbose);
360 for (int i = 0; i < args.length; i++) {
361 String arg = args[i];
362 if (arg.equals("-filemanager") && (i + 1 < args.length)) {
363 fmOpts = Arrays.asList(args[++i].split(","));
364 } else
365 throw new IllegalArgumentException(arg);
366 }
367 }
369 @Override
370 boolean run(PrintWriter out, Set<String> keys, boolean raw, List<String> opts, List<File> files) {
371 if (out != null && keys != null)
372 throw new IllegalArgumentException();
374 if (verbose)
375 System.err.println("run_jsr199: " + opts + " " + files);
377 DiagnosticCollector<JavaFileObject> dc = null;
378 if (keys != null)
379 dc = new DiagnosticCollector<JavaFileObject>();
381 if (raw) {
382 List<String> newOpts = new ArrayList<String>();
383 newOpts.add("-XDrawDiagnostics");
384 newOpts.addAll(opts);
385 opts = newOpts;
386 }
388 JavaCompiler c = ToolProvider.getSystemJavaCompiler();
390 StandardJavaFileManager fm = c.getStandardFileManager(dc, null, null);
391 if (fmOpts != null)
392 fm = new FileManager(fm, fmOpts);
394 Iterable<? extends JavaFileObject> fos = fm.getJavaFileObjectsFromFiles(files);
396 CompilationTask t = c.getTask(out, fm, dc, opts, null, fos);
397 Boolean ok = t.call();
399 if (keys != null) {
400 for (Diagnostic<? extends JavaFileObject> d: dc.getDiagnostics()) {
401 scanForKeys(unwrap(d), keys);
402 }
403 }
405 return ok;
406 }
408 /**
409 * Scan a diagnostic for resource keys. This will not detect additional
410 * sub diagnostics that might be generated by a rich diagnostic formatter.
411 */
412 private static void scanForKeys(JCDiagnostic d, Set<String> keys) {
413 keys.add(d.getCode());
414 for (Object o: d.getArgs()) {
415 if (o instanceof JCDiagnostic) {
416 scanForKeys((JCDiagnostic) o, keys);
417 }
418 }
419 for (JCDiagnostic sd: d.getSubdiagnostics())
420 scanForKeys(sd, keys);
421 }
423 private JCDiagnostic unwrap(Diagnostic<? extends JavaFileObject> diagnostic) {
424 if (diagnostic instanceof JCDiagnostic)
425 return (JCDiagnostic) diagnostic;
426 if (diagnostic instanceof ClientCodeWrapper.DiagnosticSourceUnwrapper)
427 return ((ClientCodeWrapper.DiagnosticSourceUnwrapper)diagnostic).d;
428 throw new IllegalArgumentException();
429 }
430 }
432 /**
433 * Run the test using the standard simple entry point.
434 */
435 static class SimpleCompiler extends Compiler {
436 SimpleCompiler(boolean verbose) {
437 super(verbose);
438 }
440 @Override
441 boolean run(PrintWriter out, Set<String> keys, boolean raw, List<String> opts, List<File> files) {
442 if (out != null && keys != null)
443 throw new IllegalArgumentException();
445 if (verbose)
446 System.err.println("run_simple: " + opts + " " + files);
448 List<String> args = new ArrayList<String>();
450 if (keys != null || raw)
451 args.add("-XDrawDiagnostics");
453 args.addAll(opts);
454 for (File f: files)
455 args.add(f.getPath());
457 StringWriter sw = null;
458 PrintWriter pw;
459 if (keys != null) {
460 sw = new StringWriter();
461 pw = new PrintWriter(sw);
462 } else
463 pw = out;
465 int rc = com.sun.tools.javac.Main.compile(args.toArray(new String[args.size()]), pw);
467 if (keys != null) {
468 pw.close();
469 scanForKeys(sw.toString(), keys);
470 }
472 return (rc == 0);
473 }
475 private static void scanForKeys(String text, Set<String> keys) {
476 StringTokenizer st = new StringTokenizer(text, " ,\r\n():");
477 while (st.hasMoreElements()) {
478 String t = st.nextToken();
479 if (t.startsWith("compiler."))
480 keys.add(t);
481 }
482 }
483 }
485 static class BackdoorCompiler extends Compiler {
486 BackdoorCompiler(boolean verbose) {
487 super(verbose);
488 }
490 @Override
491 boolean run(PrintWriter out, Set<String> keys, boolean raw, List<String> opts, List<File> files) {
492 if (out != null && keys != null)
493 throw new IllegalArgumentException();
495 if (verbose)
496 System.err.println("run_simple: " + opts + " " + files);
498 List<String> args = new ArrayList<String>();
500 if (out != null && raw)
501 args.add("-XDrawDiagnostics");
503 args.addAll(opts);
504 for (File f: files)
505 args.add(f.getPath());
507 StringWriter sw = null;
508 PrintWriter pw;
509 if (keys != null) {
510 sw = new StringWriter();
511 pw = new PrintWriter(sw);
512 } else
513 pw = out;
515 Context c = new Context();
516 JavacFileManager.preRegister(c); // can't create it until Log has been set up
517 MessageTracker.preRegister(c, keys);
518 com.sun.tools.javac.main.Main m = new com.sun.tools.javac.main.Main("javac", pw);
519 int rc = m.compile(args.toArray(new String[args.size()]), c);
521 if (keys != null) {
522 pw.close();
523 }
525 return (rc == 0);
526 }
528 static class MessageTracker extends JavacMessages {
530 MessageTracker(Context context) {
531 super(context);
532 }
534 static void preRegister(Context c, final Set<String> keys) {
535 if (keys != null) {
536 c.put(JavacMessages.messagesKey, new Context.Factory<JavacMessages>() {
537 public JavacMessages make(Context c) {
538 return new MessageTracker(c) {
539 @Override
540 public String getLocalizedString(Locale l, String key, Object... args) {
541 keys.add(key);
542 return super.getLocalizedString(l, key, args);
543 }
544 };
545 }
546 });
547 }
548 }
549 }
551 }
552 }