/* * Copyright (c) 2013, 2022, 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.
*/
/** * Utility methods and classes for writing jtreg tests for * javac, javah, and javap. (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)
*/ publicclass ToolBox { /** The platform line separator. */ publicstaticfinal String lineSeparator = System.getProperty("line.separator"); /** The platform OS name. */ publicstaticfinal String osName = System.getProperty("os.name");
/** The location of the class files for this test, or null if not set. */ publicstaticfinal String testClasses = System.getProperty("test.classes"); /** The location of the source files for this test, or null if not set. */ publicstaticfinal String testSrc = System.getProperty("test.src"); /** The location of the test JDK for this test, or null if not set. */ publicstaticfinal String testJDK = System.getProperty("test.jdk"); /** The timeout factor for slow systems. */ publicstaticfinalfloat timeoutFactor; static {
String ttf = System.getProperty("test.timeout.factor");
timeoutFactor = (ttf == null) ? 1.0f : Float.parseFloat(ttf);
}
/** The current directory. */ publicstaticfinal Path currDir = Path.of(".");
/** The stream used for logging output. */ public PrintStream out = System.err;
/** * Checks if the host OS is some version of Windows. * @return true if the host OS is some version of Windows
*/ publicstaticboolean 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
*/ publicvoid checkEqual(List<String> l1, List<String> l2) throws Error { if (!Objects.equals(l1, l2)) { // l1 and l2 cannot both be null if (l1 == null) thrownew Error("comparison failed: l1 is null"); if (l2 == null) thrownew 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 = l2.get(i); if (!Objects.equals(s1, s2)) { thrownew Error("comparison failed, index " + i + ", (" + s1 + ":" + s2 + ")");
}
} thrownew Error("comparison failed: l1.size=" + l1.size() + ", l2.size=" + l2.size());
}
}
/** * Filters a list of strings according to the given regular expression, * returning the strings that match the 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, true);
}
/** * Filters a list of strings according to the given regular expression, * returning the strings that match the 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 grep(pattern, lines, true);
}
/** * Filters a list of strings according to the given regular expression, * returning either the strings that match or the strings that do not match. * * @param regex the regular expression * @param lines the strings to be filtered * @param match if true, return the lines that match; otherwise if false, return the lines that do not match. * @return the strings matching(or not matching) the regular expression
*/ public List<String> grep(String regex, List<String> lines, boolean match) { return grep(Pattern.compile(regex), lines, match);
}
/** * Filters a list of strings according to the given regular expression, * returning either the strings that match or the strings that do not match. * * @param pattern the regular expression * @param lines the strings to be filtered * @param match if true, return the lines that match; otherwise if false, return the lines that do not match. * @return the strings matching(or not matching) the regular expression
*/ public List<String> grep(Pattern pattern, List<String> lines, boolean match) { return lines.stream()
.filter(s -> pattern.matcher(s).find() == match)
.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
*/ publicvoid copyFile(String from, String to) throws IOException {
copyFile(Path.of(from), Path.of(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
*/ publicvoid copyFile(Path from, Path to) throws IOException { if (Files.isDirectory(to)) {
to = to.resolve(from.getFileName());
} elseif (to.getParent() != null) {
Files.createDirectories(to.getParent());
}
Files.copy(from, to, StandardCopyOption.REPLACE_EXISTING);
}
/** * Copies the contents of a directory to another directory. * <p>Similar to the shell command: {@code rsync fromDir/ toDir/}. * * @param fromDir the directory containing the files to be copied * @param toDir the destination to which to copy the files
*/ publicvoid copyDir(String fromDir, String toDir) {
copyDir(Path.of(fromDir), Path.of(toDir));
}
/** * Copies the contents of a directory to another directory. * The destination direction should not already exist. * <p>Similar to the shell command: {@code rsync fromDir/ toDir/}. * * @param fromDir the directory containing the files to be copied * @param toDir the destination to which to copy the files
*/ publicvoid copyDir(Path fromDir, Path toDir) { try { if (toDir.getParent() != null) {
Files.createDirectories(toDir.getParent());
}
Files.walkFileTree(fromDir, new SimpleFileVisitor<Path>() {
@Override public FileVisitResult preVisitDirectory(Path fromSubdir, BasicFileAttributes attrs) throws IOException {
Files.copy(fromSubdir, toDir.resolve(fromDir.relativize(fromSubdir))); return FileVisitResult.CONTINUE;
}
/** * 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
*/ publicvoid createDirectories(String... paths) throws IOException { if (paths.length == 0) thrownew IllegalArgumentException("no directories specified"); for (String p : paths)
Files.createDirectories(Path.of(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
*/ publicvoid createDirectories(Path... paths) throws IOException { if (paths.length == 0) thrownew IllegalArgumentException("no directories specified"); for (Path p : paths)
Files.createDirectories(p);
}
/** * Deletes one or more files, awaiting confirmation that the files * no longer exist. Any directories to be deleted must be empty. * <p>Similar to the shell command: {@code rm files}. * * @param files the names of the files to be deleted * @throws IOException if an error occurred while deleting the files
*/ publicvoid deleteFiles(String... files) throws IOException {
deleteFiles(List.of(files).stream().map(Paths::get).collect(Collectors.toList()));
}
/** * Deletes one or more files, awaiting confirmation that the files * no longer exist. Any directories to be deleted must be empty. * <p>Similar to the shell command: {@code rm files}. * * @param paths the paths for the files to be deleted * @throws IOException if an error occurred while deleting the files
*/ publicvoid deleteFiles(Path... paths) throws IOException {
deleteFiles(List.of(paths));
}
/** * Deletes one or more files, awaiting confirmation that the files * no longer exist. Any directories to be deleted must be empty. * <p>Similar to the shell command: {@code rm files}. * * @param paths the paths for the files to be deleted * @throws IOException if an error occurred while deleting the files
*/ publicvoid deleteFiles(List<Path> paths) throws IOException { if (paths.isEmpty()) thrownew IllegalArgumentException("no files specified");
IOException ioe = null; for (Path path : paths) {
ioe = deleteFile(path, ioe);
} if (ioe != null) { throw ioe;
}
ensureDeleted(paths);
}
/** * Deletes all content of a directory (but not the directory itself), * awaiting confirmation that the content has been deleted. * * @param root the directory to be cleaned * @throws IOException if an error occurs while cleaning the directory
*/ publicvoid cleanDirectory(Path root) throws IOException { if (!Files.isDirectory(root)) { thrownew IOException(root + " is not a directory");
}
Files.walkFileTree(root, new SimpleFileVisitor<>() { private IOException ioe = null; // for each directory we visit, maintain a list of the files that we try to delete privatefinal Deque<List<Path>> dirFiles = new LinkedList<>();
@Override public FileVisitResult visitFile(Path file, BasicFileAttributes a) {
ioe = deleteFile(file, ioe);
dirFiles.peekFirst().add(file); return FileVisitResult.CONTINUE;
}
@Override public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes a) { if (!dir.equals(root)) {
dirFiles.peekFirst().add(dir);
}
dirFiles.addFirst(new ArrayList<>()); return FileVisitResult.CONTINUE;
}
@Override public FileVisitResult postVisitDirectory(Path dir, IOException e) throws IOException { if (e != null) { throw e;
} if (ioe != null) { throw ioe;
}
ensureDeleted(dirFiles.removeFirst()); if (!dir.equals(root)) {
ioe = deleteFile(dir, ioe);
} return FileVisitResult.CONTINUE;
}
});
}
/** * Internal method to delete a file, using {@code Files.delete}. * It does not wait to confirm deletion, nor does it retry. * If an exception occurs it is either returned or added to the set of * suppressed exceptions for an earlier exception. * * @param path the path for the file to be deleted * @param ioe the earlier exception, or null * @return the earlier exception or an exception that occurred while * trying to delete the file
*/ private IOException deleteFile(Path path, IOException ioe) { try {
Files.delete(path);
} catch (IOException e) { if (ioe == null) {
ioe = e;
} else {
ioe.addSuppressed(e);
}
} return ioe;
}
/** * Wait until it is confirmed that a set of files have been deleted. * * @param paths the paths for the files to be deleted * @throws IOException if a file has not been deleted
*/ privatevoid ensureDeleted(Collection<Path> paths) throws IOException { for (Path path : paths) {
ensureDeleted(path);
}
}
/** * Wait until it is confirmed that a file has been deleted. * * @param path the path for the file to be deleted * @throws IOException if problems occur while deleting the file
*/ privatevoid ensureDeleted(Path path) throws IOException { long startTime = System.currentTimeMillis(); do { // Note: Files.notExists is not the same as !Files.exists if (Files.notExists(path)) { return;
}
System.gc(); // allow finalizers and cleaners to run try { Thread.sleep(RETRY_DELETE_MILLIS);
} catch (InterruptedException e) { thrownew IOException("Interrupted while waiting for file to be deleted: " + path, e);
}
} while ((System.currentTimeMillis() - startTime) <= MAX_RETRY_DELETE_MILLIS);
thrownew IOException("File not deleted: " + path);
}
/** * 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
*/ publicvoid moveFile(String from, String to) throws IOException {
moveFile(Path.of(from), Path.of(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
*/ publicvoid 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(Path.of(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));
}
/** * Find .java files in one or more directories. * <p>Similar to the shell "find" command: {@code find paths -name \*.java}. * * @param paths the directories in which to search for .java files * @return the .java files found * @throws IOException if an error occurred while searching for files
*/ public Path[] findJavaFiles(Path... paths) throws IOException { return findFiles(".java", paths);
}
/** * Find files matching the file extension, in one or more directories. * <p>Similar to the shell "find" command: {@code find paths -name \*.ext}. * * @param fileExtension the extension to search for * @param paths the directories in which to search for files * @return the files matching the file extension * @throws IOException if an error occurred while searching for files
*/ public Path[] findFiles(String fileExtension, Path... paths) throws IOException {
Set<Path> files = new TreeSet<>(); // use TreeSet to force a consistent order for (Path p : paths) {
Files.walkFileTree(p, new SimpleFileVisitor<>() {
@Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { if (file.getFileName().toString().endsWith(fileExtension)) {
files.add(file);
} return FileVisitResult.CONTINUE;
}
});
} return files.toArray(new Path[0]);
}
/** * 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
*/ publicvoid writeFile(String path, String content) throws IOException {
writeFile(Path.of(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
*/ publicvoid 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.
*/ publicvoid writeJavaFiles(Path dir, String... contents) throws IOException { if (contents.length == 0) thrownew 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 Path.of(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(",", "[", "]"));
}
/** * An in-memory Java source file. * It is able to extract the file name from simple source text using * regular expressions.
*/ publicstaticclass JavaSource extends SimpleJavaFileObject { privatefinal 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
*/ publicvoid 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
*/ publicvoid 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;
}
/** * Extracts the Java file name from the class declaration. * This method is intended for simple files and uses regular expressions. * Comments in the source are stripped before looking for the * declarations from which the name is derived.
*/ static String getJavaFileNameFromSource(String source) {
StringBuilder sb = new StringBuilder();
Matcher matcher = commentPattern.matcher(source); int start = 0; while (matcher.find()) {
sb.append(source, start, matcher.start());
start = matcher.end();
}
sb.append(source.substring(start));
source = sb.toString();
String packageName = null;
matcher = modulePattern.matcher(source); if (matcher.find()) return"module-info.java";
matcher = classPattern.matcher(source); if (matcher.find()) {
String className = matcher.group(1) + ".java";
validateName(className); return (packageName == null) ? className : packageName + "/" + className;
} elseif (packageName != null) { return packageName + "/package-info.java";
} else { thrownew 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. * * @param source the source text * @return the Java file name inferred from the source * @deprecated This is a legacy method for compatibility with ToolBox v1. * Use {@link JavaSource#getName JavaSource.getName} instead.
*/
@Deprecated publicstatic String getJavaFileNameFromSource(String source) { return JavaSource.getJavaFileNameFromSource(source);
}
/** * Validates if a given name is a valid file name * or path name on known platforms. * * @param name the name * @throws IllegalArgumentException if the name is a reserved name
*/ publicstaticvoid validateName(String name) { for (String part : name.split("[./\\\\]")) { if (RESERVED_NAMES.contains(part.toLowerCase(Locale.US))) { thrownew IllegalArgumentException("Name: " + name + " is" + "a reserved name on Windows, " + "and will not work!");
}
}
}
/** * Maps binary class names to generated content.
*/ privatefinal Map<Location, Map<String, Content>> files;
/** * Constructs a memory file manager which stores output files in memory, * and delegates to a default file manager for input files.
*/ public MemoryFileManager() { this(ToolProvider.getSystemJavaCompiler().getStandardFileManager(null, null, null));
}
/** * Constructs 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<>();
}
/** * Returns the set of names of files that have been written to a given * location. * * @param location the location * @return the set of file names
*/ public Set<String> getFileNames(Location location) {
Map<String, Content> filesForLocation = files.get(location); return (filesForLocation == null)
? Collections.emptySet() : filesForLocation.keySet();
}
/** * 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
*/ publicbyte[] 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();
}
/** * Constructs a memory file object. * * @param location the location in which to save the file object * @param name binary name of the class to be stored in this file object * @param kind the kind of 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;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.