1 /* |
|
2 * Copyright (c) 2013, 2015, 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 java.io.BufferedInputStream; |
|
25 import java.io.BufferedReader; |
|
26 import java.io.BufferedWriter; |
|
27 import java.io.ByteArrayInputStream; |
|
28 import java.io.ByteArrayOutputStream; |
|
29 import java.io.File; |
|
30 import java.io.FilterOutputStream; |
|
31 import java.io.FilterWriter; |
|
32 import java.io.IOError; |
|
33 import java.io.IOException; |
|
34 import java.io.InputStream; |
|
35 import java.io.InputStreamReader; |
|
36 import java.io.OutputStream; |
|
37 import java.io.PrintStream; |
|
38 import java.io.PrintWriter; |
|
39 import java.io.StringWriter; |
|
40 import java.io.Writer; |
|
41 import java.net.URI; |
|
42 import java.nio.charset.Charset; |
|
43 import java.nio.file.FileVisitResult; |
|
44 import java.nio.file.Files; |
|
45 import java.nio.file.Path; |
|
46 import java.nio.file.Paths; |
|
47 import java.nio.file.SimpleFileVisitor; |
|
48 import java.nio.file.StandardCopyOption; |
|
49 import java.nio.file.attribute.BasicFileAttributes; |
|
50 import java.util.ArrayList; |
|
51 import java.util.Arrays; |
|
52 import java.util.Collections; |
|
53 import java.util.EnumMap; |
|
54 import java.util.EnumSet; |
|
55 import java.util.HashMap; |
|
56 import java.util.LinkedHashSet; |
|
57 import java.util.List; |
|
58 import java.util.ListIterator; |
|
59 import java.util.Locale; |
|
60 import java.util.Map; |
|
61 import java.util.Objects; |
|
62 import java.util.Set; |
|
63 import java.util.jar.Attributes; |
|
64 import java.util.jar.JarEntry; |
|
65 import java.util.jar.JarOutputStream; |
|
66 import java.util.jar.Manifest; |
|
67 import java.util.regex.Matcher; |
|
68 import java.util.regex.Pattern; |
|
69 import java.util.stream.Collectors; |
|
70 import java.util.stream.Stream; |
|
71 import java.util.stream.StreamSupport; |
|
72 |
|
73 import javax.tools.FileObject; |
|
74 import javax.tools.ForwardingJavaFileManager; |
|
75 import javax.tools.JavaCompiler; |
|
76 import javax.tools.JavaFileManager; |
|
77 import javax.tools.JavaFileObject; |
|
78 import javax.tools.JavaFileObject.Kind; |
|
79 import javax.tools.JavaFileManager.Location; |
|
80 import javax.tools.SimpleJavaFileObject; |
|
81 import javax.tools.StandardJavaFileManager; |
|
82 import javax.tools.StandardLocation; |
|
83 |
|
84 import com.sun.tools.javac.api.JavacTaskImpl; |
|
85 import com.sun.tools.javac.api.JavacTool; |
|
86 |
|
87 /** |
|
88 * Utility methods and classes for writing jtreg tests for |
|
89 * javac, javah, javap, and sjavac. (For javadoc support, |
|
90 * see JavadocTester.) |
|
91 * |
|
92 * <p>There is support for common file operations similar to |
|
93 * shell commands like cat, cp, diff, mv, rm, grep. |
|
94 * |
|
95 * <p>There is also support for invoking various tools, like |
|
96 * javac, javah, javap, jar, java and other JDK tools. |
|
97 * |
|
98 * <p><em>File separators</em>: for convenience, many operations accept strings |
|
99 * to represent filenames. On all platforms on which JDK is supported, |
|
100 * "/" is a legal filename component separator. In particular, even |
|
101 * on Windows, where the official file separator is "\", "/" is a legal |
|
102 * alternative. It is therefore recommended that any client code using |
|
103 * strings to specify filenames should use "/". |
|
104 * |
|
105 * @author Vicente Romero (original) |
|
106 * @author Jonathan Gibbons (revised) |
|
107 */ |
|
108 public class ToolBox { |
|
109 /** The platform line separator. */ |
|
110 public static final String lineSeparator = System.getProperty("line.separator"); |
|
111 /** The platform OS name. */ |
|
112 public static final String osName = System.getProperty("os.name"); |
|
113 |
|
114 /** The location of the class files for this test, or null if not set. */ |
|
115 public static final String testClasses = System.getProperty("test.classes"); |
|
116 /** The location of the source files for this test, or null if not set. */ |
|
117 public static final String testSrc = System.getProperty("test.src"); |
|
118 /** The location of the test JDK for this test, or null if not set. */ |
|
119 public static final String testJDK = System.getProperty("test.jdk"); |
|
120 |
|
121 /** The current directory. */ |
|
122 public static final Path currDir = Paths.get("."); |
|
123 |
|
124 /** The stream used for logging output. */ |
|
125 public PrintStream out = System.err; |
|
126 |
|
127 /** |
|
128 * Checks if the host OS is some version of Windows. |
|
129 * @return true if the host OS is some version of Windows |
|
130 */ |
|
131 public boolean isWindows() { |
|
132 return osName.toLowerCase(Locale.ENGLISH).startsWith("windows"); |
|
133 } |
|
134 |
|
135 /** |
|
136 * Splits a string around matches of the given regular expression. |
|
137 * If the string is empty, an empty list will be returned. |
|
138 * @param text the string to be split |
|
139 * @param sep the delimiting regular expression |
|
140 * @return the strings between the separators |
|
141 */ |
|
142 public List<String> split(String text, String sep) { |
|
143 if (text.isEmpty()) |
|
144 return Collections.emptyList(); |
|
145 return Arrays.asList(text.split(sep)); |
|
146 } |
|
147 |
|
148 /** |
|
149 * Checks if two lists of strings are equal. |
|
150 * @param l1 the first list of strings to be compared |
|
151 * @param l2 the second list of strings to be compared |
|
152 * @throws Error if the lists are not equal |
|
153 */ |
|
154 public void checkEqual(List<String> l1, List<String> l2) throws Error { |
|
155 if (!Objects.equals(l1, l2)) { |
|
156 // l1 and l2 cannot both be null |
|
157 if (l1 == null) |
|
158 throw new Error("comparison failed: l1 is null"); |
|
159 if (l2 == null) |
|
160 throw new Error("comparison failed: l2 is null"); |
|
161 // report first difference |
|
162 for (int i = 0; i < Math.min(l1.size(), l2.size()); i++) { |
|
163 String s1 = l1.get(i); |
|
164 String s2 = l1.get(i); |
|
165 if (!Objects.equals(s1, s2)) { |
|
166 throw new Error("comparison failed, index " + i + |
|
167 ", (" + s1 + ":" + s2 + ")"); |
|
168 } |
|
169 } |
|
170 throw new Error("comparison failed: l1.size=" + l1.size() + ", l2.size=" + l2.size()); |
|
171 } |
|
172 } |
|
173 |
|
174 /** |
|
175 * Filters a list of strings according to the given regular expression. |
|
176 * @param regex the regular expression |
|
177 * @param lines the strings to be filtered |
|
178 * @return the strings matching the regular expression |
|
179 */ |
|
180 public List<String> grep(String regex, List<String> lines) { |
|
181 return grep(Pattern.compile(regex), lines); |
|
182 } |
|
183 |
|
184 /** |
|
185 * Filters a list of strings according to the given regular expression. |
|
186 * @param pattern the regular expression |
|
187 * @param lines the strings to be filtered |
|
188 * @return the strings matching the regular expression |
|
189 */ |
|
190 public List<String> grep(Pattern pattern, List<String> lines) { |
|
191 return lines.stream() |
|
192 .filter(s -> pattern.matcher(s).find()) |
|
193 .collect(Collectors.toList()); |
|
194 } |
|
195 |
|
196 /** |
|
197 * Copies a file. |
|
198 * If the given destination exists and is a directory, the copy is created |
|
199 * in that directory. Otherwise, the copy will be placed at the destination, |
|
200 * possibly overwriting any existing file. |
|
201 * <p>Similar to the shell "cp" command: {@code cp from to}. |
|
202 * @param from the file to be copied |
|
203 * @param to where to copy the file |
|
204 * @throws IOException if any error occurred while copying the file |
|
205 */ |
|
206 public void copyFile(String from, String to) throws IOException { |
|
207 copyFile(Paths.get(from), Paths.get(to)); |
|
208 } |
|
209 |
|
210 /** |
|
211 * Copies a file. |
|
212 * If the given destination exists and is a directory, the copy is created |
|
213 * in that directory. Otherwise, the copy will be placed at the destination, |
|
214 * possibly overwriting any existing file. |
|
215 * <p>Similar to the shell "cp" command: {@code cp from to}. |
|
216 * @param from the file to be copied |
|
217 * @param to where to copy the file |
|
218 * @throws IOException if an error occurred while copying the file |
|
219 */ |
|
220 public void copyFile(Path from, Path to) throws IOException { |
|
221 if (Files.isDirectory(to)) { |
|
222 to = to.resolve(from.getFileName()); |
|
223 } else { |
|
224 Files.createDirectories(to.getParent()); |
|
225 } |
|
226 Files.copy(from, to, StandardCopyOption.REPLACE_EXISTING); |
|
227 } |
|
228 |
|
229 /** |
|
230 * Creates one of more directories. |
|
231 * For each of the series of paths, a directory will be created, |
|
232 * including any necessary parent directories. |
|
233 * <p>Similar to the shell command: {@code mkdir -p paths}. |
|
234 * @param paths the directories to be created |
|
235 * @throws IOException if an error occurred while creating the directories |
|
236 */ |
|
237 public void createDirectories(String... paths) throws IOException { |
|
238 if (paths.length == 0) |
|
239 throw new IllegalArgumentException("no directories specified"); |
|
240 for (String p : paths) |
|
241 Files.createDirectories(Paths.get(p)); |
|
242 } |
|
243 |
|
244 /** |
|
245 * Creates one or more directories. |
|
246 * For each of the series of paths, a directory will be created, |
|
247 * including any necessary parent directories. |
|
248 * <p>Similar to the shell command: {@code mkdir -p paths}. |
|
249 * @param paths the directories to be created |
|
250 * @throws IOException if an error occurred while creating the directories |
|
251 */ |
|
252 public void createDirectories(Path... paths) throws IOException { |
|
253 if (paths.length == 0) |
|
254 throw new IllegalArgumentException("no directories specified"); |
|
255 for (Path p : paths) |
|
256 Files.createDirectories(p); |
|
257 } |
|
258 |
|
259 /** |
|
260 * Deletes one or more files. |
|
261 * Any directories to be deleted must be empty. |
|
262 * <p>Similar to the shell command: {@code rm files}. |
|
263 * @param files the files to be deleted |
|
264 * @throws IOException if an error occurred while deleting the files |
|
265 */ |
|
266 public void deleteFiles(String... files) throws IOException { |
|
267 if (files.length == 0) |
|
268 throw new IllegalArgumentException("no files specified"); |
|
269 for (String file : files) |
|
270 Files.delete(Paths.get(file)); |
|
271 } |
|
272 |
|
273 /** |
|
274 * Deletes all content of a directory (but not the directory itself). |
|
275 * @param root the directory to be cleaned |
|
276 */ |
|
277 public void cleanDirectory(Path root) throws IOException { |
|
278 if (!Files.isDirectory(root)) { |
|
279 throw new IOException(root + " is not a directory"); |
|
280 } |
|
281 Files.walkFileTree(root, new SimpleFileVisitor<Path>() { |
|
282 @Override |
|
283 public FileVisitResult visitFile(Path file, BasicFileAttributes a) throws IOException { |
|
284 Files.delete(file); |
|
285 return FileVisitResult.CONTINUE; |
|
286 } |
|
287 |
|
288 @Override |
|
289 public FileVisitResult postVisitDirectory(Path dir, IOException e) throws IOException { |
|
290 if (e != null) { |
|
291 throw e; |
|
292 } |
|
293 if (!dir.equals(root)) { |
|
294 Files.delete(dir); |
|
295 } |
|
296 return FileVisitResult.CONTINUE; |
|
297 } |
|
298 }); |
|
299 } |
|
300 |
|
301 /** |
|
302 * Moves a file. |
|
303 * If the given destination exists and is a directory, the file will be moved |
|
304 * to that directory. Otherwise, the file will be moved to the destination, |
|
305 * possibly overwriting any existing file. |
|
306 * <p>Similar to the shell "mv" command: {@code mv from to}. |
|
307 * @param from the file to be moved |
|
308 * @param to where to move the file |
|
309 * @throws IOException if an error occurred while moving the file |
|
310 */ |
|
311 public void moveFile(String from, String to) throws IOException { |
|
312 moveFile(Paths.get(from), Paths.get(to)); |
|
313 } |
|
314 |
|
315 /** |
|
316 * Moves a file. |
|
317 * If the given destination exists and is a directory, the file will be moved |
|
318 * to that directory. Otherwise, the file will be moved to the destination, |
|
319 * possibly overwriting any existing file. |
|
320 * <p>Similar to the shell "mv" command: {@code mv from to}. |
|
321 * @param from the file to be moved |
|
322 * @param to where to move the file |
|
323 * @throws IOException if an error occurred while moving the file |
|
324 */ |
|
325 public void moveFile(Path from, Path to) throws IOException { |
|
326 if (Files.isDirectory(to)) { |
|
327 to = to.resolve(from.getFileName()); |
|
328 } else { |
|
329 Files.createDirectories(to.getParent()); |
|
330 } |
|
331 Files.move(from, to, StandardCopyOption.REPLACE_EXISTING); |
|
332 } |
|
333 |
|
334 /** |
|
335 * Reads the lines of a file. |
|
336 * The file is read using the default character encoding. |
|
337 * @param path the file to be read |
|
338 * @return the lines of the file. |
|
339 * @throws IOException if an error occurred while reading the file |
|
340 */ |
|
341 public List<String> readAllLines(String path) throws IOException { |
|
342 return readAllLines(path, null); |
|
343 } |
|
344 |
|
345 /** |
|
346 * Reads the lines of a file. |
|
347 * The file is read using the default character encoding. |
|
348 * @param path the file to be read |
|
349 * @return the lines of the file. |
|
350 * @throws IOException if an error occurred while reading the file |
|
351 */ |
|
352 public List<String> readAllLines(Path path) throws IOException { |
|
353 return readAllLines(path, null); |
|
354 } |
|
355 |
|
356 /** |
|
357 * Reads the lines of a file using the given encoding. |
|
358 * @param path the file to be read |
|
359 * @param encoding the encoding to be used to read the file |
|
360 * @return the lines of the file. |
|
361 * @throws IOException if an error occurred while reading the file |
|
362 */ |
|
363 public List<String> readAllLines(String path, String encoding) throws IOException { |
|
364 return readAllLines(Paths.get(path), encoding); |
|
365 } |
|
366 |
|
367 /** |
|
368 * Reads the lines of a file using the given encoding. |
|
369 * @param path the file to be read |
|
370 * @param encoding the encoding to be used to read the file |
|
371 * @return the lines of the file. |
|
372 * @throws IOException if an error occurred while reading the file |
|
373 */ |
|
374 public List<String> readAllLines(Path path, String encoding) throws IOException { |
|
375 return Files.readAllLines(path, getCharset(encoding)); |
|
376 } |
|
377 |
|
378 private Charset getCharset(String encoding) { |
|
379 return (encoding == null) ? Charset.defaultCharset() : Charset.forName(encoding); |
|
380 } |
|
381 |
|
382 /** |
|
383 * Writes a file containing the given content. |
|
384 * Any necessary directories for the file will be created. |
|
385 * @param path where to write the file |
|
386 * @param content the content for the file |
|
387 * @throws IOException if an error occurred while writing the file |
|
388 */ |
|
389 public void writeFile(String path, String content) throws IOException { |
|
390 writeFile(Paths.get(path), content); |
|
391 } |
|
392 |
|
393 /** |
|
394 * Writes a file containing the given content. |
|
395 * Any necessary directories for the file will be created. |
|
396 * @param path where to write the file |
|
397 * @param content the content for the file |
|
398 * @throws IOException if an error occurred while writing the file |
|
399 */ |
|
400 public void writeFile(Path path, String content) throws IOException { |
|
401 Path dir = path.getParent(); |
|
402 if (dir != null) |
|
403 Files.createDirectories(dir); |
|
404 try (BufferedWriter w = Files.newBufferedWriter(path)) { |
|
405 w.write(content); |
|
406 } |
|
407 } |
|
408 |
|
409 /** |
|
410 * Writes one or more files containing Java source code. |
|
411 * For each file to be written, the filename will be inferred from the |
|
412 * given base directory, the package declaration (if present) and from the |
|
413 * the name of the first class, interface or enum declared in the file. |
|
414 * <p>For example, if the base directory is /my/dir/ and the content |
|
415 * contains "package p; class C { }", the file will be written to |
|
416 * /my/dir/p/C.java. |
|
417 * <p>Note: the content is analyzed using regular expressions; |
|
418 * errors can occur if any contents have initial comments that might trip |
|
419 * up the analysis. |
|
420 * @param dir the base directory |
|
421 * @param contents the contents of the files to be written |
|
422 * @throws IOException if an error occurred while writing any of the files. |
|
423 */ |
|
424 public void writeJavaFiles(Path dir, String... contents) throws IOException { |
|
425 if (contents.length == 0) |
|
426 throw new IllegalArgumentException("no content specified for any files"); |
|
427 for (String c : contents) { |
|
428 new JavaSource(c).write(dir); |
|
429 } |
|
430 } |
|
431 |
|
432 /** |
|
433 * Returns the path for the binary of a JDK tool within {@link testJDK}. |
|
434 * @param tool the name of the tool |
|
435 * @return the path of the tool |
|
436 */ |
|
437 public Path getJDKTool(String tool) { |
|
438 return Paths.get(testJDK, "bin", tool); |
|
439 } |
|
440 |
|
441 /** |
|
442 * Returns a string representing the contents of an {@code Iterable} as a list. |
|
443 * @param <T> the type parameter of the {@code Iterable} |
|
444 * @param items the iterable |
|
445 * @return the string |
|
446 */ |
|
447 <T> String toString(Iterable<T> items) { |
|
448 return StreamSupport.stream(items.spliterator(), false) |
|
449 .map(Objects::toString) |
|
450 .collect(Collectors.joining(",", "[", "]")); |
|
451 } |
|
452 |
|
453 /** |
|
454 * The supertype for tasks. |
|
455 * Complex operations are modelled by building and running a "Task" object. |
|
456 * Tasks are typically configured in a fluent series of calls. |
|
457 */ |
|
458 public interface Task { |
|
459 /** |
|
460 * Returns the name of the task. |
|
461 * @return the name of the task |
|
462 */ |
|
463 String name(); |
|
464 |
|
465 /** |
|
466 * Executes the task as currently configured. |
|
467 * @return a Result object containing the results of running the task |
|
468 * @throws TaskError if the outcome of the task was not as expected |
|
469 */ |
|
470 Result run() throws TaskError; |
|
471 } |
|
472 |
|
473 /** |
|
474 * Exception thrown by {@code Task.run} when the outcome is not as |
|
475 * expected. |
|
476 */ |
|
477 public static class TaskError extends Error { |
|
478 /** |
|
479 * Creates a TaskError object with the given message. |
|
480 * @param message the message |
|
481 */ |
|
482 public TaskError(String message) { |
|
483 super(message); |
|
484 } |
|
485 } |
|
486 |
|
487 /** |
|
488 * An enum to indicate the mode a task should use it is when executed. |
|
489 */ |
|
490 public enum Mode { |
|
491 /** |
|
492 * The task should use the interface used by the command |
|
493 * line launcher for the task. |
|
494 * For example, for javac: com.sun.tools.javac.Main.compile |
|
495 */ |
|
496 CMDLINE, |
|
497 /** |
|
498 * The task should use a publicly defined API for the task. |
|
499 * For example, for javac: javax.tools.JavaCompiler |
|
500 */ |
|
501 API, |
|
502 /** |
|
503 * The task should use the standard launcher for the task. |
|
504 * For example, $JAVA_HOME/bin/javac |
|
505 */ |
|
506 EXEC |
|
507 } |
|
508 |
|
509 /** |
|
510 * An enum to indicate the expected success or failure of executing a task. |
|
511 */ |
|
512 public enum Expect { |
|
513 /** It is expected that the task will complete successfully. */ |
|
514 SUCCESS, |
|
515 /** It is expected that the task will not complete successfully. */ |
|
516 FAIL |
|
517 } |
|
518 |
|
519 /** |
|
520 * An enum to identify the streams that may be written by a {@code Task}. |
|
521 */ |
|
522 public enum OutputKind { |
|
523 /** Identifies output written to {@code System.out} or {@code stdout}. */ |
|
524 STDOUT, |
|
525 /** Identifies output written to {@code System.err} or {@code stderr}. */ |
|
526 STDERR, |
|
527 /** Identifies output written to a stream provided directly to the task. */ |
|
528 DIRECT |
|
529 }; |
|
530 |
|
531 /** |
|
532 * The results from running a {@link Task}. |
|
533 * The results contain the exit code returned when the tool was invoked, |
|
534 * and a map containing the output written to any streams during the |
|
535 * execution of the tool. |
|
536 * All tools support "stdout" and "stderr". |
|
537 * Tools that take an explicit PrintWriter save output written to that |
|
538 * stream as "main". |
|
539 */ |
|
540 public class Result { |
|
541 |
|
542 final Task task; |
|
543 final int exitCode; |
|
544 final Map<OutputKind, String> outputMap; |
|
545 |
|
546 Result(Task task, int exitCode, Map<OutputKind, String> outputMap) { |
|
547 this.task = task; |
|
548 this.exitCode = exitCode; |
|
549 this.outputMap = outputMap; |
|
550 } |
|
551 |
|
552 /** |
|
553 * Returns the content of a specified stream. |
|
554 * @param outputKind the kind of the selected stream |
|
555 * @return the content that was written to that stream when the tool |
|
556 * was executed. |
|
557 */ |
|
558 public String getOutput(OutputKind outputKind) { |
|
559 return outputMap.get(outputKind); |
|
560 } |
|
561 |
|
562 /** |
|
563 * Returns the content of named streams as a list of lines. |
|
564 * @param outputKinds the kinds of the selected streams |
|
565 * @return the content that was written to the given streams when the tool |
|
566 * was executed. |
|
567 */ |
|
568 public List<String> getOutputLines(OutputKind... outputKinds) { |
|
569 List<String> result = new ArrayList<>(); |
|
570 for (OutputKind outputKind : outputKinds) { |
|
571 result.addAll(Arrays.asList(outputMap.get(outputKind).split(lineSeparator))); |
|
572 } |
|
573 return result; |
|
574 } |
|
575 |
|
576 /** |
|
577 * Writes the content of the specified stream to the log. |
|
578 * @param kind the kind of the selected stream |
|
579 * @return this Result object |
|
580 */ |
|
581 public Result write(OutputKind kind) { |
|
582 String text = getOutput(kind); |
|
583 if (text == null || text.isEmpty()) |
|
584 out.println("[" + task.name() + ":" + kind + "]: empty"); |
|
585 else { |
|
586 out.println("[" + task.name() + ":" + kind + "]:"); |
|
587 out.print(text); |
|
588 } |
|
589 return this; |
|
590 } |
|
591 |
|
592 /** |
|
593 * Writes the content of all streams with any content to the log. |
|
594 * @return this Result object |
|
595 */ |
|
596 public Result writeAll() { |
|
597 outputMap.forEach((name, text) -> { |
|
598 if (!text.isEmpty()) { |
|
599 out.println("[" + name + "]:"); |
|
600 out.print(text); |
|
601 } |
|
602 }); |
|
603 return this; |
|
604 } |
|
605 } |
|
606 |
|
607 /** |
|
608 * A utility base class to simplify the implementation of tasks. |
|
609 * Provides support for running the task in a process and for |
|
610 * capturing output written by the task to stdout, stderr and |
|
611 * other writers where applicable. |
|
612 * @param <T> the implementing subclass |
|
613 */ |
|
614 protected static abstract class AbstractTask<T extends AbstractTask<T>> implements Task { |
|
615 protected final Mode mode; |
|
616 private final Map<OutputKind, String> redirects = new EnumMap<>(OutputKind.class); |
|
617 private final Map<String, String> envVars = new HashMap<>(); |
|
618 private Expect expect = Expect.SUCCESS; |
|
619 int expectedExitCode = 0; |
|
620 |
|
621 /** |
|
622 * Create a task that will execute in the specified mode. |
|
623 * @param mode the mode |
|
624 */ |
|
625 protected AbstractTask(Mode mode) { |
|
626 this.mode = mode; |
|
627 } |
|
628 |
|
629 /** |
|
630 * Sets the expected outcome of the task and calls {@code run()}. |
|
631 * @param expect the expected outcome |
|
632 * @return the result of calling {@code run()} |
|
633 */ |
|
634 public Result run(Expect expect) { |
|
635 expect(expect, Integer.MIN_VALUE); |
|
636 return run(); |
|
637 } |
|
638 |
|
639 /** |
|
640 * Sets the expected outcome of the task and calls {@code run()}. |
|
641 * @param expect the expected outcome |
|
642 * @param exitCode the expected exit code if the expected outcome |
|
643 * is {@code FAIL} |
|
644 * @return the result of calling {@code run()} |
|
645 */ |
|
646 public Result run(Expect expect, int exitCode) { |
|
647 expect(expect, exitCode); |
|
648 return run(); |
|
649 } |
|
650 |
|
651 /** |
|
652 * Sets the expected outcome and expected exit code of the task. |
|
653 * The exit code will not be checked if the outcome is |
|
654 * {@code Expect.SUCCESS} or if the exit code is set to |
|
655 * {@code Integer.MIN_VALUE}. |
|
656 * @param expect the expected outcome |
|
657 * @param exitCode the expected exit code |
|
658 */ |
|
659 protected void expect(Expect expect, int exitCode) { |
|
660 this.expect = expect; |
|
661 this.expectedExitCode = exitCode; |
|
662 } |
|
663 |
|
664 /** |
|
665 * Checks the exit code contained in a {@code Result} against the |
|
666 * expected outcome and exit value |
|
667 * @param result the result object |
|
668 * @return the result object |
|
669 * @throws TaskError if the exit code stored in the result object |
|
670 * does not match the expected outcome and exit code. |
|
671 */ |
|
672 protected Result checkExit(Result result) throws TaskError { |
|
673 switch (expect) { |
|
674 case SUCCESS: |
|
675 if (result.exitCode != 0) { |
|
676 result.writeAll(); |
|
677 throw new TaskError("Task " + name() + " failed: rc=" + result.exitCode); |
|
678 } |
|
679 break; |
|
680 |
|
681 case FAIL: |
|
682 if (result.exitCode == 0) { |
|
683 result.writeAll(); |
|
684 throw new TaskError("Task " + name() + " succeeded unexpectedly"); |
|
685 } |
|
686 |
|
687 if (expectedExitCode != Integer.MIN_VALUE |
|
688 && result.exitCode != expectedExitCode) { |
|
689 result.writeAll(); |
|
690 throw new TaskError("Task " + name() + "failed with unexpected exit code " |
|
691 + result.exitCode + ", expected " + expectedExitCode); |
|
692 } |
|
693 break; |
|
694 } |
|
695 return result; |
|
696 } |
|
697 |
|
698 /** |
|
699 * Sets an environment variable to be used by this task. |
|
700 * @param name the name of the environment variable |
|
701 * @param value the value for the environment variable |
|
702 * @return this task object |
|
703 * @throws IllegalStateException if the task mode is not {@code EXEC} |
|
704 */ |
|
705 protected T envVar(String name, String value) { |
|
706 if (mode != Mode.EXEC) |
|
707 throw new IllegalStateException(); |
|
708 envVars.put(name, value); |
|
709 return (T) this; |
|
710 } |
|
711 |
|
712 /** |
|
713 * Redirects output from an output stream to a file. |
|
714 * @param outputKind the name of the stream to be redirected. |
|
715 * @param path the file |
|
716 * @return this task object |
|
717 * @throws IllegalStateException if the task mode is not {@code EXEC} |
|
718 */ |
|
719 protected T redirect(OutputKind outputKind, String path) { |
|
720 if (mode != Mode.EXEC) |
|
721 throw new IllegalStateException(); |
|
722 redirects.put(outputKind, path); |
|
723 return (T) this; |
|
724 } |
|
725 |
|
726 /** |
|
727 * Returns a {@code ProcessBuilder} initialized with any |
|
728 * redirects and environment variables that have been set. |
|
729 * @return a {@code ProcessBuilder} |
|
730 */ |
|
731 protected ProcessBuilder getProcessBuilder() { |
|
732 if (mode != Mode.EXEC) |
|
733 throw new IllegalStateException(); |
|
734 ProcessBuilder pb = new ProcessBuilder(); |
|
735 if (redirects.get(OutputKind.STDOUT) != null) |
|
736 pb.redirectOutput(new File(redirects.get(OutputKind.STDOUT))); |
|
737 if (redirects.get(OutputKind.STDERR) != null) |
|
738 pb.redirectError(new File(redirects.get(OutputKind.STDERR))); |
|
739 pb.environment().putAll(envVars); |
|
740 return pb; |
|
741 } |
|
742 |
|
743 /** |
|
744 * Collects the output from a process and saves it in a {@code Result}. |
|
745 * @param tb the {@code ToolBox} containing the task {@code t} |
|
746 * @param t the task initiating the process |
|
747 * @param p the process |
|
748 * @return a Result object containing the output from the process and its |
|
749 * exit value. |
|
750 * @throws InterruptedException if the thread is interrupted |
|
751 */ |
|
752 protected Result runProcess(ToolBox tb, Task t, Process p) throws InterruptedException { |
|
753 if (mode != Mode.EXEC) |
|
754 throw new IllegalStateException(); |
|
755 ProcessOutput sysOut = new ProcessOutput(p.getInputStream()).start(); |
|
756 ProcessOutput sysErr = new ProcessOutput(p.getErrorStream()).start(); |
|
757 sysOut.waitUntilDone(); |
|
758 sysErr.waitUntilDone(); |
|
759 int rc = p.waitFor(); |
|
760 Map<OutputKind, String> outputMap = new EnumMap<>(OutputKind.class); |
|
761 outputMap.put(OutputKind.STDOUT, sysOut.getOutput()); |
|
762 outputMap.put(OutputKind.STDERR, sysErr.getOutput()); |
|
763 return checkExit(tb.new Result(t, rc, outputMap)); |
|
764 } |
|
765 |
|
766 /** |
|
767 * Thread-friendly class to read the output from a process until the stream |
|
768 * is exhausted. |
|
769 */ |
|
770 static class ProcessOutput implements Runnable { |
|
771 ProcessOutput(InputStream from) { |
|
772 in = new BufferedReader(new InputStreamReader(from)); |
|
773 out = new StringBuilder(); |
|
774 } |
|
775 |
|
776 ProcessOutput start() { |
|
777 new Thread(this).start(); |
|
778 return this; |
|
779 } |
|
780 |
|
781 @Override |
|
782 public void run() { |
|
783 try { |
|
784 String line; |
|
785 while ((line = in.readLine()) != null) { |
|
786 out.append(line).append(lineSeparator); |
|
787 } |
|
788 } catch (IOException e) { |
|
789 } |
|
790 synchronized (this) { |
|
791 done = true; |
|
792 notifyAll(); |
|
793 } |
|
794 } |
|
795 |
|
796 synchronized void waitUntilDone() throws InterruptedException { |
|
797 boolean interrupted = false; |
|
798 |
|
799 // poll interrupted flag, while waiting for copy to complete |
|
800 while (!(interrupted = Thread.interrupted()) && !done) |
|
801 wait(1000); |
|
802 |
|
803 if (interrupted) |
|
804 throw new InterruptedException(); |
|
805 } |
|
806 |
|
807 String getOutput() { |
|
808 return out.toString(); |
|
809 } |
|
810 |
|
811 private BufferedReader in; |
|
812 private final StringBuilder out; |
|
813 private boolean done; |
|
814 } |
|
815 |
|
816 /** |
|
817 * Utility class to simplify the handling of temporarily setting a |
|
818 * new stream for System.out or System.err. |
|
819 */ |
|
820 static class StreamOutput { |
|
821 // Functional interface to set a stream. |
|
822 // Expected use: System::setOut, System::setErr |
|
823 private interface Initializer { |
|
824 void set(PrintStream s); |
|
825 } |
|
826 |
|
827 private final ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
|
828 private final PrintStream ps = new PrintStream(baos); |
|
829 private final PrintStream prev; |
|
830 private final Initializer init; |
|
831 |
|
832 StreamOutput(PrintStream s, Initializer init) { |
|
833 prev = s; |
|
834 init.set(ps); |
|
835 this.init = init; |
|
836 } |
|
837 |
|
838 /** |
|
839 * Closes the stream and returns the contents that were written to it. |
|
840 * @return the contents that were written to it. |
|
841 */ |
|
842 String close() { |
|
843 init.set(prev); |
|
844 ps.close(); |
|
845 return baos.toString(); |
|
846 } |
|
847 } |
|
848 |
|
849 /** |
|
850 * Utility class to simplify the handling of creating an in-memory PrintWriter. |
|
851 */ |
|
852 static class WriterOutput { |
|
853 private final StringWriter sw = new StringWriter(); |
|
854 final PrintWriter pw = new PrintWriter(sw); |
|
855 |
|
856 /** |
|
857 * Closes the stream and returns the contents that were written to it. |
|
858 * @return the contents that were written to it. |
|
859 */ |
|
860 String close() { |
|
861 pw.close(); |
|
862 return sw.toString(); |
|
863 } |
|
864 } |
|
865 } |
|
866 |
|
867 /** |
|
868 * A task to configure and run the Java compiler, javac. |
|
869 */ |
|
870 public class JavacTask extends AbstractTask<JavacTask> { |
|
871 private boolean includeStandardOptions; |
|
872 private List<Path> classpath; |
|
873 private List<Path> sourcepath; |
|
874 private Path outdir; |
|
875 private List<String> options; |
|
876 private List<String> classes; |
|
877 private List<String> files; |
|
878 private List<JavaFileObject> fileObjects; |
|
879 private JavaFileManager fileManager; |
|
880 |
|
881 private JavaCompiler compiler; |
|
882 private StandardJavaFileManager internalFileManager; |
|
883 |
|
884 /** |
|
885 * Creates a task to execute {@code javac} using API mode. |
|
886 */ |
|
887 public JavacTask() { |
|
888 super(Mode.API); |
|
889 } |
|
890 |
|
891 /** |
|
892 * Creates a task to execute {@code javac} in a specified mode. |
|
893 * @param mode the mode to be used |
|
894 */ |
|
895 public JavacTask(Mode mode) { |
|
896 super(mode); |
|
897 } |
|
898 |
|
899 /** |
|
900 * Sets the classpath. |
|
901 * @param classpath the classpath |
|
902 * @return this task object |
|
903 */ |
|
904 public JavacTask classpath(String classpath) { |
|
905 this.classpath = Stream.of(classpath.split(File.pathSeparator)) |
|
906 .filter(s -> !s.isEmpty()) |
|
907 .map(s -> Paths.get(s)) |
|
908 .collect(Collectors.toList()); |
|
909 return this; |
|
910 } |
|
911 |
|
912 /** |
|
913 * Sets the classpath. |
|
914 * @param classpath the classpath |
|
915 * @return this task object |
|
916 */ |
|
917 public JavacTask classpath(Path... classpath) { |
|
918 this.classpath = Arrays.asList(classpath); |
|
919 return this; |
|
920 } |
|
921 |
|
922 /** |
|
923 * Sets the sourcepath. |
|
924 * @param sourcepath the sourcepath |
|
925 * @return this task object |
|
926 */ |
|
927 public JavacTask sourcepath(String sourcepath) { |
|
928 this.sourcepath = Stream.of(sourcepath.split(File.pathSeparator)) |
|
929 .filter(s -> !s.isEmpty()) |
|
930 .map(s -> Paths.get(s)) |
|
931 .collect(Collectors.toList()); |
|
932 return this; |
|
933 } |
|
934 |
|
935 /** |
|
936 * Sets the sourcepath. |
|
937 * @param classpath the sourcepath |
|
938 * @return this task object |
|
939 */ |
|
940 public JavacTask sourcepath(Path... sourcepath) { |
|
941 this.sourcepath = Arrays.asList(sourcepath); |
|
942 return this; |
|
943 } |
|
944 |
|
945 /** |
|
946 * Sets the output directory. |
|
947 * @param outdir the output directory |
|
948 * @return this task object |
|
949 */ |
|
950 public JavacTask outdir(String outdir) { |
|
951 this.outdir = Paths.get(outdir); |
|
952 return this; |
|
953 } |
|
954 |
|
955 /** |
|
956 * Sets the output directory. |
|
957 * @param outdir the output directory |
|
958 * @return this task object |
|
959 */ |
|
960 public JavacTask outdir(Path outdir) { |
|
961 this.outdir = outdir; |
|
962 return this; |
|
963 } |
|
964 |
|
965 /** |
|
966 * Sets the options. |
|
967 * @param options the options |
|
968 * @return this task object |
|
969 */ |
|
970 public JavacTask options(String... options) { |
|
971 this.options = Arrays.asList(options); |
|
972 return this; |
|
973 } |
|
974 |
|
975 /** |
|
976 * Sets the classes to be analyzed. |
|
977 * @param classes the classes |
|
978 * @return this task object |
|
979 */ |
|
980 public JavacTask classes(String... classes) { |
|
981 this.classes = Arrays.asList(classes); |
|
982 return this; |
|
983 } |
|
984 |
|
985 /** |
|
986 * Sets the files to be compiled or analyzed. |
|
987 * @param files the files |
|
988 * @return this task object |
|
989 */ |
|
990 public JavacTask files(String... files) { |
|
991 this.files = Arrays.asList(files); |
|
992 return this; |
|
993 } |
|
994 |
|
995 /** |
|
996 * Sets the files to be compiled or analyzed. |
|
997 * @param files the files |
|
998 * @return this task object |
|
999 */ |
|
1000 public JavacTask files(Path... files) { |
|
1001 this.files = Stream.of(files) |
|
1002 .map(Path::toString) |
|
1003 .collect(Collectors.toList()); |
|
1004 return this; |
|
1005 } |
|
1006 |
|
1007 /** |
|
1008 * Sets the sources to be compiled or analyzed. |
|
1009 * Each source string is converted into an in-memory object that |
|
1010 * can be passed directly to the compiler. |
|
1011 * @param sources the sources |
|
1012 * @return this task object |
|
1013 */ |
|
1014 public JavacTask sources(String... sources) { |
|
1015 fileObjects = Stream.of(sources) |
|
1016 .map(s -> new JavaSource(s)) |
|
1017 .collect(Collectors.toList()); |
|
1018 return this; |
|
1019 } |
|
1020 |
|
1021 /** |
|
1022 * Sets the file manager to be used by this task. |
|
1023 * @param fileManager the file manager |
|
1024 * @return this task object |
|
1025 */ |
|
1026 public JavacTask fileManager(JavaFileManager fileManager) { |
|
1027 this.fileManager = fileManager; |
|
1028 return this; |
|
1029 } |
|
1030 |
|
1031 /** |
|
1032 * {@inheritDoc} |
|
1033 * @return the name "javac" |
|
1034 */ |
|
1035 @Override |
|
1036 public String name() { |
|
1037 return "javac"; |
|
1038 } |
|
1039 |
|
1040 /** |
|
1041 * Calls the compiler with the arguments as currently configured. |
|
1042 * @return a Result object indicating the outcome of the compilation |
|
1043 * and the content of any output written to stdout, stderr, or the |
|
1044 * main stream by the compiler. |
|
1045 * @throws TaskError if the outcome of the task is not as expected. |
|
1046 */ |
|
1047 @Override |
|
1048 public Result run() { |
|
1049 if (mode == Mode.EXEC) |
|
1050 return runExec(); |
|
1051 |
|
1052 WriterOutput direct = new WriterOutput(); |
|
1053 // The following are to catch output to System.out and System.err, |
|
1054 // in case these are used instead of the primary (main) stream |
|
1055 StreamOutput sysOut = new StreamOutput(System.out, System::setOut); |
|
1056 StreamOutput sysErr = new StreamOutput(System.err, System::setErr); |
|
1057 int rc; |
|
1058 Map<OutputKind, String> outputMap = new HashMap<>(); |
|
1059 try { |
|
1060 switch (mode == null ? Mode.API : mode) { |
|
1061 case API: |
|
1062 rc = runAPI(direct.pw); |
|
1063 break; |
|
1064 case CMDLINE: |
|
1065 rc = runCommand(direct.pw); |
|
1066 break; |
|
1067 default: |
|
1068 throw new IllegalStateException(); |
|
1069 } |
|
1070 } catch (IOException e) { |
|
1071 out.println("Exception occurred: " + e); |
|
1072 rc = 99; |
|
1073 } finally { |
|
1074 outputMap.put(OutputKind.STDOUT, sysOut.close()); |
|
1075 outputMap.put(OutputKind.STDERR, sysErr.close()); |
|
1076 outputMap.put(OutputKind.DIRECT, direct.close()); |
|
1077 } |
|
1078 return checkExit(new Result(this, rc, outputMap)); |
|
1079 } |
|
1080 |
|
1081 private int runAPI(PrintWriter pw) throws IOException { |
|
1082 try { |
|
1083 // if (compiler == null) { |
|
1084 // TODO: allow this to be set externally |
|
1085 // compiler = ToolProvider.getSystemJavaCompiler(); |
|
1086 compiler = JavacTool.create(); |
|
1087 // } |
|
1088 |
|
1089 if (fileManager == null) |
|
1090 fileManager = internalFileManager = compiler.getStandardFileManager(null, null, null); |
|
1091 if (outdir != null) |
|
1092 setLocationFromPaths(StandardLocation.CLASS_OUTPUT, Collections.singletonList(outdir)); |
|
1093 if (classpath != null) |
|
1094 setLocationFromPaths(StandardLocation.CLASS_PATH, classpath); |
|
1095 if (sourcepath != null) |
|
1096 setLocationFromPaths(StandardLocation.SOURCE_PATH, sourcepath); |
|
1097 List<String> allOpts = new ArrayList<>(); |
|
1098 if (options != null) |
|
1099 allOpts.addAll(options); |
|
1100 |
|
1101 Iterable<? extends JavaFileObject> allFiles = joinFiles(files, fileObjects); |
|
1102 JavaCompiler.CompilationTask task = compiler.getTask(pw, |
|
1103 fileManager, |
|
1104 null, // diagnostic listener; should optionally collect diags |
|
1105 allOpts, |
|
1106 classes, |
|
1107 allFiles); |
|
1108 return ((JavacTaskImpl) task).doCall().exitCode; |
|
1109 } finally { |
|
1110 if (internalFileManager != null) |
|
1111 internalFileManager.close(); |
|
1112 } |
|
1113 } |
|
1114 |
|
1115 private void setLocationFromPaths(StandardLocation location, List<Path> files) throws IOException { |
|
1116 if (!(fileManager instanceof StandardJavaFileManager)) |
|
1117 throw new IllegalStateException("not a StandardJavaFileManager"); |
|
1118 ((StandardJavaFileManager) fileManager).setLocationFromPaths(location, files); |
|
1119 } |
|
1120 |
|
1121 private int runCommand(PrintWriter pw) { |
|
1122 List<String> args = getAllArgs(); |
|
1123 String[] argsArray = args.toArray(new String[args.size()]); |
|
1124 return com.sun.tools.javac.Main.compile(argsArray, pw); |
|
1125 } |
|
1126 |
|
1127 private Result runExec() { |
|
1128 List<String> args = new ArrayList<>(); |
|
1129 Path javac = getJDKTool("javac"); |
|
1130 args.add(javac.toString()); |
|
1131 if (includeStandardOptions) { |
|
1132 args.addAll(split(System.getProperty("test.tool.vm.opts"), " +")); |
|
1133 args.addAll(split(System.getProperty("test.compiler.opts"), " +")); |
|
1134 } |
|
1135 args.addAll(getAllArgs()); |
|
1136 |
|
1137 String[] argsArray = args.toArray(new String[args.size()]); |
|
1138 ProcessBuilder pb = getProcessBuilder(); |
|
1139 pb.command(argsArray); |
|
1140 try { |
|
1141 return runProcess(ToolBox.this, this, pb.start()); |
|
1142 } catch (IOException | InterruptedException e) { |
|
1143 throw new Error(e); |
|
1144 } |
|
1145 } |
|
1146 |
|
1147 private List<String> getAllArgs() { |
|
1148 List<String> args = new ArrayList<>(); |
|
1149 if (options != null) |
|
1150 args.addAll(options); |
|
1151 if (outdir != null) { |
|
1152 args.add("-d"); |
|
1153 args.add(outdir.toString()); |
|
1154 } |
|
1155 if (classpath != null) { |
|
1156 args.add("-classpath"); |
|
1157 args.add(toSearchPath(classpath)); |
|
1158 } |
|
1159 if (sourcepath != null) { |
|
1160 args.add("-sourcepath"); |
|
1161 args.add(toSearchPath(sourcepath)); |
|
1162 } |
|
1163 if (classes != null) |
|
1164 args.addAll(classes); |
|
1165 if (files != null) |
|
1166 args.addAll(files); |
|
1167 |
|
1168 return args; |
|
1169 } |
|
1170 |
|
1171 private String toSearchPath(List<Path> files) { |
|
1172 return files.stream() |
|
1173 .map(Path::toString) |
|
1174 .collect(Collectors.joining(File.pathSeparator)); |
|
1175 } |
|
1176 |
|
1177 private Iterable<? extends JavaFileObject> joinFiles( |
|
1178 List<String> files, List<JavaFileObject> fileObjects) { |
|
1179 if (files == null) |
|
1180 return fileObjects; |
|
1181 if (internalFileManager == null) |
|
1182 internalFileManager = compiler.getStandardFileManager(null, null, null); |
|
1183 Iterable<? extends JavaFileObject> filesAsFileObjects = |
|
1184 internalFileManager.getJavaFileObjectsFromStrings(files); |
|
1185 if (fileObjects == null) |
|
1186 return filesAsFileObjects; |
|
1187 List<JavaFileObject> combinedList = new ArrayList<>(); |
|
1188 for (JavaFileObject o : filesAsFileObjects) |
|
1189 combinedList.add(o); |
|
1190 combinedList.addAll(fileObjects); |
|
1191 return combinedList; |
|
1192 } |
|
1193 } |
|
1194 |
|
1195 /** |
|
1196 * A task to configure and run the native header tool, javah. |
|
1197 */ |
|
1198 public class JavahTask extends AbstractTask<JavahTask> { |
|
1199 private String classpath; |
|
1200 private List<String> options; |
|
1201 private List<String> classes; |
|
1202 |
|
1203 /** |
|
1204 * Create a task to execute {@code javah} using {@code CMDLINE} mode. |
|
1205 */ |
|
1206 public JavahTask() { |
|
1207 super(Mode.CMDLINE); |
|
1208 } |
|
1209 |
|
1210 /** |
|
1211 * Sets the classpath. |
|
1212 * @param classpath the classpath |
|
1213 * @return this task object |
|
1214 */ |
|
1215 public JavahTask classpath(String classpath) { |
|
1216 this.classpath = classpath; |
|
1217 return this; |
|
1218 } |
|
1219 |
|
1220 /** |
|
1221 * Sets the options. |
|
1222 * @param options the options |
|
1223 * @return this task object |
|
1224 */ |
|
1225 public JavahTask options(String... options) { |
|
1226 this.options = Arrays.asList(options); |
|
1227 return this; |
|
1228 } |
|
1229 |
|
1230 /** |
|
1231 * Sets the classes to be analyzed. |
|
1232 * @param classes the classes |
|
1233 * @return this task object |
|
1234 */ |
|
1235 public JavahTask classes(String... classes) { |
|
1236 this.classes = Arrays.asList(classes); |
|
1237 return this; |
|
1238 } |
|
1239 |
|
1240 /** |
|
1241 * {@inheritDoc} |
|
1242 * @return the name "javah" |
|
1243 */ |
|
1244 @Override |
|
1245 public String name() { |
|
1246 return "javah"; |
|
1247 } |
|
1248 |
|
1249 /** |
|
1250 * Calls the javah tool with the arguments as currently configured. |
|
1251 * @return a Result object indicating the outcome of the task |
|
1252 * and the content of any output written to stdout, stderr, or the |
|
1253 * main stream provided to the task. |
|
1254 * @throws TaskError if the outcome of the task is not as expected. |
|
1255 */ |
|
1256 @Override |
|
1257 public Result run() { |
|
1258 List<String> args = new ArrayList<>(); |
|
1259 if (options != null) |
|
1260 args.addAll(options); |
|
1261 if (classpath != null) { |
|
1262 args.add("-classpath"); |
|
1263 args.add(classpath); |
|
1264 } |
|
1265 if (classes != null) |
|
1266 args.addAll(classes); |
|
1267 |
|
1268 WriterOutput direct = new WriterOutput(); |
|
1269 // These are to catch output to System.out and System.err, |
|
1270 // in case these are used instead of the primary streams |
|
1271 StreamOutput sysOut = new StreamOutput(System.out, System::setOut); |
|
1272 StreamOutput sysErr = new StreamOutput(System.err, System::setErr); |
|
1273 int rc; |
|
1274 Map<OutputKind, String> outputMap = new HashMap<>(); |
|
1275 try { |
|
1276 rc = com.sun.tools.javah.Main.run(args.toArray(new String[args.size()]), direct.pw); |
|
1277 } finally { |
|
1278 outputMap.put(OutputKind.STDOUT, sysOut.close()); |
|
1279 outputMap.put(OutputKind.STDERR, sysErr.close()); |
|
1280 outputMap.put(OutputKind.DIRECT, direct.close()); |
|
1281 } |
|
1282 return checkExit(new Result(this, rc, outputMap)); |
|
1283 } |
|
1284 } |
|
1285 |
|
1286 /** |
|
1287 * A task to configure and run the disassembler tool, javap. |
|
1288 */ |
|
1289 public class JavapTask extends AbstractTask<JavapTask> { |
|
1290 private String classpath; |
|
1291 private List<String> options; |
|
1292 private List<String> classes; |
|
1293 |
|
1294 /** |
|
1295 * Create a task to execute {@code javap} using {@code CMDLINE} mode. |
|
1296 */ |
|
1297 public JavapTask() { |
|
1298 super(Mode.CMDLINE); |
|
1299 } |
|
1300 |
|
1301 /** |
|
1302 * Sets the classpath. |
|
1303 * @param classpath the classpath |
|
1304 * @return this task object |
|
1305 */ |
|
1306 public JavapTask classpath(String classpath) { |
|
1307 this.classpath = classpath; |
|
1308 return this; |
|
1309 } |
|
1310 |
|
1311 /** |
|
1312 * Sets the options. |
|
1313 * @param options the options |
|
1314 * @return this task object |
|
1315 */ |
|
1316 public JavapTask options(String... options) { |
|
1317 this.options = Arrays.asList(options); |
|
1318 return this; |
|
1319 } |
|
1320 |
|
1321 /** |
|
1322 * Sets the classes to be analyzed. |
|
1323 * @param classes the classes |
|
1324 * @return this task object |
|
1325 */ |
|
1326 public JavapTask classes(String... classes) { |
|
1327 this.classes = Arrays.asList(classes); |
|
1328 return this; |
|
1329 } |
|
1330 |
|
1331 /** |
|
1332 * {@inheritDoc} |
|
1333 * @return the name "javap" |
|
1334 */ |
|
1335 @Override |
|
1336 public String name() { |
|
1337 return "javap"; |
|
1338 } |
|
1339 |
|
1340 /** |
|
1341 * Calls the javap tool with the arguments as currently configured. |
|
1342 * @return a Result object indicating the outcome of the task |
|
1343 * and the content of any output written to stdout, stderr, or the |
|
1344 * main stream. |
|
1345 * @throws TaskError if the outcome of the task is not as expected. |
|
1346 */ |
|
1347 @Override |
|
1348 public Result run() { |
|
1349 List<String> args = new ArrayList<>(); |
|
1350 if (options != null) |
|
1351 args.addAll(options); |
|
1352 if (classpath != null) { |
|
1353 args.add("-classpath"); |
|
1354 args.add(classpath); |
|
1355 } |
|
1356 if (classes != null) |
|
1357 args.addAll(classes); |
|
1358 |
|
1359 WriterOutput direct = new WriterOutput(); |
|
1360 // These are to catch output to System.out and System.err, |
|
1361 // in case these are used instead of the primary streams |
|
1362 StreamOutput sysOut = new StreamOutput(System.out, System::setOut); |
|
1363 StreamOutput sysErr = new StreamOutput(System.err, System::setErr); |
|
1364 |
|
1365 int rc; |
|
1366 Map<OutputKind, String> outputMap = new HashMap<>(); |
|
1367 try { |
|
1368 rc = com.sun.tools.javap.Main.run(args.toArray(new String[args.size()]), direct.pw); |
|
1369 } finally { |
|
1370 outputMap.put(OutputKind.STDOUT, sysOut.close()); |
|
1371 outputMap.put(OutputKind.STDERR, sysErr.close()); |
|
1372 outputMap.put(OutputKind.DIRECT, direct.close()); |
|
1373 } |
|
1374 return checkExit(new Result(this, rc, outputMap)); |
|
1375 } |
|
1376 } |
|
1377 |
|
1378 /** |
|
1379 * A task to configure and run the jar file utility. |
|
1380 */ |
|
1381 public class JarTask extends AbstractTask<JarTask> { |
|
1382 private Path jar; |
|
1383 private Manifest manifest; |
|
1384 private String classpath; |
|
1385 private String mainClass; |
|
1386 private Path baseDir; |
|
1387 private List<Path> paths; |
|
1388 private Set<FileObject> fileObjects; |
|
1389 |
|
1390 /** |
|
1391 * Creates a task to write jar files, using API mode. |
|
1392 */ |
|
1393 public JarTask() { |
|
1394 super(Mode.API); |
|
1395 paths = Collections.emptyList(); |
|
1396 fileObjects = new LinkedHashSet<>(); |
|
1397 } |
|
1398 |
|
1399 /** |
|
1400 * Creates a JarTask for use with a given jar file. |
|
1401 * @param path the file |
|
1402 */ |
|
1403 public JarTask(String path) { |
|
1404 this(); |
|
1405 jar = Paths.get(path); |
|
1406 } |
|
1407 |
|
1408 /** |
|
1409 * Creates a JarTask for use with a given jar file. |
|
1410 * @param path the file |
|
1411 */ |
|
1412 public JarTask(Path path) { |
|
1413 this(); |
|
1414 jar = path; |
|
1415 } |
|
1416 |
|
1417 /** |
|
1418 * Sets a manifest for the jar file. |
|
1419 * @param manifest the manifest |
|
1420 * @return this task object |
|
1421 */ |
|
1422 public JarTask manifest(Manifest manifest) { |
|
1423 this.manifest = manifest; |
|
1424 return this; |
|
1425 } |
|
1426 |
|
1427 /** |
|
1428 * Sets a manifest for the jar file. |
|
1429 * @param manifest a string containing the contents of the manifest |
|
1430 * @return this task object |
|
1431 * @throws IOException if there is a problem creating the manifest |
|
1432 */ |
|
1433 public JarTask manifest(String manifest) throws IOException { |
|
1434 this.manifest = new Manifest(new ByteArrayInputStream(manifest.getBytes())); |
|
1435 return this; |
|
1436 } |
|
1437 |
|
1438 /** |
|
1439 * Sets the classpath to be written to the {@code Class-Path} |
|
1440 * entry in the manifest. |
|
1441 * @param classpath the classpath |
|
1442 * @return this task object |
|
1443 */ |
|
1444 public JarTask classpath(String classpath) { |
|
1445 this.classpath = classpath; |
|
1446 return this; |
|
1447 } |
|
1448 |
|
1449 /** |
|
1450 * Sets the class to be written to the {@code Main-Class} |
|
1451 * entry in the manifest.. |
|
1452 * @param mainClass the name of the main class |
|
1453 * @return this task object |
|
1454 */ |
|
1455 public JarTask mainClass(String mainClass) { |
|
1456 this.mainClass = mainClass; |
|
1457 return this; |
|
1458 } |
|
1459 |
|
1460 /** |
|
1461 * Sets the base directory for files to be written into the jar file. |
|
1462 * @param baseDir the base directory |
|
1463 * @return this task object |
|
1464 */ |
|
1465 public JarTask baseDir(String baseDir) { |
|
1466 this.baseDir = Paths.get(baseDir); |
|
1467 return this; |
|
1468 } |
|
1469 |
|
1470 /** |
|
1471 * Sets the base directory for files to be written into the jar file. |
|
1472 * @param baseDir the base directory |
|
1473 * @return this task object |
|
1474 */ |
|
1475 public JarTask baseDir(Path baseDir) { |
|
1476 this.baseDir = baseDir; |
|
1477 return this; |
|
1478 } |
|
1479 |
|
1480 /** |
|
1481 * Sets the files to be written into the jar file. |
|
1482 * @param files the files |
|
1483 * @return this task object |
|
1484 */ |
|
1485 public JarTask files(String... files) { |
|
1486 this.paths = Stream.of(files) |
|
1487 .map(file -> Paths.get(file)) |
|
1488 .collect(Collectors.toList()); |
|
1489 return this; |
|
1490 } |
|
1491 |
|
1492 /** |
|
1493 * Adds a set of file objects to be written into the jar file, by copying them |
|
1494 * from a Location in a JavaFileManager. |
|
1495 * The file objects to be written are specified by a series of paths; |
|
1496 * each path can be in one of the following forms: |
|
1497 * <ul> |
|
1498 * <li>The name of a class. For example, java.lang.Object. |
|
1499 * In this case, the corresponding .class file will be written to the jar file. |
|
1500 * <li>the name of a package followed by {@code .*}. For example, {@code java.lang.*}. |
|
1501 * In this case, all the class files in the specified package will be written to |
|
1502 * the jar file. |
|
1503 * <li>the name of a package followed by {@code .**}. For example, {@code java.lang.**}. |
|
1504 * In this case, all the class files in the specified package, and any subpackages |
|
1505 * will be written to the jar file. |
|
1506 * </ul> |
|
1507 * |
|
1508 * @param fm the file manager in which to find the file objects |
|
1509 * @param l the location in which to find the file objects |
|
1510 * @param paths the paths specifying the file objects to be copied |
|
1511 * @return this task object |
|
1512 * @throws IOException if errors occur while determining the set of file objects |
|
1513 */ |
|
1514 public JarTask files(JavaFileManager fm, Location l, String... paths) |
|
1515 throws IOException { |
|
1516 for (String p : paths) { |
|
1517 if (p.endsWith(".**")) |
|
1518 addPackage(fm, l, p.substring(0, p.length() - 3), true); |
|
1519 else if (p.endsWith(".*")) |
|
1520 addPackage(fm, l, p.substring(0, p.length() - 2), false); |
|
1521 else |
|
1522 addFile(fm, l, p); |
|
1523 } |
|
1524 return this; |
|
1525 } |
|
1526 |
|
1527 private void addPackage(JavaFileManager fm, Location l, String pkg, boolean recurse) |
|
1528 throws IOException { |
|
1529 for (JavaFileObject fo : fm.list(l, pkg, EnumSet.allOf(JavaFileObject.Kind.class), recurse)) { |
|
1530 fileObjects.add(fo); |
|
1531 } |
|
1532 } |
|
1533 |
|
1534 private void addFile(JavaFileManager fm, Location l, String path) throws IOException { |
|
1535 JavaFileObject fo = fm.getJavaFileForInput(l, path, Kind.CLASS); |
|
1536 fileObjects.add(fo); |
|
1537 } |
|
1538 |
|
1539 /** |
|
1540 * Provides limited jar command-like functionality. |
|
1541 * The supported commands are: |
|
1542 * <ul> |
|
1543 * <li> jar cf jarfile -C dir files... |
|
1544 * <li> jar cfm jarfile manifestfile -C dir files... |
|
1545 * </ul> |
|
1546 * Any values specified by other configuration methods will be ignored. |
|
1547 * @param args arguments in the style of those for the jar command |
|
1548 * @return a Result object containing the results of running the task |
|
1549 */ |
|
1550 public Result run(String... args) { |
|
1551 if (args.length < 2) |
|
1552 throw new IllegalArgumentException(); |
|
1553 |
|
1554 ListIterator<String> iter = Arrays.asList(args).listIterator(); |
|
1555 String first = iter.next(); |
|
1556 switch (first) { |
|
1557 case "cf": |
|
1558 jar = Paths.get(iter.next()); |
|
1559 break; |
|
1560 case "cfm": |
|
1561 jar = Paths.get(iter.next()); |
|
1562 try (InputStream in = Files.newInputStream(Paths.get(iter.next()))) { |
|
1563 manifest = new Manifest(in); |
|
1564 } catch (IOException e) { |
|
1565 throw new IOError(e); |
|
1566 } |
|
1567 break; |
|
1568 } |
|
1569 |
|
1570 if (iter.hasNext()) { |
|
1571 if (iter.next().equals("-C")) |
|
1572 baseDir = Paths.get(iter.next()); |
|
1573 else |
|
1574 iter.previous(); |
|
1575 } |
|
1576 |
|
1577 paths = new ArrayList<>(); |
|
1578 while (iter.hasNext()) |
|
1579 paths.add(Paths.get(iter.next())); |
|
1580 |
|
1581 return run(); |
|
1582 } |
|
1583 |
|
1584 /** |
|
1585 * {@inheritDoc} |
|
1586 * @return the name "jar" |
|
1587 */ |
|
1588 @Override |
|
1589 public String name() { |
|
1590 return "jar"; |
|
1591 } |
|
1592 |
|
1593 /** |
|
1594 * Creates a jar file with the arguments as currently configured. |
|
1595 * @return a Result object indicating the outcome of the compilation |
|
1596 * and the content of any output written to stdout, stderr, or the |
|
1597 * main stream by the compiler. |
|
1598 * @throws TaskError if the outcome of the task is not as expected. |
|
1599 */ |
|
1600 @Override |
|
1601 public Result run() { |
|
1602 Manifest m = (manifest == null) ? new Manifest() : manifest; |
|
1603 Attributes mainAttrs = m.getMainAttributes(); |
|
1604 if (mainClass != null) |
|
1605 mainAttrs.put(Attributes.Name.MAIN_CLASS, mainClass); |
|
1606 if (classpath != null) |
|
1607 mainAttrs.put(Attributes.Name.CLASS_PATH, classpath); |
|
1608 |
|
1609 StreamOutput sysOut = new StreamOutput(System.out, System::setOut); |
|
1610 StreamOutput sysErr = new StreamOutput(System.err, System::setErr); |
|
1611 |
|
1612 Map<OutputKind, String> outputMap = new HashMap<>(); |
|
1613 |
|
1614 try (OutputStream os = Files.newOutputStream(jar); |
|
1615 JarOutputStream jos = openJar(os, m)) { |
|
1616 writeFiles(jos); |
|
1617 writeFileObjects(jos); |
|
1618 } catch (IOException e) { |
|
1619 error("Exception while opening " + jar, e); |
|
1620 } finally { |
|
1621 outputMap.put(OutputKind.STDOUT, sysOut.close()); |
|
1622 outputMap.put(OutputKind.STDERR, sysErr.close()); |
|
1623 } |
|
1624 return checkExit(new Result(this, (errors == 0) ? 0 : 1, outputMap)); |
|
1625 } |
|
1626 |
|
1627 private JarOutputStream openJar(OutputStream os, Manifest m) throws IOException { |
|
1628 if (m == null || m.getMainAttributes().isEmpty() && m.getEntries().isEmpty()) { |
|
1629 return new JarOutputStream(os); |
|
1630 } else { |
|
1631 if (m.getMainAttributes().get(Attributes.Name.MANIFEST_VERSION) == null) |
|
1632 m.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0"); |
|
1633 return new JarOutputStream(os, m); |
|
1634 } |
|
1635 } |
|
1636 |
|
1637 private void writeFiles(JarOutputStream jos) throws IOException { |
|
1638 Path base = (baseDir == null) ? currDir : baseDir; |
|
1639 for (Path path : paths) { |
|
1640 Files.walkFileTree(base.resolve(path), new SimpleFileVisitor<Path>() { |
|
1641 @Override |
|
1642 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { |
|
1643 try { |
|
1644 String p = base.relativize(file) |
|
1645 .normalize() |
|
1646 .toString() |
|
1647 .replace(File.separatorChar, '/'); |
|
1648 JarEntry e = new JarEntry(p); |
|
1649 jos.putNextEntry(e); |
|
1650 try { |
|
1651 jos.write(Files.readAllBytes(file)); |
|
1652 } finally { |
|
1653 jos.closeEntry(); |
|
1654 } |
|
1655 return FileVisitResult.CONTINUE; |
|
1656 } catch (IOException e) { |
|
1657 error("Exception while adding " + file + " to jar file", e); |
|
1658 return FileVisitResult.TERMINATE; |
|
1659 } |
|
1660 } |
|
1661 }); |
|
1662 } |
|
1663 } |
|
1664 |
|
1665 private void writeFileObjects(JarOutputStream jos) throws IOException { |
|
1666 for (FileObject fo : fileObjects) { |
|
1667 String p = guessPath(fo); |
|
1668 JarEntry e = new JarEntry(p); |
|
1669 jos.putNextEntry(e); |
|
1670 try { |
|
1671 byte[] buf = new byte[1024]; |
|
1672 try (BufferedInputStream in = new BufferedInputStream(fo.openInputStream())) { |
|
1673 int n; |
|
1674 while ((n = in.read(buf)) > 0) |
|
1675 jos.write(buf, 0, n); |
|
1676 } catch (IOException ex) { |
|
1677 error("Exception while adding " + fo.getName() + " to jar file", ex); |
|
1678 } |
|
1679 } finally { |
|
1680 jos.closeEntry(); |
|
1681 } |
|
1682 } |
|
1683 } |
|
1684 |
|
1685 /* |
|
1686 * A jar: URL is of the form jar:URL!/<entry> where URL is a URL for the .jar file itself. |
|
1687 * In Symbol files (i.e. ct.sym) the underlying entry is prefixed META-INF/sym/<base>. |
|
1688 */ |
|
1689 private final Pattern jarEntry = Pattern.compile(".*!/(?:META-INF/sym/[^/]+/)?(.*)"); |
|
1690 |
|
1691 /* |
|
1692 * A jrt: URL is of the form jrt:/modules/<module>/<package>/<file> |
|
1693 */ |
|
1694 private final Pattern jrtEntry = Pattern.compile("/modules/([^/]+)/(.*)"); |
|
1695 |
|
1696 /* |
|
1697 * A file: URL is of the form file:/path/to/{modules,patches}/<module>/<package>/<file> |
|
1698 */ |
|
1699 private final Pattern fileEntry = Pattern.compile(".*/(?:modules|patches)/([^/]+)/(.*)"); |
|
1700 |
|
1701 private String guessPath(FileObject fo) { |
|
1702 URI u = fo.toUri(); |
|
1703 switch (u.getScheme()) { |
|
1704 case "jar": { |
|
1705 Matcher m = jarEntry.matcher(u.getSchemeSpecificPart()); |
|
1706 if (m.matches()) { |
|
1707 return m.group(1); |
|
1708 } |
|
1709 break; |
|
1710 } |
|
1711 case "jrt": { |
|
1712 Matcher m = jrtEntry.matcher(u.getSchemeSpecificPart()); |
|
1713 if (m.matches()) { |
|
1714 return m.group(2); |
|
1715 } |
|
1716 break; |
|
1717 } |
|
1718 case "file": { |
|
1719 Matcher m = fileEntry.matcher(u.getSchemeSpecificPart()); |
|
1720 if (m.matches()) { |
|
1721 return m.group(2); |
|
1722 } |
|
1723 break; |
|
1724 } |
|
1725 } |
|
1726 throw new IllegalArgumentException(fo.getName() + "--" + fo.toUri()); |
|
1727 } |
|
1728 |
|
1729 private void error(String message, Throwable t) { |
|
1730 out.println("Error: " + message + ": " + t); |
|
1731 errors++; |
|
1732 } |
|
1733 |
|
1734 private int errors; |
|
1735 } |
|
1736 |
|
1737 /** |
|
1738 * A task to configure and run the Java launcher. |
|
1739 */ |
|
1740 public class JavaTask extends AbstractTask<JavaTask> { |
|
1741 boolean includeStandardOptions = true; |
|
1742 private String classpath; |
|
1743 private List<String> vmOptions; |
|
1744 private String className; |
|
1745 private List<String> classArgs; |
|
1746 |
|
1747 /** |
|
1748 * Create a task to run the Java launcher, using {@code EXEC} mode. |
|
1749 */ |
|
1750 public JavaTask() { |
|
1751 super(Mode.EXEC); |
|
1752 } |
|
1753 |
|
1754 /** |
|
1755 * Sets the classpath. |
|
1756 * @param classpath the classpath |
|
1757 * @return this task object |
|
1758 */ |
|
1759 public JavaTask classpath(String classpath) { |
|
1760 this.classpath = classpath; |
|
1761 return this; |
|
1762 } |
|
1763 |
|
1764 /** |
|
1765 * Sets the VM options. |
|
1766 * @param vmOptions the options |
|
1767 * @return this task object |
|
1768 */ |
|
1769 public JavaTask vmOptions(String... vmOptions) { |
|
1770 this.vmOptions = Arrays.asList(vmOptions); |
|
1771 return this; |
|
1772 } |
|
1773 |
|
1774 /** |
|
1775 * Sets the name of the class to be executed. |
|
1776 * @param className the name of the class |
|
1777 * @return this task object |
|
1778 */ |
|
1779 public JavaTask className(String className) { |
|
1780 this.className = className; |
|
1781 return this; |
|
1782 } |
|
1783 |
|
1784 /** |
|
1785 * Sets the arguments for the class to be executed. |
|
1786 * @param classArgs the arguments |
|
1787 * @return this task object |
|
1788 */ |
|
1789 public JavaTask classArgs(String... classArgs) { |
|
1790 this.classArgs = Arrays.asList(classArgs); |
|
1791 return this; |
|
1792 } |
|
1793 |
|
1794 /** |
|
1795 * Sets whether or not the standard VM and java options for the test should be passed |
|
1796 * to the new VM instance. If this method is not called, the default behavior is that |
|
1797 * the options will be passed to the new VM instance. |
|
1798 * |
|
1799 * @param includeStandardOptions whether or not the standard VM and java options for |
|
1800 * the test should be passed to the new VM instance. |
|
1801 * @return this task object |
|
1802 */ |
|
1803 public JavaTask includeStandardOptions(boolean includeStandardOptions) { |
|
1804 this.includeStandardOptions = includeStandardOptions; |
|
1805 return this; |
|
1806 } |
|
1807 |
|
1808 /** |
|
1809 * {@inheritDoc} |
|
1810 * @return the name "java" |
|
1811 */ |
|
1812 @Override |
|
1813 public String name() { |
|
1814 return "java"; |
|
1815 } |
|
1816 |
|
1817 /** |
|
1818 * Calls the Java launcher with the arguments as currently configured. |
|
1819 * @return a Result object indicating the outcome of the task |
|
1820 * and the content of any output written to stdout or stderr. |
|
1821 * @throws TaskError if the outcome of the task is not as expected. |
|
1822 */ |
|
1823 @Override |
|
1824 public Result run() { |
|
1825 List<String> args = new ArrayList<>(); |
|
1826 args.add(getJDKTool("java").toString()); |
|
1827 if (includeStandardOptions) { |
|
1828 args.addAll(split(System.getProperty("test.vm.opts"), " +")); |
|
1829 args.addAll(split(System.getProperty("test.java.opts"), " +")); |
|
1830 } |
|
1831 if (classpath != null) { |
|
1832 args.add("-classpath"); |
|
1833 args.add(classpath); |
|
1834 } |
|
1835 if (vmOptions != null) |
|
1836 args.addAll(vmOptions); |
|
1837 if (className != null) |
|
1838 args.add(className); |
|
1839 if (classArgs != null) |
|
1840 args.addAll(classArgs); |
|
1841 ProcessBuilder pb = getProcessBuilder(); |
|
1842 pb.command(args); |
|
1843 try { |
|
1844 return runProcess(ToolBox.this, this, pb.start()); |
|
1845 } catch (IOException | InterruptedException e) { |
|
1846 throw new Error(e); |
|
1847 } |
|
1848 } |
|
1849 } |
|
1850 |
|
1851 /** |
|
1852 * A task to configure and run a general command. |
|
1853 */ |
|
1854 public class ExecTask extends AbstractTask<ExecTask> { |
|
1855 private final String command; |
|
1856 private List<String> args; |
|
1857 |
|
1858 /** |
|
1859 * Create a task to execute a given command, to be run using {@code EXEC} mode. |
|
1860 * @param command the command to be executed |
|
1861 */ |
|
1862 public ExecTask(String command) { |
|
1863 super(Mode.EXEC); |
|
1864 this.command = command; |
|
1865 } |
|
1866 |
|
1867 /** |
|
1868 * Create a task to execute a given command, to be run using {@code EXEC} mode. |
|
1869 * @param command the command to be executed |
|
1870 */ |
|
1871 public ExecTask(Path command) { |
|
1872 super(Mode.EXEC); |
|
1873 this.command = command.toString(); |
|
1874 } |
|
1875 |
|
1876 /** |
|
1877 * Sets the arguments for the command to be executed |
|
1878 * @param args the arguments |
|
1879 * @return this task object |
|
1880 */ |
|
1881 public ExecTask args(String... args) { |
|
1882 this.args = Arrays.asList(args); |
|
1883 return this; |
|
1884 } |
|
1885 |
|
1886 /** |
|
1887 * {@inheritDoc} |
|
1888 * @return the name "exec" |
|
1889 */ |
|
1890 @Override |
|
1891 public String name() { |
|
1892 return "exec"; |
|
1893 } |
|
1894 |
|
1895 /** |
|
1896 * Calls the command with the arguments as currently configured. |
|
1897 * @return a Result object indicating the outcome of the task |
|
1898 * and the content of any output written to stdout or stderr. |
|
1899 * @throws TaskError if the outcome of the task is not as expected. |
|
1900 */ |
|
1901 @Override |
|
1902 public Result run() { |
|
1903 List<String> cmdArgs = new ArrayList<>(); |
|
1904 cmdArgs.add(command); |
|
1905 if (args != null) |
|
1906 cmdArgs.addAll(args); |
|
1907 ProcessBuilder pb = getProcessBuilder(); |
|
1908 pb.command(cmdArgs); |
|
1909 try { |
|
1910 return runProcess(ToolBox.this, this, pb.start()); |
|
1911 } catch (IOException | InterruptedException e) { |
|
1912 throw new Error(e); |
|
1913 } |
|
1914 } |
|
1915 } |
|
1916 |
|
1917 /** |
|
1918 * An in-memory Java source file. |
|
1919 * It is able to extract the file name from simple source text using |
|
1920 * regular expressions. |
|
1921 */ |
|
1922 public static class JavaSource extends SimpleJavaFileObject { |
|
1923 private final String source; |
|
1924 |
|
1925 /** |
|
1926 * Creates a in-memory file object for Java source code. |
|
1927 * @param className the name of the class |
|
1928 * @param source the source text |
|
1929 */ |
|
1930 public JavaSource(String className, String source) { |
|
1931 super(URI.create(className), JavaFileObject.Kind.SOURCE); |
|
1932 this.source = source; |
|
1933 } |
|
1934 |
|
1935 /** |
|
1936 * Creates a in-memory file object for Java source code. |
|
1937 * The name of the class will be inferred from the source code. |
|
1938 * @param source the source text |
|
1939 */ |
|
1940 public JavaSource(String source) { |
|
1941 super(URI.create(getJavaFileNameFromSource(source)), |
|
1942 JavaFileObject.Kind.SOURCE); |
|
1943 this.source = source; |
|
1944 } |
|
1945 |
|
1946 /** |
|
1947 * Writes the source code to a file in the current directory. |
|
1948 * @throws IOException if there is a problem writing the file |
|
1949 */ |
|
1950 public void write() throws IOException { |
|
1951 write(currDir); |
|
1952 } |
|
1953 |
|
1954 /** |
|
1955 * Writes the source code to a file in a specified directory. |
|
1956 * @param dir the directory |
|
1957 * @throws IOException if there is a problem writing the file |
|
1958 */ |
|
1959 public void write(Path dir) throws IOException { |
|
1960 Path file = dir.resolve(getJavaFileNameFromSource(source)); |
|
1961 Files.createDirectories(file.getParent()); |
|
1962 try (BufferedWriter out = Files.newBufferedWriter(file)) { |
|
1963 out.write(source.replace("\n", lineSeparator)); |
|
1964 } |
|
1965 } |
|
1966 |
|
1967 @Override |
|
1968 public CharSequence getCharContent(boolean ignoreEncodingErrors) { |
|
1969 return source; |
|
1970 } |
|
1971 |
|
1972 private static Pattern modulePattern = |
|
1973 Pattern.compile("module\\s+((?:\\w+\\.)*)"); |
|
1974 private static Pattern packagePattern = |
|
1975 Pattern.compile("package\\s+(((?:\\w+\\.)*)(?:\\w+))"); |
|
1976 private static Pattern classPattern = |
|
1977 Pattern.compile("(?:public\\s+)?(?:class|enum|interface)\\s+(\\w+)"); |
|
1978 |
|
1979 /** |
|
1980 * Extracts the Java file name from the class declaration. |
|
1981 * This method is intended for simple files and uses regular expressions, |
|
1982 * so comments matching the pattern can make the method fail. |
|
1983 */ |
|
1984 static String getJavaFileNameFromSource(String source) { |
|
1985 String packageName = null; |
|
1986 |
|
1987 Matcher matcher = modulePattern.matcher(source); |
|
1988 if (matcher.find()) |
|
1989 return "module-info.java"; |
|
1990 |
|
1991 matcher = packagePattern.matcher(source); |
|
1992 if (matcher.find()) |
|
1993 packageName = matcher.group(1).replace(".", "/"); |
|
1994 |
|
1995 matcher = classPattern.matcher(source); |
|
1996 if (matcher.find()) { |
|
1997 String className = matcher.group(1) + ".java"; |
|
1998 return (packageName == null) ? className : packageName + "/" + className; |
|
1999 } else if (packageName != null) { |
|
2000 return packageName + "/package-info.java"; |
|
2001 } else { |
|
2002 throw new Error("Could not extract the java class " + |
|
2003 "name from the provided source"); |
|
2004 } |
|
2005 } |
|
2006 } |
|
2007 |
|
2008 /** |
|
2009 * Extracts the Java file name from the class declaration. |
|
2010 * This method is intended for simple files and uses regular expressions, |
|
2011 * so comments matching the pattern can make the method fail. |
|
2012 * @deprecated This is a legacy method for compatibility with ToolBox v1. |
|
2013 * Use {@link JavaSource#getName JavaSource.getName} instead. |
|
2014 * @param source the source text |
|
2015 * @return the Java file name inferred from the source |
|
2016 */ |
|
2017 @Deprecated |
|
2018 public static String getJavaFileNameFromSource(String source) { |
|
2019 return JavaSource.getJavaFileNameFromSource(source); |
|
2020 } |
|
2021 |
|
2022 /** |
|
2023 * A memory file manager, for saving generated files in memory. |
|
2024 * The file manager delegates to a separate file manager for listing and |
|
2025 * reading input files. |
|
2026 */ |
|
2027 public static class MemoryFileManager extends ForwardingJavaFileManager { |
|
2028 private interface Content { |
|
2029 byte[] getBytes(); |
|
2030 String getString(); |
|
2031 } |
|
2032 |
|
2033 /** |
|
2034 * Maps binary class names to generated content. |
|
2035 */ |
|
2036 final Map<Location, Map<String, Content>> files; |
|
2037 |
|
2038 /** |
|
2039 * Construct a memory file manager which stores output files in memory, |
|
2040 * and delegates to a default file manager for input files. |
|
2041 */ |
|
2042 public MemoryFileManager() { |
|
2043 this(JavacTool.create().getStandardFileManager(null, null, null)); |
|
2044 } |
|
2045 |
|
2046 /** |
|
2047 * Construct a memory file manager which stores output files in memory, |
|
2048 * and delegates to a specified file manager for input files. |
|
2049 * @param fileManager the file manager to be used for input files |
|
2050 */ |
|
2051 public MemoryFileManager(JavaFileManager fileManager) { |
|
2052 super(fileManager); |
|
2053 files = new HashMap<>(); |
|
2054 } |
|
2055 |
|
2056 @Override |
|
2057 public JavaFileObject getJavaFileForOutput(Location location, |
|
2058 String name, |
|
2059 JavaFileObject.Kind kind, |
|
2060 FileObject sibling) |
|
2061 { |
|
2062 return new MemoryFileObject(location, name, kind); |
|
2063 } |
|
2064 |
|
2065 /** |
|
2066 * Returns the content written to a file in a given location, |
|
2067 * or null if no such file has been written. |
|
2068 * @param location the location |
|
2069 * @param name the name of the file |
|
2070 * @return the content as an array of bytes |
|
2071 */ |
|
2072 public byte[] getFileBytes(Location location, String name) { |
|
2073 Content content = getFile(location, name); |
|
2074 return (content == null) ? null : content.getBytes(); |
|
2075 } |
|
2076 |
|
2077 /** |
|
2078 * Returns the content written to a file in a given location, |
|
2079 * or null if no such file has been written. |
|
2080 * @param location the location |
|
2081 * @param name the name of the file |
|
2082 * @return the content as a string |
|
2083 */ |
|
2084 public String getFileString(Location location, String name) { |
|
2085 Content content = getFile(location, name); |
|
2086 return (content == null) ? null : content.getString(); |
|
2087 } |
|
2088 |
|
2089 private Content getFile(Location location, String name) { |
|
2090 Map<String, Content> filesForLocation = files.get(location); |
|
2091 return (filesForLocation == null) ? null : filesForLocation.get(name); |
|
2092 } |
|
2093 |
|
2094 private void save(Location location, String name, Content content) { |
|
2095 Map<String, Content> filesForLocation = files.get(location); |
|
2096 if (filesForLocation == null) |
|
2097 files.put(location, filesForLocation = new HashMap<>()); |
|
2098 filesForLocation.put(name, content); |
|
2099 } |
|
2100 |
|
2101 /** |
|
2102 * A writable file object stored in memory. |
|
2103 */ |
|
2104 private class MemoryFileObject extends SimpleJavaFileObject { |
|
2105 private final Location location; |
|
2106 private final String name; |
|
2107 |
|
2108 /** |
|
2109 * Constructs a memory file object. |
|
2110 * @param name binary name of the class to be stored in this file object |
|
2111 */ |
|
2112 MemoryFileObject(Location location, String name, JavaFileObject.Kind kind) { |
|
2113 super(URI.create("mfm:///" + name.replace('.','/') + kind.extension), |
|
2114 Kind.CLASS); |
|
2115 this.location = location; |
|
2116 this.name = name; |
|
2117 } |
|
2118 |
|
2119 @Override |
|
2120 public OutputStream openOutputStream() { |
|
2121 return new FilterOutputStream(new ByteArrayOutputStream()) { |
|
2122 @Override |
|
2123 public void close() throws IOException { |
|
2124 out.close(); |
|
2125 byte[] bytes = ((ByteArrayOutputStream) out).toByteArray(); |
|
2126 save(location, name, new Content() { |
|
2127 @Override |
|
2128 public byte[] getBytes() { |
|
2129 return bytes; |
|
2130 } |
|
2131 @Override |
|
2132 public String getString() { |
|
2133 return new String(bytes); |
|
2134 } |
|
2135 |
|
2136 }); |
|
2137 } |
|
2138 }; |
|
2139 } |
|
2140 |
|
2141 @Override |
|
2142 public Writer openWriter() { |
|
2143 return new FilterWriter(new StringWriter()) { |
|
2144 @Override |
|
2145 public void close() throws IOException { |
|
2146 out.close(); |
|
2147 String text = ((StringWriter) out).toString(); |
|
2148 save(location, name, new Content() { |
|
2149 @Override |
|
2150 public byte[] getBytes() { |
|
2151 return text.getBytes(); |
|
2152 } |
|
2153 @Override |
|
2154 public String getString() { |
|
2155 return text; |
|
2156 } |
|
2157 |
|
2158 }); |
|
2159 } |
|
2160 }; |
|
2161 } |
|
2162 } |
|
2163 |
|
2164 } |
|
2165 |
|
2166 } |
|