Path: blob/master/test/langtools/tools/lib/toolbox/ToolBox.java
41152 views
/*1* Copyright (c) 2013, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation.7*8* This code is distributed in the hope that it will be useful, but WITHOUT9* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or10* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License11* version 2 for more details (a copy is included in the LICENSE file that12* accompanied this code).13*14* You should have received a copy of the GNU General Public License version15* 2 along with this work; if not, write to the Free Software Foundation,16* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.17*18* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA19* or visit www.oracle.com if you need additional information or have any20* questions.21*/2223package toolbox;2425import java.io.BufferedWriter;26import java.io.ByteArrayOutputStream;27import java.io.FilterOutputStream;28import java.io.FilterWriter;29import java.io.IOException;30import java.io.OutputStream;31import java.io.PrintStream;32import java.io.StringWriter;33import java.io.Writer;34import java.net.URI;35import java.nio.charset.Charset;36import java.nio.file.DirectoryNotEmptyException;37import java.nio.file.FileVisitResult;38import java.nio.file.Files;39import java.nio.file.NoSuchFileException;40import java.nio.file.Path;41import java.nio.file.Paths;42import java.nio.file.SimpleFileVisitor;43import java.nio.file.StandardCopyOption;44import java.nio.file.attribute.BasicFileAttributes;45import java.util.ArrayList;46import java.util.Arrays;47import java.util.Collection;48import java.util.Collections;49import java.util.Deque;50import java.util.HashMap;51import java.util.LinkedList;52import java.util.List;53import java.util.Locale;54import java.util.Map;55import java.util.Objects;56import java.util.Set;57import java.util.TreeSet;58import java.util.regex.Matcher;59import java.util.regex.Pattern;60import java.util.stream.Collectors;61import java.util.stream.StreamSupport;6263import javax.tools.FileObject;64import javax.tools.ForwardingJavaFileManager;65import javax.tools.JavaFileManager;66import javax.tools.JavaFileObject;67import javax.tools.JavaFileObject.Kind;68import javax.tools.JavaFileManager.Location;69import javax.tools.SimpleJavaFileObject;70import javax.tools.ToolProvider;7172/**73* Utility methods and classes for writing jtreg tests for74* javac, javah, javap, and sjavac. (For javadoc support,75* see JavadocTester.)76*77* <p>There is support for common file operations similar to78* shell commands like cat, cp, diff, mv, rm, grep.79*80* <p>There is also support for invoking various tools, like81* javac, javah, javap, jar, java and other JDK tools.82*83* <p><em>File separators</em>: for convenience, many operations accept strings84* to represent filenames. On all platforms on which JDK is supported,85* "/" is a legal filename component separator. In particular, even86* on Windows, where the official file separator is "\", "/" is a legal87* alternative. It is therefore recommended that any client code using88* strings to specify filenames should use "/".89*90* @author Vicente Romero (original)91* @author Jonathan Gibbons (revised)92*/93public class ToolBox {94/** The platform line separator. */95public static final String lineSeparator = System.getProperty("line.separator");96/** The platform OS name. */97public static final String osName = System.getProperty("os.name");9899/** The location of the class files for this test, or null if not set. */100public static final String testClasses = System.getProperty("test.classes");101/** The location of the source files for this test, or null if not set. */102public static final String testSrc = System.getProperty("test.src");103/** The location of the test JDK for this test, or null if not set. */104public static final String testJDK = System.getProperty("test.jdk");105/** The timeout factor for slow systems. */106public static final float timeoutFactor;107static {108String ttf = System.getProperty("test.timeout.factor");109timeoutFactor = (ttf == null) ? 1.0f : Float.valueOf(ttf);110}111112/** The current directory. */113public static final Path currDir = Paths.get(".");114115/** The stream used for logging output. */116public PrintStream out = System.err;117118/**119* Checks if the host OS is some version of Windows.120* @return true if the host OS is some version of Windows121*/122public static boolean isWindows() {123return osName.toLowerCase(Locale.ENGLISH).startsWith("windows");124}125126/**127* Splits a string around matches of the given regular expression.128* If the string is empty, an empty list will be returned.129* @param text the string to be split130* @param sep the delimiting regular expression131* @return the strings between the separators132*/133public List<String> split(String text, String sep) {134if (text.isEmpty())135return Collections.emptyList();136return Arrays.asList(text.split(sep));137}138139/**140* Checks if two lists of strings are equal.141* @param l1 the first list of strings to be compared142* @param l2 the second list of strings to be compared143* @throws Error if the lists are not equal144*/145public void checkEqual(List<String> l1, List<String> l2) throws Error {146if (!Objects.equals(l1, l2)) {147// l1 and l2 cannot both be null148if (l1 == null)149throw new Error("comparison failed: l1 is null");150if (l2 == null)151throw new Error("comparison failed: l2 is null");152// report first difference153for (int i = 0; i < Math.min(l1.size(), l2.size()); i++) {154String s1 = l1.get(i);155String s2 = l2.get(i);156if (!Objects.equals(s1, s2)) {157throw new Error("comparison failed, index " + i +158", (" + s1 + ":" + s2 + ")");159}160}161throw new Error("comparison failed: l1.size=" + l1.size() + ", l2.size=" + l2.size());162}163}164165/**166* Filters a list of strings according to the given regular expression,167* returning the strings that match the regular expression.168* @param regex the regular expression169* @param lines the strings to be filtered170* @return the strings matching the regular expression171*/172public List<String> grep(String regex, List<String> lines) {173return grep(Pattern.compile(regex), lines, true);174}175176/**177* Filters a list of strings according to the given regular expression,178* returning the strings that match the regular expression.179* @param pattern the regular expression180* @param lines the strings to be filtered181* @return the strings matching the regular expression182*/183public List<String> grep(Pattern pattern, List<String> lines) {184return grep(pattern, lines, true);185}186187/**188* Filters a list of strings according to the given regular expression,189* returning either the strings that match or the strings that do not match.190* @param regex the regular expression191* @param lines the strings to be filtered192* @param match if true, return the lines that match; otherwise if false, return the lines that do not match.193* @return the strings matching(or not matching) the regular expression194*/195public List<String> grep(String regex, List<String> lines, boolean match) {196return grep(Pattern.compile(regex), lines, match);197}198199/**200* Filters a list of strings according to the given regular expression,201* returning either the strings that match or the strings that do not match.202* @param pattern the regular expression203* @param lines the strings to be filtered204* @param match if true, return the lines that match; otherwise if false, return the lines that do not match.205* @return the strings matching(or not matching) the regular expression206*/207public List<String> grep(Pattern pattern, List<String> lines, boolean match) {208return lines.stream()209.filter(s -> pattern.matcher(s).find() == match)210.collect(Collectors.toList());211}212213/**214* Copies a file.215* If the given destination exists and is a directory, the copy is created216* in that directory. Otherwise, the copy will be placed at the destination,217* possibly overwriting any existing file.218* <p>Similar to the shell "cp" command: {@code cp from to}.219* @param from the file to be copied220* @param to where to copy the file221* @throws IOException if any error occurred while copying the file222*/223public void copyFile(String from, String to) throws IOException {224copyFile(Paths.get(from), Paths.get(to));225}226227/**228* Copies a file.229* If the given destination exists and is a directory, the copy is created230* in that directory. Otherwise, the copy will be placed at the destination,231* possibly overwriting any existing file.232* <p>Similar to the shell "cp" command: {@code cp from to}.233* @param from the file to be copied234* @param to where to copy the file235* @throws IOException if an error occurred while copying the file236*/237public void copyFile(Path from, Path to) throws IOException {238if (Files.isDirectory(to)) {239to = to.resolve(from.getFileName());240} else {241Files.createDirectories(to.getParent());242}243Files.copy(from, to, StandardCopyOption.REPLACE_EXISTING);244}245246/**247* Creates one of more directories.248* For each of the series of paths, a directory will be created,249* including any necessary parent directories.250* <p>Similar to the shell command: {@code mkdir -p paths}.251* @param paths the directories to be created252* @throws IOException if an error occurred while creating the directories253*/254public void createDirectories(String... paths) throws IOException {255if (paths.length == 0)256throw new IllegalArgumentException("no directories specified");257for (String p : paths)258Files.createDirectories(Paths.get(p));259}260261/**262* Creates one or more directories.263* For each of the series of paths, a directory will be created,264* including any necessary parent directories.265* <p>Similar to the shell command: {@code mkdir -p paths}.266* @param paths the directories to be created267* @throws IOException if an error occurred while creating the directories268*/269public void createDirectories(Path... paths) throws IOException {270if (paths.length == 0)271throw new IllegalArgumentException("no directories specified");272for (Path p : paths)273Files.createDirectories(p);274}275276/**277* Deletes one or more files, awaiting confirmation that the files278* no longer exist. Any directories to be deleted must be empty.279* <p>Similar to the shell command: {@code rm files}.280* @param files the names of the files to be deleted281* @throws IOException if an error occurred while deleting the files282*/283public void deleteFiles(String... files) throws IOException {284deleteFiles(List.of(files).stream().map(Paths::get).collect(Collectors.toList()));285}286287/**288* Deletes one or more files, awaiting confirmation that the files289* no longer exist. Any directories to be deleted must be empty.290* <p>Similar to the shell command: {@code rm files}.291* @param paths the paths for the files to be deleted292* @throws IOException if an error occurred while deleting the files293*/294public void deleteFiles(Path... paths) throws IOException {295deleteFiles(List.of(paths));296}297298/**299* Deletes one or more files, awaiting confirmation that the files300* no longer exist. Any directories to be deleted must be empty.301* <p>Similar to the shell command: {@code rm files}.302* @param paths the paths for the files to be deleted303* @throws IOException if an error occurred while deleting the files304*/305public void deleteFiles(List<Path> paths) throws IOException {306if (paths.isEmpty())307throw new IllegalArgumentException("no files specified");308IOException ioe = null;309for (Path path : paths) {310ioe = deleteFile(path, ioe);311}312if (ioe != null) {313throw ioe;314}315ensureDeleted(paths);316}317318/**319* Deletes all content of a directory (but not the directory itself),320* awaiting confirmation that the content has been deleted.321* @param root the directory to be cleaned322* @throws IOException if an error occurs while cleaning the directory323*/324public void cleanDirectory(Path root) throws IOException {325if (!Files.isDirectory(root)) {326throw new IOException(root + " is not a directory");327}328Files.walkFileTree(root, new SimpleFileVisitor<Path>() {329private IOException ioe = null;330// for each directory we visit, maintain a list of the files that we try to delete331private Deque<List<Path>> dirFiles = new LinkedList<>();332333@Override334public FileVisitResult visitFile(Path file, BasicFileAttributes a) throws IOException {335ioe = deleteFile(file, ioe);336dirFiles.peekFirst().add(file);337return FileVisitResult.CONTINUE;338}339340@Override341public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes a) throws IOException {342if (!dir.equals(root)) {343dirFiles.peekFirst().add(dir);344}345dirFiles.addFirst(new ArrayList<>());346return FileVisitResult.CONTINUE;347}348349@Override350public FileVisitResult postVisitDirectory(Path dir, IOException e) throws IOException {351if (e != null) {352throw e;353}354if (ioe != null) {355throw ioe;356}357ensureDeleted(dirFiles.removeFirst());358if (!dir.equals(root)) {359ioe = deleteFile(dir, ioe);360}361return FileVisitResult.CONTINUE;362}363});364}365366/**367* Internal method to delete a file, using {@code Files.delete}.368* It does not wait to confirm deletion, nor does it retry.369* If an exception occurs it is either returned or added to the set of370* suppressed exceptions for an earlier exception.371* @param path the path for the file to be deleted372* @param ioe the earlier exception, or null373* @return the earlier exception or an exception that occurred while374* trying to delete the file375*/376private IOException deleteFile(Path path, IOException ioe) {377try {378Files.delete(path);379} catch (IOException e) {380if (ioe == null) {381ioe = e;382} else {383ioe.addSuppressed(e);384}385}386return ioe;387}388389/**390* Wait until it is confirmed that a set of files have been deleted.391* @param paths the paths for the files to be deleted392* @throws IOException if a file has not been deleted393*/394private void ensureDeleted(Collection<Path> paths)395throws IOException {396for (Path path : paths) {397ensureDeleted(path);398}399}400401/**402* Wait until it is confirmed that a file has been deleted.403* @param path the path for the file to be deleted404* @throws IOException if problems occur while deleting the file405*/406private void ensureDeleted(Path path) throws IOException {407long startTime = System.currentTimeMillis();408do {409// Note: Files.notExists is not the same as !Files.exists410if (Files.notExists(path)) {411return;412}413System.gc(); // allow finalizers and cleaners to run414try {415Thread.sleep(RETRY_DELETE_MILLIS);416} catch (InterruptedException e) {417throw new IOException("Interrupted while waiting for file to be deleted: " + path, e);418}419} while ((System.currentTimeMillis() - startTime) <= MAX_RETRY_DELETE_MILLIS);420421throw new IOException("File not deleted: " + path);422}423424private static final int RETRY_DELETE_MILLIS = isWindows() ? (int)(500 * timeoutFactor): 0;425private static final int MAX_RETRY_DELETE_MILLIS = isWindows() ? (int)(15 * 1000 * timeoutFactor) : 0;426427/**428* Moves a file.429* If the given destination exists and is a directory, the file will be moved430* to that directory. Otherwise, the file will be moved to the destination,431* possibly overwriting any existing file.432* <p>Similar to the shell "mv" command: {@code mv from to}.433* @param from the file to be moved434* @param to where to move the file435* @throws IOException if an error occurred while moving the file436*/437public void moveFile(String from, String to) throws IOException {438moveFile(Paths.get(from), Paths.get(to));439}440441/**442* Moves a file.443* If the given destination exists and is a directory, the file will be moved444* to that directory. Otherwise, the file will be moved to the destination,445* possibly overwriting any existing file.446* <p>Similar to the shell "mv" command: {@code mv from to}.447* @param from the file to be moved448* @param to where to move the file449* @throws IOException if an error occurred while moving the file450*/451public void moveFile(Path from, Path to) throws IOException {452if (Files.isDirectory(to)) {453to = to.resolve(from.getFileName());454} else {455Files.createDirectories(to.getParent());456}457Files.move(from, to, StandardCopyOption.REPLACE_EXISTING);458}459460/**461* Reads the lines of a file.462* The file is read using the default character encoding.463* @param path the file to be read464* @return the lines of the file465* @throws IOException if an error occurred while reading the file466*/467public List<String> readAllLines(String path) throws IOException {468return readAllLines(path, null);469}470471/**472* Reads the lines of a file.473* The file is read using the default character encoding.474* @param path the file to be read475* @return the lines of the file476* @throws IOException if an error occurred while reading the file477*/478public List<String> readAllLines(Path path) throws IOException {479return readAllLines(path, null);480}481482/**483* Reads the lines of a file using the given encoding.484* @param path the file to be read485* @param encoding the encoding to be used to read the file486* @return the lines of the file.487* @throws IOException if an error occurred while reading the file488*/489public List<String> readAllLines(String path, String encoding) throws IOException {490return readAllLines(Paths.get(path), encoding);491}492493/**494* Reads the lines of a file using the given encoding.495* @param path the file to be read496* @param encoding the encoding to be used to read the file497* @return the lines of the file498* @throws IOException if an error occurred while reading the file499*/500public List<String> readAllLines(Path path, String encoding) throws IOException {501return Files.readAllLines(path, getCharset(encoding));502}503504private Charset getCharset(String encoding) {505return (encoding == null) ? Charset.defaultCharset() : Charset.forName(encoding);506}507508/**509* Find .java files in one or more directories.510* <p>Similar to the shell "find" command: {@code find paths -name \*.java}.511* @param paths the directories in which to search for .java files512* @return the .java files found513* @throws IOException if an error occurred while searching for files514*/515public Path[] findJavaFiles(Path... paths) throws IOException {516return findFiles(".java", paths);517}518519/**520* Find files matching the file extension, in one or more directories.521* <p>Similar to the shell "find" command: {@code find paths -name \*.ext}.522* @param fileExtension the extension to search for523* @param paths the directories in which to search for files524* @return the files matching the file extension525* @throws IOException if an error occurred while searching for files526*/527public Path[] findFiles(String fileExtension, Path... paths) throws IOException {528Set<Path> files = new TreeSet<>(); // use TreeSet to force a consistent order529for (Path p : paths) {530Files.walkFileTree(p, new SimpleFileVisitor<Path>() {531@Override532public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)533throws IOException {534if (file.getFileName().toString().endsWith(fileExtension)) {535files.add(file);536}537return FileVisitResult.CONTINUE;538}539});540}541return files.toArray(new Path[files.size()]);542}543544/**545* Writes a file containing the given content.546* Any necessary directories for the file will be created.547* @param path where to write the file548* @param content the content for the file549* @throws IOException if an error occurred while writing the file550*/551public void writeFile(String path, String content) throws IOException {552writeFile(Paths.get(path), content);553}554555/**556* Writes a file containing the given content.557* Any necessary directories for the file will be created.558* @param path where to write the file559* @param content the content for the file560* @throws IOException if an error occurred while writing the file561*/562public void writeFile(Path path, String content) throws IOException {563Path dir = path.getParent();564if (dir != null)565Files.createDirectories(dir);566try (BufferedWriter w = Files.newBufferedWriter(path)) {567w.write(content);568}569}570571/**572* Writes one or more files containing Java source code.573* For each file to be written, the filename will be inferred from the574* given base directory, the package declaration (if present) and from the575* the name of the first class, interface or enum declared in the file.576* <p>For example, if the base directory is /my/dir/ and the content577* contains "package p; class C { }", the file will be written to578* /my/dir/p/C.java.579* <p>Note: the content is analyzed using regular expressions;580* errors can occur if any contents have initial comments that might trip581* up the analysis.582* @param dir the base directory583* @param contents the contents of the files to be written584* @throws IOException if an error occurred while writing any of the files.585*/586public void writeJavaFiles(Path dir, String... contents) throws IOException {587if (contents.length == 0)588throw new IllegalArgumentException("no content specified for any files");589for (String c : contents) {590new JavaSource(c).write(dir);591}592}593594/**595* Returns the path for the binary of a JDK tool within {@link testJDK}.596* @param tool the name of the tool597* @return the path of the tool598*/599public Path getJDKTool(String tool) {600return Paths.get(testJDK, "bin", tool);601}602603/**604* Returns a string representing the contents of an {@code Iterable} as a list.605* @param <T> the type parameter of the {@code Iterable}606* @param items the iterable607* @return the string608*/609<T> String toString(Iterable<T> items) {610return StreamSupport.stream(items.spliterator(), false)611.map(Objects::toString)612.collect(Collectors.joining(",", "[", "]"));613}614615616/**617* An in-memory Java source file.618* It is able to extract the file name from simple source text using619* regular expressions.620*/621public static class JavaSource extends SimpleJavaFileObject {622private final String source;623624/**625* Creates a in-memory file object for Java source code.626* @param className the name of the class627* @param source the source text628*/629public JavaSource(String className, String source) {630super(URI.create(className), JavaFileObject.Kind.SOURCE);631this.source = source;632}633634/**635* Creates a in-memory file object for Java source code.636* The name of the class will be inferred from the source code.637* @param source the source text638*/639public JavaSource(String source) {640super(URI.create(getJavaFileNameFromSource(source)),641JavaFileObject.Kind.SOURCE);642this.source = source;643}644645/**646* Writes the source code to a file in the current directory.647* @throws IOException if there is a problem writing the file648*/649public void write() throws IOException {650write(currDir);651}652653/**654* Writes the source code to a file in a specified directory.655* @param dir the directory656* @throws IOException if there is a problem writing the file657*/658public void write(Path dir) throws IOException {659Path file = dir.resolve(getJavaFileNameFromSource(source));660Files.createDirectories(file.getParent());661try (BufferedWriter out = Files.newBufferedWriter(file)) {662out.write(source.replace("\n", lineSeparator));663}664}665666@Override667public CharSequence getCharContent(boolean ignoreEncodingErrors) {668return source;669}670671private static Pattern commentPattern =672Pattern.compile("(?s)(\\s+//.*?\n|/\\*.*?\\*/)");673private static Pattern modulePattern =674Pattern.compile("module\\s+((?:\\w+\\.)*)");675private static Pattern packagePattern =676Pattern.compile("package\\s+(((?:\\w+\\.)*)(?:\\w+))");677private static Pattern classPattern =678Pattern.compile("(?:public\\s+)?(?:class|enum|interface|record)\\s+(\\w+)");679680/**681* Extracts the Java file name from the class declaration.682* This method is intended for simple files and uses regular expressions.683* Comments in the source are stripped before looking for the684* declarations from which the name is derived.685*/686static String getJavaFileNameFromSource(String source) {687StringBuilder sb = new StringBuilder();688Matcher matcher = commentPattern.matcher(source);689int start = 0;690while (matcher.find()) {691sb.append(source.substring(start, matcher.start()));692start = matcher.end();693}694sb.append(source.substring(start));695source = sb.toString();696697String packageName = null;698699matcher = modulePattern.matcher(source);700if (matcher.find())701return "module-info.java";702703matcher = packagePattern.matcher(source);704if (matcher.find()) {705packageName = matcher.group(1).replace(".", "/");706validateName(packageName);707}708709matcher = classPattern.matcher(source);710if (matcher.find()) {711String className = matcher.group(1) + ".java";712validateName(className);713return (packageName == null) ? className : packageName + "/" + className;714} else if (packageName != null) {715return packageName + "/package-info.java";716} else {717throw new Error("Could not extract the java class " +718"name from the provided source");719}720}721}722723/**724* Extracts the Java file name from the class declaration.725* This method is intended for simple files and uses regular expressions,726* so comments matching the pattern can make the method fail.727* @deprecated This is a legacy method for compatibility with ToolBox v1.728* Use {@link JavaSource#getName JavaSource.getName} instead.729* @param source the source text730* @return the Java file name inferred from the source731*/732@Deprecated733public static String getJavaFileNameFromSource(String source) {734return JavaSource.getJavaFileNameFromSource(source);735}736737private static final Set<String> RESERVED_NAMES = Set.of(738"con", "prn", "aux", "nul",739"com1", "com2", "com3", "com4", "com5", "com6", "com7", "com8", "com9",740"lpt1", "lpt2", "lpt3", "lpt4", "lpt5", "lpt6", "lpt7", "lpt8", "lpt9"741);742743/**Validate if a given name is a valid file name744* or path name on known platforms.745*/746public static void validateName(String name) {747for (String part : name.split("\\.|/|\\\\")) {748if (RESERVED_NAMES.contains(part.toLowerCase(Locale.US))) {749throw new IllegalArgumentException("Name: " + name + " is" +750"a reserved name on Windows, " +751"and will not work!");752}753}754}755/**756* A memory file manager, for saving generated files in memory.757* The file manager delegates to a separate file manager for listing and758* reading input files.759*/760public static class MemoryFileManager extends ForwardingJavaFileManager {761private interface Content {762byte[] getBytes();763String getString();764}765766/**767* Maps binary class names to generated content.768*/769private final Map<Location, Map<String, Content>> files;770771/**772* Construct a memory file manager which stores output files in memory,773* and delegates to a default file manager for input files.774*/775public MemoryFileManager() {776this(ToolProvider.getSystemJavaCompiler().getStandardFileManager(null, null, null));777}778779/**780* Construct a memory file manager which stores output files in memory,781* and delegates to a specified file manager for input files.782* @param fileManager the file manager to be used for input files783*/784public MemoryFileManager(JavaFileManager fileManager) {785super(fileManager);786files = new HashMap<>();787}788789@Override790public JavaFileObject getJavaFileForOutput(Location location,791String name,792JavaFileObject.Kind kind,793FileObject sibling)794{795return new MemoryFileObject(location, name, kind);796}797798/**799* Returns the set of names of files that have been written to a given800* location.801* @param location the location802* @return the set of file names803*/804public Set<String> getFileNames(Location location) {805Map<String, Content> filesForLocation = files.get(location);806return (filesForLocation == null)807? Collections.emptySet() : filesForLocation.keySet();808}809810/**811* Returns the content written to a file in a given location,812* or null if no such file has been written.813* @param location the location814* @param name the name of the file815* @return the content as an array of bytes816*/817public byte[] getFileBytes(Location location, String name) {818Content content = getFile(location, name);819return (content == null) ? null : content.getBytes();820}821822/**823* Returns the content written to a file in a given location,824* or null if no such file has been written.825* @param location the location826* @param name the name of the file827* @return the content as a string828*/829public String getFileString(Location location, String name) {830Content content = getFile(location, name);831return (content == null) ? null : content.getString();832}833834private Content getFile(Location location, String name) {835Map<String, Content> filesForLocation = files.get(location);836return (filesForLocation == null) ? null : filesForLocation.get(name);837}838839private void save(Location location, String name, Content content) {840Map<String, Content> filesForLocation = files.get(location);841if (filesForLocation == null)842files.put(location, filesForLocation = new HashMap<>());843filesForLocation.put(name, content);844}845846/**847* A writable file object stored in memory.848*/849private class MemoryFileObject extends SimpleJavaFileObject {850private final Location location;851private final String name;852853/**854* Constructs a memory file object.855* @param name binary name of the class to be stored in this file object856*/857MemoryFileObject(Location location, String name, JavaFileObject.Kind kind) {858super(URI.create("mfm:///" + name.replace('.','/') + kind.extension),859Kind.CLASS);860this.location = location;861this.name = name;862}863864@Override865public OutputStream openOutputStream() {866return new FilterOutputStream(new ByteArrayOutputStream()) {867@Override868public void close() throws IOException {869out.close();870byte[] bytes = ((ByteArrayOutputStream) out).toByteArray();871save(location, name, new Content() {872@Override873public byte[] getBytes() {874return bytes;875}876@Override877public String getString() {878return new String(bytes);879}880881});882}883};884}885886@Override887public Writer openWriter() {888return new FilterWriter(new StringWriter()) {889@Override890public void close() throws IOException {891out.close();892String text = ((StringWriter) out).toString();893save(location, name, new Content() {894@Override895public byte[] getBytes() {896return text.getBytes();897}898@Override899public String getString() {900return text;901}902903});904}905};906}907}908}909}910911912913