langtools/test/tools/lib/ToolBox.java
changeset 36778 e04318f39f92
parent 36777 28d33fb9097f
child 36779 9a030c4d2591
equal deleted inserted replaced
36777:28d33fb9097f 36778:e04318f39f92
     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 }