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