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