blob: 859765b2db1521e2395da8c2e10f88c3a170f954 [file] [log] [blame]
/*
* Copyright (c) 2013, 2014, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FilterOutputStream;
import java.io.FilterWriter;
import java.io.IOError;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.net.URI;
import java.nio.charset.Charset;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import javax.tools.FileObject;
import javax.tools.ForwardingJavaFileManager;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import javax.tools.JavaFileObject.Kind;
import javax.tools.JavaFileManager.Location;
import javax.tools.SimpleJavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.StandardLocation;
import com.sun.tools.javac.api.JavacTaskImpl;
import com.sun.tools.javac.api.JavacTool;
/**
* Utility methods and classes for writing jtreg tests for
* javac, javah, javap, and sjavac. (For javadoc support,
* see JavadocTester.)
*
* <p>There is support for common file operations similar to
* shell commands like cat, cp, diff, mv, rm, grep.
*
* <p>There is also support for invoking various tools, like
* javac, javah, javap, jar, java and other JDK tools.
*
* <p><em>File separators</em>: for convenience, many operations accept strings
* to represent filenames. On all platforms on which JDK is supported,
* "/" is a legal filename component separator. In particular, even
* on Windows, where the official file separator is "\", "/" is a legal
* alternative. It is therefore recommended that any client code using
* strings to specify filenames should use "/".
*
* @author Vicente Romero (original)
* @author Jonathan Gibbons (revised)
*/
public class ToolBox {
/** The platform line separator. */
public static final String lineSeparator = System.getProperty("line.separator");
/** The platform OS name. */
public static final String osName = System.getProperty("os.name");
/** The location of the class files for this test, or null if not set. */
public static final String testClasses = System.getProperty("test.classes");
/** The location of the source files for this test, or null if not set. */
public static final String testSrc = System.getProperty("test.src");
/** The location of the test JDK for this test, or null if not set. */
public static final String testJDK = System.getProperty("test.jdk");
/** The current directory. */
public static final Path currDir = Paths.get(".");
/** The stream used for logging output. */
public PrintStream out = System.err;
JavaCompiler compiler;
StandardJavaFileManager standardJavaFileManager;
/**
* Checks if the host OS is some version of Windows.
* @return true if the host OS is some version of Windows
*/
public boolean isWindows() {
return osName.toLowerCase(Locale.ENGLISH).startsWith("windows");
}
/**
* Splits a string around matches of the given regular expression.
* If the string is empty, an empty list will be returned.
* @param text the string to be split
* @param sep the delimiting regular expression
* @return the strings between the separators
*/
public List<String> split(String text, String sep) {
if (text.isEmpty())
return Collections.emptyList();
return Arrays.asList(text.split(sep));
}
/**
* Checks if two lists of strings are equal.
* @param l1 the first list of strings to be compared
* @param l2 the second list of strings to be compared
* @throws Error if the lists are not equal
*/
public void checkEqual(List<String> l1, List<String> l2) throws Error {
if (!Objects.equals(l1, l2)) {
// l1 and l2 cannot both be null
if (l1 == null)
throw new Error("comparison failed: l1 is null");
if (l2 == null)
throw new Error("comparison failed: l2 is null");
// report first difference
for (int i = 0; i < Math.min(l1.size(), l2.size()); i++) {
String s1 = l1.get(i);
String s2 = l1.get(i);
if (!Objects.equals(s1, s2)) {
throw new Error("comparison failed, index " + i +
", (" + s1 + ":" + s2 + ")");
}
}
throw new Error("comparison failed: l1.size=" + l1.size() + ", l2.size=" + l2.size());
}
}
/**
* Filters a list of strings according to the given regular expression.
* @param regex the regular expression
* @param lines the strings to be filtered
* @return the strings matching the regular expression
*/
public List<String> grep(String regex, List<String> lines) {
return grep(Pattern.compile(regex), lines);
}
/**
* Filters a list of strings according to the given regular expression.
* @param pattern the regular expression
* @param lines the strings to be filtered
* @return the strings matching the regular expression
*/
public List<String> grep(Pattern pattern, List<String> lines) {
return lines.stream()
.filter(s -> pattern.matcher(s).find())
.collect(Collectors.toList());
}
/**
* Copies a file.
* If the given destination exists and is a directory, the copy is created
* in that directory. Otherwise, the copy will be placed at the destination,
* possibly overwriting any existing file.
* <p>Similar to the shell "cp" command: {@code cp from to}.
* @param from the file to be copied
* @param to where to copy the file
* @throws IOException if any error occurred while copying the file
*/
public void copyFile(String from, String to) throws IOException {
copyFile(Paths.get(from), Paths.get(to));
}
/**
* Copies a file.
* If the given destination exists and is a directory, the copy is created
* in that directory. Otherwise, the copy will be placed at the destination,
* possibly overwriting any existing file.
* <p>Similar to the shell "cp" command: {@code cp from to}.
* @param from the file to be copied
* @param to where to copy the file
* @throws IOException if an error occurred while copying the file
*/
public void copyFile(Path from, Path to) throws IOException {
if (Files.isDirectory(to)) {
to = to.resolve(from.getFileName());
} else {
Files.createDirectories(to.getParent());
}
Files.copy(from, to, StandardCopyOption.REPLACE_EXISTING);
}
/**
* Creates one of more directories.
* For each of the series of paths, a directory will be created,
* including any necessary parent directories.
* <p>Similar to the shell command: {@code mkdir -p paths}.
* @param paths the directories to be created
* @throws IOException if an error occurred while creating the directories
*/
public void createDirectories(String... paths) throws IOException {
if (paths.length == 0)
throw new IllegalArgumentException("no directories specified");
for (String p : paths)
Files.createDirectories(Paths.get(p));
}
/**
* Creates one or more directories.
* For each of the series of paths, a directory will be created,
* including any necessary parent directories.
* <p>Similar to the shell command: {@code mkdir -p paths}.
* @param paths the directories to be created
* @throws IOException if an error occurred while creating the directories
*/
public void createDirectories(Path... paths) throws IOException {
if (paths.length == 0)
throw new IllegalArgumentException("no directories specified");
for (Path p : paths)
Files.createDirectories(p);
}
/**
* Deletes one or more files.
* Any directories to be deleted must be empty.
* <p>Similar to the shell command: {@code rm files}.
* @param files the files to be deleted
* @throws IOException if an error occurred while deleting the files
*/
public void deleteFiles(String... files) throws IOException {
if (files.length == 0)
throw new IllegalArgumentException("no files specified");
for (String file : files)
Files.delete(Paths.get(file));
}
/**
* Moves a file.
* If the given destination exists and is a directory, the file will be moved
* to that directory. Otherwise, the file will be moved to the destination,
* possibly overwriting any existing file.
* <p>Similar to the shell "mv" command: {@code mv from to}.
* @param from the file to be moved
* @param to where to move the file
* @throws IOException if an error occurred while moving the file
*/
public void moveFile(String from, String to) throws IOException {
moveFile(Paths.get(from), Paths.get(to));
}
/**
* Moves a file.
* If the given destination exists and is a directory, the file will be moved
* to that directory. Otherwise, the file will be moved to the destination,
* possibly overwriting any existing file.
* <p>Similar to the shell "mv" command: {@code mv from to}.
* @param from the file to be moved
* @param to where to move the file
* @throws IOException if an error occurred while moving the file
*/
public void moveFile(Path from, Path to) throws IOException {
if (Files.isDirectory(to)) {
to = to.resolve(from.getFileName());
} else {
Files.createDirectories(to.getParent());
}
Files.move(from, to, StandardCopyOption.REPLACE_EXISTING);
}
/**
* Reads the lines of a file.
* The file is read using the default character encoding.
* @param path the file to be read
* @return the lines of the file.
* @throws IOException if an error occurred while reading the file
*/
public List<String> readAllLines(String path) throws IOException {
return readAllLines(path, null);
}
/**
* Reads the lines of a file.
* The file is read using the default character encoding.
* @param path the file to be read
* @return the lines of the file.
* @throws IOException if an error occurred while reading the file
*/
public List<String> readAllLines(Path path) throws IOException {
return readAllLines(path, null);
}
/**
* Reads the lines of a file using the given encoding.
* @param path the file to be read
* @param encoding the encoding to be used to read the file
* @return the lines of the file.
* @throws IOException if an error occurred while reading the file
*/
public List<String> readAllLines(String path, String encoding) throws IOException {
return readAllLines(Paths.get(path), encoding);
}
/**
* Reads the lines of a file using the given encoding.
* @param path the file to be read
* @param encoding the encoding to be used to read the file
* @return the lines of the file.
* @throws IOException if an error occurred while reading the file
*/
public List<String> readAllLines(Path path, String encoding) throws IOException {
return Files.readAllLines(path, getCharset(encoding));
}
private Charset getCharset(String encoding) {
return (encoding == null) ? Charset.defaultCharset() : Charset.forName(encoding);
}
/**
* Writes a file containing the given content.
* Any necessary directories for the file will be created.
* @param path where to write the file
* @param content the content for the file
* @throws IOException if an error occurred while writing the file
*/
public void writeFile(String path, String content) throws IOException {
writeFile(Paths.get(path), content);
}
/**
* Writes a file containing the given content.
* Any necessary directories for the file will be created.
* @param path where to write the file
* @param content the content for the file
* @throws IOException if an error occurred while writing the file
*/
public void writeFile(Path path, String content) throws IOException {
Path dir = path.getParent();
if (dir != null)
Files.createDirectories(dir);
try (BufferedWriter w = Files.newBufferedWriter(path)) {
w.write(content);
}
}
/**
* Writes one or more files containing Java source code.
* For each file to be written, the filename will be inferred from the
* given base directory, the package declaration (if present) and from the
* the name of the first class, interface or enum declared in the file.
* <p>For example, if the base directory is /my/dir/ and the content
* contains "package p; class C { }", the file will be written to
* /my/dir/p/C.java.
* <p>Note: the content is analyzed using regular expressions;
* errors can occur if any contents have initial comments that might trip
* up the analysis.
* @param dir the base directory
* @param contents the contents of the files to be written
* @throws IOException if an error occurred while writing any of the files.
*/
public void writeJavaFiles(Path dir, String... contents) throws IOException {
if (contents.length == 0)
throw new IllegalArgumentException("no content specified for any files");
for (String c : contents) {
new JavaSource(c).write(dir);
}
}
/**
* Returns the path for the binary of a JDK tool within {@link testJDK}.
* @param tool the name of the tool
* @return the path of the tool
*/
public Path getJDKTool(String tool) {
return Paths.get(testJDK, "bin", tool);
}
/**
* Returns a string representing the contents of an {@code Iterable} as a list.
* @param <T> the type parameter of the {@code Iterable}
* @param items the iterable
* @return the string
*/
<T> String toString(Iterable<T> items) {
return StreamSupport.stream(items.spliterator(), false)
.map(Objects::toString)
.collect(Collectors.joining(",", "[", "]"));
}
/**
* The supertype for tasks.
* Complex operations are modelled by building and running a "Task" object.
* Tasks are typically configured in a fluent series of calls.
*/
public interface Task {
/**
* Returns the name of the task.
* @return the name of the task
*/
String name();
/**
* Executes the task as currently configured.
* @return a Result object containing the results of running the task
* @throws TaskError if the outcome of the task was not as expected
*/
Result run() throws TaskError;
}
/**
* Exception thrown by {@code Task.run} when the outcome is not as
* expected.
*/
public static class TaskError extends Error {
/**
* Creates a TaskError object with the given message.
* @param message the message
*/
public TaskError(String message) {
super(message);
}
}
/**
* An enum to indicate the mode a task should use it is when executed.
*/
public enum Mode {
/**
* The task should use the interface used by the command
* line launcher for the task.
* For example, for javac: com.sun.tools.javac.Main.compile
*/
CMDLINE,
/**
* The task should use a publicly defined API for the task.
* For example, for javac: javax.tools.JavaCompiler
*/
API,
/**
* The task should use the standard launcher for the task.
* For example, $JAVA_HOME/bin/javac
*/
EXEC
}
/**
* An enum to indicate the expected success or failure of executing a task.
*/
public enum Expect {
/** It is expected that the task will complete successfully. */
SUCCESS,
/** It is expected that the task will not complete successfully. */
FAIL
}
/**
* An enum to identify the streams that may be written by a {@code Task}.
*/
public enum OutputKind {
/** Identifies output written to {@code System.out} or {@code stdout}. */
STDOUT,
/** Identifies output written to {@code System.err} or {@code stderr}. */
STDERR,
/** Identifies output written to a stream provided directly to the task. */
DIRECT
};
/**
* The results from running a {@link Task}.
* The results contain the exit code returned when the tool was invoked,
* and a map containing the output written to any streams during the
* execution of the tool.
* All tools support "stdout" and "stderr".
* Tools that take an explicit PrintWriter save output written to that
* stream as "main".
*/
public class Result {
final Task task;
final int exitCode;
final Map<OutputKind, String> outputMap;
Result(Task task, int exitCode, Map<OutputKind, String> outputMap) {
this.task = task;
this.exitCode = exitCode;
this.outputMap = outputMap;
}
/**
* Returns the content of a specified stream.
* @param outputKind the kind of the selected stream
* @return the content that was written to that stream when the tool
* was executed.
*/
public String getOutput(OutputKind outputKind) {
return outputMap.get(outputKind);
}
/**
* Returns the content of a named stream as a list of lines.
* @param outputKind the kind of the selected stream
* @return the content that was written to that stream when the tool
* was executed.
*/
public List<String> getOutputLines(OutputKind outputKind) {
return Arrays.asList(outputMap.get(outputKind).split(lineSeparator));
}
/**
* Writes the content of the specified stream to the log.
* @param kind the kind of the selected stream
* @return this Result object
*/
public Result write(OutputKind kind) {
String text = getOutput(kind);
if (text == null || text.isEmpty())
out.println("[" + task.name() + ":" + kind + "]: empty");
else {
out.println("[" + task.name() + ":" + kind + "]:");
out.print(text);
}
return this;
}
/**
* Writes the content of all streams with any content to the log.
* @return this Result object
*/
public Result writeAll() {
outputMap.forEach((name, text) -> {
if (!text.isEmpty()) {
out.println("[" + name + "]:");
out.print(text);
}
});
return this;
}
}
/**
* A utility base class to simplify the implementation of tasks.
* Provides support for running the task in a process and for
* capturing output written by the task to stdout, stderr and
* other writers where applicable.
* @param <T> the implementing subclass
*/
protected static abstract class AbstractTask<T extends AbstractTask<T>> implements Task {
protected final Mode mode;
private final Map<OutputKind, String> redirects = new EnumMap<>(OutputKind.class);
private final Map<String, String> envVars = new HashMap<>();
private Expect expect = Expect.SUCCESS;
int expectedExitCode = 0;
/**
* Create a task that will execute in the specified mode.
* @param mode the mode
*/
protected AbstractTask(Mode mode) {
this.mode = mode;
}
/**
* Sets the expected outcome of the task and calls {@code run()}.
* @param expect the expected outcome
* @return the result of calling {@code run()}
*/
public Result run(Expect expect) {
expect(expect, Integer.MIN_VALUE);
return run();
}
/**
* Sets the expected outcome of the task and calls {@code run()}.
* @param expect the expected outcome
* @param exitCode the expected exit code if the expected outcome
* is {@code FAIL}
* @return the result of calling {@code run()}
*/
public Result run(Expect expect, int exitCode) {
expect(expect, exitCode);
return run();
}
/**
* Sets the expected outcome and expected exit code of the task.
* The exit code will not be checked if the outcome is
* {@code Expect.SUCCESS} or if the exit code is set to
* {@code Integer.MIN_VALUE}.
* @param expect the expected outcome
* @param exitCode the expected exit code
*/
protected void expect(Expect expect, int exitCode) {
this.expect = expect;
this.expectedExitCode = exitCode;
}
/**
* Checks the exit code contained in a {@code Result} against the
* expected outcome and exit value
* @param result the result object
* @return the result object
* @throws TaskError if the exit code stored in the result object
* does not match the expected outcome and exit code.
*/
protected Result checkExit(Result result) throws TaskError {
switch (expect) {
case SUCCESS:
if (result.exitCode != 0) {
result.writeAll();
throw new TaskError("Task " + name() + " failed: rc=" + result.exitCode);
}
break;
case FAIL:
if (result.exitCode == 0) {
result.writeAll();
throw new TaskError("Task " + name() + " succeeded unexpectedly");
}
if (expectedExitCode != Integer.MIN_VALUE
&& result.exitCode != expectedExitCode) {
result.writeAll();
throw new TaskError("Task " + name() + "failed with unexpected exit code "
+ result.exitCode + ", expected " + expectedExitCode);
}
break;
}
return result;
}
/**
* Sets an environment variable to be used by this task.
* @param name the name of the environment variable
* @param value the value for the environment variable
* @return this task object
* @throws IllegalStateException if the task mode is not {@code EXEC}
*/
protected T envVar(String name, String value) {
if (mode != Mode.EXEC)
throw new IllegalStateException();
envVars.put(name, value);
return (T) this;
}
/**
* Redirects output from an output stream to a file.
* @param outputKind the name of the stream to be redirected.
* @param path the file
* @return this task object
* @throws IllegalStateException if the task mode is not {@code EXEC}
*/
protected T redirect(OutputKind outputKind, String path) {
if (mode != Mode.EXEC)
throw new IllegalStateException();
redirects.put(outputKind, path);
return (T) this;
}
/**
* Returns a {@code ProcessBuilder} initialized with any
* redirects and environment variables that have been set.
* @return a {@code ProcessBuilder}
*/
protected ProcessBuilder getProcessBuilder() {
if (mode != Mode.EXEC)
throw new IllegalStateException();
ProcessBuilder pb = new ProcessBuilder();
if (redirects.get(OutputKind.STDOUT) != null)
pb.redirectOutput(new File(redirects.get(OutputKind.STDOUT)));
if (redirects.get(OutputKind.STDERR) != null)
pb.redirectError(new File(redirects.get(OutputKind.STDERR)));
pb.environment().putAll(envVars);
return pb;
}
/**
* Collects the output from a process and saves it in a {@code Result}.
* @param tb the {@code ToolBox} containing the task {@code t}
* @param t the task initiating the process
* @param p the process
* @return a Result object containing the output from the process and its
* exit value.
* @throws InterruptedException if the thread is interrupted
*/
protected Result runProcess(ToolBox tb, Task t, Process p) throws InterruptedException {
if (mode != Mode.EXEC)
throw new IllegalStateException();
ProcessOutput sysOut = new ProcessOutput(p.getInputStream()).start();
ProcessOutput sysErr = new ProcessOutput(p.getErrorStream()).start();
sysOut.waitUntilDone();
sysErr.waitUntilDone();
int rc = p.waitFor();
Map<OutputKind, String> outputMap = new EnumMap<>(OutputKind.class);
outputMap.put(OutputKind.STDOUT, sysOut.getOutput());
outputMap.put(OutputKind.STDERR, sysErr.getOutput());
return checkExit(tb.new Result(t, rc, outputMap));
}
/**
* Thread-friendly class to read the output from a process until the stream
* is exhausted.
*/
static class ProcessOutput implements Runnable {
ProcessOutput(InputStream from) {
in = new BufferedReader(new InputStreamReader(from));
out = new StringBuilder();
}
ProcessOutput start() {
new Thread(this).start();
return this;
}
@Override
public void run() {
try {
String line;
while ((line = in.readLine()) != null) {
out.append(line).append("\n");
}
} catch (IOException e) {
}
synchronized (this) {
done = true;
notifyAll();
}
}
synchronized void waitUntilDone() throws InterruptedException {
boolean interrupted = false;
// poll interrupted flag, while waiting for copy to complete
while (!(interrupted = Thread.interrupted()) && !done)
wait(1000);
if (interrupted)
throw new InterruptedException();
}
String getOutput() {
return out.toString();
}
private BufferedReader in;
private final StringBuilder out;
private boolean done;
}
/**
* Utility class to simplify the handling of temporarily setting a
* new stream for System.out or System.err.
*/
static class StreamOutput {
// Functional interface to set a stream.
// Expected use: System::setOut, System::setErr
private interface Initializer {
void set(PrintStream s);
}
private final ByteArrayOutputStream baos = new ByteArrayOutputStream();
private final PrintStream ps = new PrintStream(baos);
private final PrintStream prev;
private final Initializer init;
StreamOutput(PrintStream s, Initializer init) {
prev = s;
init.set(ps);
this.init = init;
}
/**
* Closes the stream and returns the contents that were written to it.
* @return the contents that were written to it.
*/
String close() {
init.set(prev);
ps.close();
return baos.toString();
}
}
/**
* Utility class to simplify the handling of creating an in-memory PrintWriter.
*/
static class WriterOutput {
private final StringWriter sw = new StringWriter();
final PrintWriter pw = new PrintWriter(sw);
/**
* Closes the stream and returns the contents that were written to it.
* @return the contents that were written to it.
*/
String close() {
pw.close();
return sw.toString();
}
}
}
/**
* A task to configure and run the Java compiler, javac.
*/
public class JavacTask extends AbstractTask<JavacTask> {
private boolean includeStandardOptions;
private String classpath;
private String sourcepath;
private String outdir;
private List<String> options;
private List<String> classes;
private List<String> files;
private List<JavaFileObject> fileObjects;
private JavaFileManager fileManager;
/**
* Creates a task to execute {@code javac} using API mode.
*/
public JavacTask() {
super(Mode.API);
}
/**
* Creates a task to execute {@code javac} in a specified mode.
* @param mode the mode to be used
*/
public JavacTask(Mode mode) {
super(mode);
}
/**
* Sets the classpath.
* @param classpath the classpath
* @return this task object
*/
public JavacTask classpath(String classpath) {
this.classpath = classpath;
return this;
}
/**
* Sets the sourcepath.
* @param sourcepath the sourcepath
* @return this task object
*/
public JavacTask sourcepath(String sourcepath) {
this.sourcepath = sourcepath;
return this;
}
/**
* Sets the output directory.
* @param outdir the output directory
* @return this task object
*/
public JavacTask outdir(String outdir) {
this.outdir = outdir;
return this;
}
/**
* Sets the options.
* @param options the options
* @return this task object
*/
public JavacTask options(String... options) {
this.options = Arrays.asList(options);
return this;
}
/**
* Sets the classes to be analyzed.
* @param classes the classes
* @return this task object
*/
public JavacTask classes(String... classes) {
this.classes = Arrays.asList(classes);
return this;
}
/**
* Sets the files to be compiled or analyzed.
* @param files the files
* @return this task object
*/
public JavacTask files(String... files) {
this.files = Arrays.asList(files);
return this;
}
/**
* Sets the files to be compiled or analyzed.
* @param files the files
* @return this task object
*/
public JavacTask files(Path... files) {
this.files = Stream.of(files)
.map(Path::toString)
.collect(Collectors.toList());
return this;
}
/**
* Sets the sources to be compiled or analyzed.
* Each source string is converted into an in-memory object that
* can be passed directly to the compiler.
* @param sources the sources
* @return this task object
*/
public JavacTask sources(String... sources) {
fileObjects = Stream.of(sources)
.map(s -> new JavaSource(s))
.collect(Collectors.toList());
return this;
}
/**
* Sets the file manager to be used by this task.
* @param fileManager the file manager
* @return this task object
*/
public JavacTask fileManager(JavaFileManager fileManager) {
this.fileManager = fileManager;
return this;
}
/**
* {@inheritDoc}
* @return the name "javac"
*/
@Override
public String name() {
return "javac";
}
/**
* Calls the compiler with the arguments as currently configured.
* @return a Result object indicating the outcome of the compilation
* and the content of any output written to stdout, stderr, or the
* main stream by the compiler.
* @throws TaskError if the outcome of the task is not as expected.
*/
@Override
public Result run() {
if (mode == Mode.EXEC)
return runExec();
WriterOutput direct = new WriterOutput();
// The following are to catch output to System.out and System.err,
// in case these are used instead of the primary (main) stream
StreamOutput sysOut = new StreamOutput(System.out, System::setOut);
StreamOutput sysErr = new StreamOutput(System.err, System::setErr);
int rc;
Map<OutputKind, String> outputMap = new HashMap<>();
try {
switch (mode == null ? Mode.API : mode) {
case API:
rc = runAPI(direct.pw);
break;
case CMDLINE:
rc = runCommand(direct.pw);
break;
default:
throw new IllegalStateException();
}
} catch (IOException e) {
out.println("Exception occurred: " + e);
rc = 99;
} finally {
outputMap.put(OutputKind.STDOUT, sysOut.close());
outputMap.put(OutputKind.STDERR, sysErr.close());
outputMap.put(OutputKind.DIRECT, direct.close());
}
return checkExit(new Result(this, rc, outputMap));
}
private int runAPI(PrintWriter pw) throws IOException {
// if (compiler == null) {
// TODO: allow this to be set externally
// compiler = ToolProvider.getSystemJavaCompiler();
compiler = JavacTool.create();
// }
if (fileManager == null)
fileManager = compiler.getStandardFileManager(null, null, null);
if (outdir != null)
setLocation(StandardLocation.CLASS_OUTPUT, toFiles(outdir));
if (classpath != null)
setLocation(StandardLocation.CLASS_PATH, toFiles(classpath));
if (sourcepath != null)
setLocation(StandardLocation.SOURCE_PATH, toFiles(sourcepath));
List<String> allOpts = new ArrayList<>();
if (options != null)
allOpts.addAll(options);
Iterable<? extends JavaFileObject> allFiles = joinFiles(files, fileObjects);
JavaCompiler.CompilationTask task = compiler.getTask(pw,
fileManager,
null, // diagnostic listener; should optionally collect diags
allOpts,
classes,
allFiles);
return ((JavacTaskImpl) task).doCall().exitCode;
}
private void setLocation(StandardLocation location, List<File> files) throws IOException {
if (!(fileManager instanceof StandardJavaFileManager))
throw new IllegalStateException("not a StandardJavaFileManager");
((StandardJavaFileManager) fileManager).setLocation(location, files);
}
private int runCommand(PrintWriter pw) {
List<String> args = getAllArgs();
String[] argsArray = args.toArray(new String[args.size()]);
return com.sun.tools.javac.Main.compile(argsArray, pw);
}
private Result runExec() {
List<String> args = new ArrayList<>();
Path javac = getJDKTool("javac");
args.add(javac.toString());
if (includeStandardOptions) {
args.addAll(split(System.getProperty("test.tool.vm.opts"), " +"));
args.addAll(split(System.getProperty("test.compiler.opts"), " +"));
}
args.addAll(getAllArgs());
String[] argsArray = args.toArray(new String[args.size()]);
ProcessBuilder pb = getProcessBuilder();
pb.command(argsArray);
try {
return runProcess(ToolBox.this, this, pb.start());
} catch (IOException | InterruptedException e) {
throw new Error(e);
}
}
private List<String> getAllArgs() {
List<String> args = new ArrayList<>();
if (options != null)
args.addAll(options);
if (outdir != null) {
args.add("-d");
args.add(outdir);
}
if (classpath != null) {
args.add("-classpath");
args.add(classpath);
}
if (sourcepath != null) {
args.add("-sourcepath");
args.add(sourcepath);
}
if (classes != null)
args.addAll(classes);
if (files != null)
args.addAll(files);
return args;
}
private List<File> toFiles(String path) {
List<File> result = new ArrayList<>();
for (String s : path.split(File.pathSeparator)) {
if (!s.isEmpty())
result.add(new File(s));
}
return result;
}
private Iterable<? extends JavaFileObject> joinFiles(
List<String> files, List<JavaFileObject> fileObjects) {
if (files == null)
return fileObjects;
if (standardJavaFileManager == null)
standardJavaFileManager = compiler.getStandardFileManager(null, null, null);
Iterable<? extends JavaFileObject> filesAsFileObjects =
standardJavaFileManager.getJavaFileObjectsFromStrings(files);
if (fileObjects == null)
return filesAsFileObjects;
List<JavaFileObject> combinedList = new ArrayList<>();
for (JavaFileObject o : filesAsFileObjects)
combinedList.add(o);
combinedList.addAll(fileObjects);
return combinedList;
}
}
/**
* A task to configure and run the native header tool, javah.
*/
public class JavahTask extends AbstractTask<JavahTask> {
private String classpath;
private List<String> options;
private List<String> classes;
/**
* Create a task to execute {@code javah} using {@code CMDLINE} mode.
*/
public JavahTask() {
super(Mode.CMDLINE);
}
/**
* Sets the classpath.
* @param classpath the classpath
* @return this task object
*/
public JavahTask classpath(String classpath) {
this.classpath = classpath;
return this;
}
/**
* Sets the options.
* @param options the options
* @return this task object
*/
public JavahTask options(String... options) {
this.options = Arrays.asList(options);
return this;
}
/**
* Sets the classes to be analyzed.
* @param classes the classes
* @return this task object
*/
public JavahTask classes(String... classes) {
this.classes = Arrays.asList(classes);
return this;
}
/**
* {@inheritDoc}
* @return the name "javah"
*/
@Override
public String name() {
return "javah";
}
/**
* Calls the javah tool with the arguments as currently configured.
* @return a Result object indicating the outcome of the task
* and the content of any output written to stdout, stderr, or the
* main stream provided to the task.
* @throws TaskError if the outcome of the task is not as expected.
*/
@Override
public Result run() {
List<String> args = new ArrayList<>();
if (options != null)
args.addAll(options);
if (classpath != null) {
args.add("-classpath");
args.add(classpath);
}
if (classes != null)
args.addAll(classes);
WriterOutput direct = new WriterOutput();
// These are to catch output to System.out and System.err,
// in case these are used instead of the primary streams
StreamOutput sysOut = new StreamOutput(System.out, System::setOut);
StreamOutput sysErr = new StreamOutput(System.err, System::setErr);
int rc;
Map<OutputKind, String> outputMap = new HashMap<>();
try {
rc = com.sun.tools.javah.Main.run(args.toArray(new String[args.size()]), direct.pw);
} finally {
outputMap.put(OutputKind.STDOUT, sysOut.close());
outputMap.put(OutputKind.STDERR, sysErr.close());
outputMap.put(OutputKind.DIRECT, direct.close());
}
return checkExit(new Result(this, rc, outputMap));
}
}
/**
* A task to configure and run the disassembler tool, javap.
*/
public class JavapTask extends AbstractTask<JavapTask> {
private String classpath;
private List<String> options;
private List<String> classes;
/**
* Create a task to execute {@code javap} using {@code CMDLINE} mode.
*/
public JavapTask() {
super(Mode.CMDLINE);
}
/**
* Sets the classpath.
* @param classpath the classpath
* @return this task object
*/
public JavapTask classpath(String classpath) {
this.classpath = classpath;
return this;
}
/**
* Sets the options.
* @param options the options
* @return this task object
*/
public JavapTask options(String... options) {
this.options = Arrays.asList(options);
return this;
}
/**
* Sets the classes to be analyzed.
* @param classes the classes
* @return this task object
*/
public JavapTask classes(String... classes) {
this.classes = Arrays.asList(classes);
return this;
}
/**
* {@inheritDoc}
* @return the name "javap"
*/
@Override
public String name() {
return "javap";
}
/**
* Calls the javap tool with the arguments as currently configured.
* @return a Result object indicating the outcome of the task
* and the content of any output written to stdout, stderr, or the
* main stream.
* @throws TaskError if the outcome of the task is not as expected.
*/
@Override
public Result run() {
List<String> args = new ArrayList<>();
if (options != null)
args.addAll(options);
if (classpath != null) {
args.add("-classpath");
args.add(classpath);
}
if (classes != null)
args.addAll(classes);
WriterOutput direct = new WriterOutput();
// These are to catch output to System.out and System.err,
// in case these are used instead of the primary streams
StreamOutput sysOut = new StreamOutput(System.out, System::setOut);
StreamOutput sysErr = new StreamOutput(System.err, System::setErr);
int rc;
Map<OutputKind, String> outputMap = new HashMap<>();
try {
rc = com.sun.tools.javap.Main.run(args.toArray(new String[args.size()]), direct.pw);
} finally {
outputMap.put(OutputKind.STDOUT, sysOut.close());
outputMap.put(OutputKind.STDERR, sysErr.close());
outputMap.put(OutputKind.DIRECT, direct.close());
}
return checkExit(new Result(this, rc, outputMap));
}
}
/**
* A task to configure and run the jar file utility.
*/
public class JarTask extends AbstractTask<JarTask> {
private Path jar;
private Manifest manifest;
private String classpath;
private String mainClass;
private Path baseDir;
private List<Path> paths;
private Set<FileObject> fileObjects;
/**
* Creates a task to write jar files, using API mode.
*/
public JarTask() {
super(Mode.API);
paths = Collections.emptyList();
fileObjects = new LinkedHashSet<>();
}
/**
* Creates a JarTask for use with a given jar file.
* @param path the file
*/
public JarTask(String path) {
this();
jar = Paths.get(path);
}
/**
* Sets a manifest for the jar file.
* @param manifest the manifest
* @return this task object
*/
public JarTask manifest(Manifest manifest) {
this.manifest = manifest;
return this;
}
/**
* Sets a manifest for the jar file.
* @param manifest a string containing the contents of the manifest
* @return this task object
* @throws IOException if there is a problem creating the manifest
*/
public JarTask manifest(String manifest) throws IOException {
this.manifest = new Manifest(new ByteArrayInputStream(manifest.getBytes()));
return this;
}
/**
* Sets the classpath to be written to the {@code Class-Path}
* entry in the manifest.
* @param classpath the classpath
* @return this task object
*/
public JarTask classpath(String classpath) {
this.classpath = classpath;
return this;
}
/**
* Sets the class to be written to the {@code Main-Class}
* entry in the manifest..
* @param mainClass the name of the main class
* @return this task object
*/
public JarTask mainClass(String mainClass) {
this.mainClass = mainClass;
return this;
}
/**
* Sets the base directory for files to be written into the jar file.
* @param baseDir the base directory
* @return this task object
*/
public JarTask baseDir(String baseDir) {
this.baseDir = Paths.get(baseDir);
return this;
}
/**
* Sets the files to be written into the jar file.
* @param files the files
* @return this task object
*/
public JarTask files(String... files) {
this.paths = Stream.of(files)
.map(file -> Paths.get(file))
.collect(Collectors.toList());
return this;
}
/**
* Adds a set of file objects to be written into the jar file, by copying them
* from a Location in a JavaFileManager.
* The file objects to be written are specified by a series of paths;
* each path can be in one of the following forms:
* <ul>
* <li>The name of a class. For example, java.lang.Object.
* In this case, the corresponding .class file will be written to the jar file.
* <li>the name of a package followed by {@code .*}. For example, {@code java.lang.*}.
* In this case, all the class files in the specified package will be written to
* the jar file.
* <li>the name of a package followed by {@code .**}. For example, {@code java.lang.**}.
* In this case, all the class files in the specified package, and any subpackages
* will be written to the jar file.
* </ul>
*
* @param fm the file manager in which to find the file objects
* @param l the location in which to find the file objects
* @param paths the paths specifying the file objects to be copied
* @return this task object
* @throws IOException if errors occur while determining the set of file objects
*/
public JarTask files(JavaFileManager fm, Location l, String... paths)
throws IOException {
for (String p : paths) {
if (p.endsWith(".**"))
addPackage(fm, l, p.substring(0, p.length() - 3), true);
else if (p.endsWith(".*"))
addPackage(fm, l, p.substring(0, p.length() - 2), false);
else
addFile(fm, l, p);
}
return this;
}
private void addPackage(JavaFileManager fm, Location l, String pkg, boolean recurse)
throws IOException {
for (JavaFileObject fo : fm.list(l, pkg, EnumSet.allOf(JavaFileObject.Kind.class), recurse)) {
fileObjects.add(fo);
}
}
private void addFile(JavaFileManager fm, Location l, String path) throws IOException {
JavaFileObject fo = fm.getJavaFileForInput(l, path, Kind.CLASS);
fileObjects.add(fo);
}
/**
* Provides limited jar command-like functionality.
* The supported commands are:
* <ul>
* <li> jar cf jarfile -C dir files...
* <li> jar cfm jarfile manifestfile -C dir files...
* </ul>
* Any values specified by other configuration methods will be ignored.
* @param args arguments in the style of those for the jar command
* @return a Result object containing the results of running the task
*/
public Result run(String... args) {
if (args.length < 2)
throw new IllegalArgumentException();
ListIterator<String> iter = Arrays.asList(args).listIterator();
String first = iter.next();
switch (first) {
case "cf":
jar = Paths.get(iter.next());
break;
case "cfm":
jar = Paths.get(iter.next());
try (InputStream in = Files.newInputStream(Paths.get(iter.next()))) {
manifest = new Manifest(in);
} catch (IOException e) {
throw new IOError(e);
}
break;
}
if (iter.hasNext()) {
if (iter.next().equals("-C"))
baseDir = Paths.get(iter.next());
else
iter.previous();
}
paths = new ArrayList<>();
while (iter.hasNext())
paths.add(Paths.get(iter.next()));
return run();
}
/**
* {@inheritDoc}
* @return the name "jar"
*/
@Override
public String name() {
return "jar";
}
/**
* Creates a jar file with the arguments as currently configured.
* @return a Result object indicating the outcome of the compilation
* and the content of any output written to stdout, stderr, or the
* main stream by the compiler.
* @throws TaskError if the outcome of the task is not as expected.
*/
@Override
public Result run() {
Manifest m = (manifest == null) ? new Manifest() : manifest;
Attributes mainAttrs = m.getMainAttributes();
if (mainClass != null)
mainAttrs.put(Attributes.Name.MAIN_CLASS, mainClass);
if (classpath != null)
mainAttrs.put(Attributes.Name.CLASS_PATH, classpath);
StreamOutput sysOut = new StreamOutput(System.out, System::setOut);
StreamOutput sysErr = new StreamOutput(System.err, System::setErr);
Map<OutputKind, String> outputMap = new HashMap<>();
try (OutputStream os = Files.newOutputStream(jar);
JarOutputStream jos = openJar(os, m)) {
writeFiles(jos);
writeFileObjects(jos);
} catch (IOException e) {
error("Exception while opening " + jar, e);
} finally {
outputMap.put(OutputKind.STDOUT, sysOut.close());
outputMap.put(OutputKind.STDERR, sysErr.close());
}
return checkExit(new Result(this, (errors == 0) ? 0 : 1, outputMap));
}
private JarOutputStream openJar(OutputStream os, Manifest m) throws IOException {
if (m == null || m.getMainAttributes().isEmpty() && m.getEntries().isEmpty()) {
return new JarOutputStream(os);
} else {
if (m.getMainAttributes().get(Attributes.Name.MANIFEST_VERSION) == null)
m.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0");
return new JarOutputStream(os, m);
}
}
private void writeFiles(JarOutputStream jos) throws IOException {
Path base = (baseDir == null) ? currDir : baseDir;
for (Path path : paths) {
Files.walkFileTree(base.resolve(path), new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
try {
String p = base.relativize(file)
.normalize()
.toString()
.replace(File.separatorChar, '/');
JarEntry e = new JarEntry(p);
jos.putNextEntry(e);
try {
jos.write(Files.readAllBytes(file));
} finally {
jos.closeEntry();
}
return FileVisitResult.CONTINUE;
} catch (IOException e) {
error("Exception while adding " + file + " to jar file", e);
return FileVisitResult.TERMINATE;
}
}
});
}
}
private void writeFileObjects(JarOutputStream jos) throws IOException {
for (FileObject fo : fileObjects) {
String p = guessPath(fo);
JarEntry e = new JarEntry(p);
jos.putNextEntry(e);
try {
byte[] buf = new byte[1024];
try (BufferedInputStream in = new BufferedInputStream(fo.openInputStream())) {
int n;
while ((n = in.read(buf)) > 0)
jos.write(buf, 0, n);
} catch (IOException ex) {
error("Exception while adding " + fo.getName() + " to jar file", ex);
}
} finally {
jos.closeEntry();
}
}
}
/*
* A jar: URL is of the form jar:URL!/entry where URL is a URL for the .jar file itself.
* In Symbol files (i.e. ct.sym) the underlying entry is prefixed META-INF/sym/<base>.
*/
private final Pattern jarEntry = Pattern.compile(".*!/(?:META-INF/sym/[^/]+/)?(.*)");
private String guessPath(FileObject fo) {
URI u = fo.toUri();
switch (u.getScheme()) {
case "jar":
Matcher m = jarEntry.matcher(u.getSchemeSpecificPart());
if (m.matches()) {
return m.group(1);
}
break;
}
throw new IllegalArgumentException(fo.getName());
}
private void error(String message, Throwable t) {
out.println("Error: " + message + ": " + t);
errors++;
}
private int errors;
}
/**
* A task to configure and run the Java launcher.
*/
public class JavaTask extends AbstractTask<JavaTask> {
boolean includeStandardOptions = true;
private String classpath;
private List<String> vmOptions;
private String className;
private List<String> classArgs;
/**
* Create a task to run the Java launcher, using {@code EXEC} mode.
*/
public JavaTask() {
super(Mode.EXEC);
}
/**
* Sets the classpath.
* @param classpath the classpath
* @return this task object
*/
public JavaTask classpath(String classpath) {
this.classpath = classpath;
return this;
}
/**
* Sets the VM options.
* @param vmOptions the options
* @return this task object
*/
public JavaTask vmOptions(String... vmOptions) {
this.vmOptions = Arrays.asList(vmOptions);
return this;
}
/**
* Sets the name of the class to be executed.
* @param className the name of the class
* @return this task object
*/
public JavaTask className(String className) {
this.className = className;
return this;
}
/**
* Sets the arguments for the class to be executed.
* @param classArgs the arguments
* @return this task object
*/
public JavaTask classArgs(String... classArgs) {
this.classArgs = Arrays.asList(classArgs);
return this;
}
/**
* Sets whether or not the standard VM and java options for the test should be passed
* to the new VM instance. If this method is not called, the default behavior is that
* the options will be passed to the new VM instance.
*
* @param includeStandardOptions whether or not the standard VM and java options for
* the test should be passed to the new VM instance.
* @return this task object
*/
public JavaTask includeStandardOptions(boolean includeStandardOptions) {
this.includeStandardOptions = includeStandardOptions;
return this;
}
/**
* {@inheritDoc}
* @return the name "java"
*/
@Override
public String name() {
return "java";
}
/**
* Calls the Java launcher with the arguments as currently configured.
* @return a Result object indicating the outcome of the task
* and the content of any output written to stdout or stderr.
* @throws TaskError if the outcome of the task is not as expected.
*/
@Override
public Result run() {
List<String> args = new ArrayList<>();
args.add(getJDKTool("java").toString());
if (includeStandardOptions) {
args.addAll(split(System.getProperty("test.vm.opts"), " +"));
args.addAll(split(System.getProperty("test.java.opts"), " +"));
}
if (classpath != null) {
args.add("-classpath");
args.add(classpath);
}
if (vmOptions != null)
args.addAll(vmOptions);
if (className != null)
args.add(className);
if (classArgs != null)
args.addAll(classArgs);
ProcessBuilder pb = getProcessBuilder();
pb.command(args);
try {
return runProcess(ToolBox.this, this, pb.start());
} catch (IOException | InterruptedException e) {
throw new Error(e);
}
}
}
/**
* A task to configure and run a general command.
*/
public class ExecTask extends AbstractTask<ExecTask> {
private final String command;
private List<String> args;
/**
* Create a task to execute a given command, to be run using {@code EXEC} mode.
* @param command the command to be executed
*/
public ExecTask(String command) {
super(Mode.EXEC);
this.command = command;
}
/**
* Create a task to execute a given command, to be run using {@code EXEC} mode.
* @param command the command to be executed
*/
public ExecTask(Path command) {
super(Mode.EXEC);
this.command = command.toString();
}
/**
* Sets the arguments for the command to be executed
* @param args the arguments
* @return this task object
*/
public ExecTask args(String... args) {
this.args = Arrays.asList(args);
return this;
}
/**
* {@inheritDoc}
* @return the name "exec"
*/
@Override
public String name() {
return "exec";
}
/**
* Calls the command with the arguments as currently configured.
* @return a Result object indicating the outcome of the task
* and the content of any output written to stdout or stderr.
* @throws TaskError if the outcome of the task is not as expected.
*/
@Override
public Result run() {
List<String> cmdArgs = new ArrayList<>();
cmdArgs.add(command);
if (args != null)
cmdArgs.addAll(args);
ProcessBuilder pb = getProcessBuilder();
pb.command(cmdArgs);
try {
return runProcess(ToolBox.this, this, pb.start());
} catch (IOException | InterruptedException e) {
throw new Error(e);
}
}
}
/**
* An in-memory Java source file.
* It is able to extract the file name from simple source text using
* regular expressions.
*/
public static class JavaSource extends SimpleJavaFileObject {
private final String source;
/**
* Creates a in-memory file object for Java source code.
* @param className the name of the class
* @param source the source text
*/
public JavaSource(String className, String source) {
super(URI.create(className), JavaFileObject.Kind.SOURCE);
this.source = source;
}
/**
* Creates a in-memory file object for Java source code.
* The name of the class will be inferred from the source code.
* @param source the source text
*/
public JavaSource(String source) {
super(URI.create(getJavaFileNameFromSource(source)),
JavaFileObject.Kind.SOURCE);
this.source = source;
}
/**
* Writes the source code to a file in the current directory.
* @throws IOException if there is a problem writing the file
*/
public void write() throws IOException {
write(currDir);
}
/**
* Writes the source code to a file in a specified directory.
* @param dir the directory
* @throws IOException if there is a problem writing the file
*/
public void write(Path dir) throws IOException {
Path file = dir.resolve(getJavaFileNameFromSource(source));
Files.createDirectories(file.getParent());
try (BufferedWriter out = Files.newBufferedWriter(file)) {
out.write(source.replace("\n", lineSeparator));
}
}
@Override
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
return source;
}
private static Pattern packagePattern =
Pattern.compile("package\\s+(((?:\\w+\\.)*)(?:\\w+))");
private static Pattern classPattern =
Pattern.compile("(?:public\\s+)?(?:class|enum|interface)\\s+(\\w+)");
/**
* Extracts the Java file name from the class declaration.
* This method is intended for simple files and uses regular expressions,
* so comments matching the pattern can make the method fail.
*/
static String getJavaFileNameFromSource(String source) {
String packageName = null;
Matcher matcher = packagePattern.matcher(source);
if (matcher.find())
packageName = matcher.group(1).replace(".", "/");
matcher = classPattern.matcher(source);
if (matcher.find()) {
String className = matcher.group(1) + ".java";
return (packageName == null) ? className : packageName + "/" + className;
} else {
throw new Error("Could not extract the java class " +
"name from the provided source");
}
}
}
/**
* Extracts the Java file name from the class declaration.
* This method is intended for simple files and uses regular expressions,
* so comments matching the pattern can make the method fail.
* @deprecated This is a legacy method for compatibility with ToolBox v1.
* Use {@link JavaSource#getName JavaSource.getName} instead.
* @param source the source text
* @return the Java file name inferred from the source
*/
@Deprecated
public static String getJavaFileNameFromSource(String source) {
return JavaSource.getJavaFileNameFromSource(source);
}
/**
* A memory file manager, for saving generated files in memory.
* The file manager delegates to a separate file manager for listing and
* reading input files.
*/
public static class MemoryFileManager extends ForwardingJavaFileManager {
private interface Content {
byte[] getBytes();
String getString();
}
/**
* Maps binary class names to generated content.
*/
final Map<Location, Map<String, Content>> files;
/**
* Construct a memory file manager which stores output files in memory,
* and delegates to a default file manager for input files.
*/
public MemoryFileManager() {
this(JavacTool.create().getStandardFileManager(null, null, null));
}
/**
* Construct a memory file manager which stores output files in memory,
* and delegates to a specified file manager for input files.
* @param fileManager the file manager to be used for input files
*/
public MemoryFileManager(JavaFileManager fileManager) {
super(fileManager);
files = new HashMap<>();
}
@Override
public JavaFileObject getJavaFileForOutput(Location location,
String name,
JavaFileObject.Kind kind,
FileObject sibling)
{
return new MemoryFileObject(location, name, kind);
}
/**
* Returns the content written to a file in a given location,
* or null if no such file has been written.
* @param location the location
* @param name the name of the file
* @return the content as an array of bytes
*/
public byte[] getFileBytes(Location location, String name) {
Content content = getFile(location, name);
return (content == null) ? null : content.getBytes();
}
/**
* Returns the content written to a file in a given location,
* or null if no such file has been written.
* @param location the location
* @param name the name of the file
* @return the content as a string
*/
public String getFileString(Location location, String name) {
Content content = getFile(location, name);
return (content == null) ? null : content.getString();
}
private Content getFile(Location location, String name) {
Map<String, Content> filesForLocation = files.get(location);
return (filesForLocation == null) ? null : filesForLocation.get(name);
}
private void save(Location location, String name, Content content) {
Map<String, Content> filesForLocation = files.get(location);
if (filesForLocation == null)
files.put(location, filesForLocation = new HashMap<>());
filesForLocation.put(name, content);
}
/**
* A writable file object stored in memory.
*/
private class MemoryFileObject extends SimpleJavaFileObject {
private final Location location;
private final String name;
/**
* Constructs a memory file object.
* @param name binary name of the class to be stored in this file object
*/
MemoryFileObject(Location location, String name, JavaFileObject.Kind kind) {
super(URI.create("mfm:///" + name.replace('.','/') + kind.extension),
Kind.CLASS);
this.location = location;
this.name = name;
}
@Override
public OutputStream openOutputStream() {
return new FilterOutputStream(new ByteArrayOutputStream()) {
@Override
public void close() throws IOException {
out.close();
byte[] bytes = ((ByteArrayOutputStream) out).toByteArray();
save(location, name, new Content() {
@Override
public byte[] getBytes() {
return bytes;
}
@Override
public String getString() {
return new String(bytes);
}
});
}
};
}
@Override
public Writer openWriter() {
return new FilterWriter(new StringWriter()) {
@Override
public void close() throws IOException {
out.close();
String text = ((StringWriter) out).toString();
save(location, name, new Content() {
@Override
public byte[] getBytes() {
return text.getBytes();
}
@Override
public String getString() {
return text;
}
});
}
};
}
}
}
}