/* * Copyright (c) 2013, 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.BufferedReader; import java.io.File; import java.io.FileNotFoundException; import java.io.FileWriter; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.io.StringWriter; import java.net.URI; import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.EnumSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.tools.JavaCompiler; import javax.tools.JavaFileObject; import javax.tools.SimpleJavaFileObject; import javax.tools.ToolProvider; import com.sun.source.util.JavacTask; import com.sun.tools.javac.api.JavacTaskImpl; import sun.tools.jar.Main; import static java.nio.file.StandardCopyOption.*; /** * Toolbox for jtreg tests. */ public class ToolBox { public static final String lineSeparator = System.getProperty("line.separator"); public static final String jdkUnderTest = System.getProperty("test.jdk"); public static final Path javaBinary = Paths.get(jdkUnderTest, "bin", "java"); public static final Path javacBinary = Paths.get(jdkUnderTest, "bin", "javac"); public static final List<String> testToolVMOpts; public static final List<String> testVMOpts; private static final Charset defaultCharset = Charset.defaultCharset(); static final JavaCompiler comp = ToolProvider.getSystemJavaCompiler(); static { String sysProp = System.getProperty("test.tool.vm.opts"); if (sysProp != null && sysProp.length() > 0) { testToolVMOpts = Arrays.asList(sysProp.split("\\s+")); } else { testToolVMOpts = Collections.<String>emptyList(); } sysProp = System.getProperty("test.vm.opts"); if (sysProp != null && sysProp.length() > 0) { testVMOpts = Arrays.asList(sysProp.split("\\s+")); } else { testVMOpts = Collections.<String>emptyList(); } } /** * The expected result of command-like method execution. */ public enum Expect {SUCCESS, FAIL} enum AcceptedParams { EXPECT, SOURCES, OPTIONS, STD_OUTPUT, ERR_OUTPUT, EXTRA_ENV, } enum OutputKind {STD, ERR} /** * Helper class to abstract the processing of command's output. */ static abstract class WriterHelper { OutputKind kind; public abstract void pipeOutput(ProcessBuilder pb); public abstract void readFromStream(Process p) throws IOException; public abstract void addAll(Collection<? extends String> c) throws IOException; } /** * Helper class for redirecting command's output to a file. */ static class FileWriterHelper extends WriterHelper { File file; FileWriterHelper(File file, OutputKind kind) { this.file = file; this.kind = kind; } @Override public void pipeOutput(ProcessBuilder pb) { if (file != null) { switch (kind) { case STD: pb.redirectInput(file); break; case ERR: pb.redirectError(file); break; } } } @Override public void readFromStream(Process p) throws IOException {} @Override public void addAll(Collection<? extends String> c) throws IOException { if (file.exists()) Files.write(file.toPath(), c, defaultCharset, StandardOpenOption.WRITE, StandardOpenOption.APPEND); else Files.write(file.toPath(), c, defaultCharset); } } /** * Helper class for redirecting command's output to a String list. */ static class ListWriterHelper extends WriterHelper { List<String> list; public ListWriterHelper(List<String> list, OutputKind kind) { this.kind = kind; this.list = list; } @Override public void pipeOutput(ProcessBuilder pb) {} @Override public void readFromStream(Process p) throws IOException { BufferedReader br = null; switch (kind) { case STD: br = new BufferedReader(new InputStreamReader(p.getInputStream())); break; case ERR: br = new BufferedReader(new InputStreamReader(p.getErrorStream())); break; } String line; while ((line = br.readLine()) != null) { list.add(line); } } public void addAll(Collection<? extends String> c) { list.addAll(c); } } /** * Simple factory class for creating a WriterHelper instance. */ static class WriterHelperFactory { static WriterHelper make(File file, OutputKind kind) { return new FileWriterHelper(file, kind); } static WriterHelper make(List<String> list, OutputKind kind) { return new ListWriterHelper(list, kind); } } /** * A generic class for holding command's arguments. */ public static abstract class GenericArgs <T extends GenericArgs> { protected static List<Set<AcceptedParams>> minAcceptedParams; protected Set<AcceptedParams> currentParams = EnumSet.<AcceptedParams>noneOf(AcceptedParams.class); protected Expect whatToExpect; protected WriterHelper stdOutput; protected WriterHelper errOutput; protected List<String> args = new ArrayList<>(); protected String[] argsArr; protected GenericArgs() { set(Expect.SUCCESS); } public T set(Expect whatToExpt) { currentParams.add(AcceptedParams.EXPECT); this.whatToExpect = whatToExpt; return (T)this; } public T setStdOutput(List<String> stdOutput) { currentParams.add(AcceptedParams.STD_OUTPUT); this.stdOutput = WriterHelperFactory.make(stdOutput, OutputKind.STD); return (T)this; } public T setStdOutput(File output) { currentParams.add(AcceptedParams.STD_OUTPUT); this.stdOutput = WriterHelperFactory.make(output, OutputKind.STD); return (T)this; } public T setErrOutput(List<String> errOutput) { currentParams.add(AcceptedParams.ERR_OUTPUT); this.errOutput = WriterHelperFactory.make(errOutput, OutputKind.ERR); return (T)this; } public T setErrOutput(File errOutput) { currentParams.add(AcceptedParams.ERR_OUTPUT); this.errOutput = WriterHelperFactory.make(errOutput, OutputKind.ERR); return (T)this; } public T setAllArgs(String... args) { currentParams.add(AcceptedParams.OPTIONS); this.argsArr = args; return (T) this; } public T appendArgs(String... args) { appendArgs(Arrays.asList(args)); return (T)this; } public T appendArgs(Path... args) { if (args != null) { List<String> list = new ArrayList<>(); for (int i = 0; i < args.length; i++) { if (args[i] != null) { list.add(args[i].toString()); } } appendArgs(list); } return (T)this; } public T appendArgs(List<String> args) { if (args != null && args.size() > 0) { currentParams.add(AcceptedParams.OPTIONS); for (int i = 0; i < args.size(); i++) { if (args.get(i) != null) { this.args.add(args.get(i)); } } } return (T)this; } public T setOptions(List<String> options) { currentParams.add(AcceptedParams.OPTIONS); this.args = options; return (T)this; } public T setOptions(String... options) { currentParams.add(AcceptedParams.OPTIONS); this.args = Arrays.asList(options); return (T)this; } public boolean hasMinParams() { for (Set<AcceptedParams> minSet : minAcceptedParams) { if (currentParams.containsAll(minSet)) { return true; } } return false; } } /** * A more specific class for holding javac-like command's arguments. */ public static class JavaToolArgs extends GenericArgs<JavaToolArgs> { static { minAcceptedParams = new ArrayList<>(); minAcceptedParams.add(EnumSet.<AcceptedParams>of( AcceptedParams.EXPECT, AcceptedParams.OPTIONS)); minAcceptedParams.add(EnumSet.<AcceptedParams>of( AcceptedParams.EXPECT, AcceptedParams.SOURCES)); } protected List<? extends JavaFileObject> sources; public JavaToolArgs() { super(); } public JavaToolArgs(Expect whatToExpt) { super.set(whatToExpt); } public JavaToolArgs setSources(List<? extends JavaFileObject> sources) { currentParams.add(AcceptedParams.SOURCES); this.sources = sources; return this; } public JavaToolArgs setSources(JavaSource... sources) { return setSources(Arrays.asList(sources)); } public JavaToolArgs setSources(String... sources) { List<JavaSource> javaSrcs = new ArrayList<>(); for (String source : sources) { javaSrcs.add(new JavaSource(source)); } return setSources(javaSrcs); } } /** * A more specific class for holding any command's arguments. */ public static class AnyToolArgs extends GenericArgs<AnyToolArgs> { static { minAcceptedParams = new ArrayList<>(); minAcceptedParams.add(EnumSet.<AcceptedParams>of( AcceptedParams.EXPECT, AcceptedParams.OPTIONS)); } Map<String, String> extraEnv; public AnyToolArgs() { super(); } public AnyToolArgs(Expect whatToExpt) { set(whatToExpt); } public AnyToolArgs set(Map<String, String> extraEnv) { currentParams.add(AcceptedParams.EXTRA_ENV); this.extraEnv = extraEnv; return this; } } /** * Custom exception for bad command execution. */ public static class CommandExecutionException extends Exception { CommandExecutionException(List<String> command, Expect whatToExpt) { super(createMessage(command, whatToExpt)); } CommandExecutionException(Expect whatToExpt, String... command) { this(Arrays.asList(command), whatToExpt); } private static String createMessage(List<String> command, Expect whatToExpt) { StringBuilder sb = new StringBuilder().append("Command : "); sb.append(command.toString()).append(lineSeparator); switch (whatToExpt) { case SUCCESS: sb.append(" has unexpectedly failed"); break; case FAIL: sb.append(" has been unexpectedly successful"); break; } return sb.toString(); } } /** * Custom exception for not equal resources. */ public static class ResourcesNotEqualException extends Exception { public ResourcesNotEqualException(List<String> res1, List<String> res2) { super(createMessage(res1, res2)); } public ResourcesNotEqualException(String line1, String line2) { super(createMessage(line1, line2)); } public ResourcesNotEqualException(Path path1, Path path2) { super(createMessage(path1, path2)); } private static String createMessage(Path path1, Path path2) { return new StringBuilder() .append("The resources provided for comparison in paths \n") .append(path1.toString()).append(" and \n") .append(path2.toString()).append("are different").toString(); } private static String createMessage(String line1, String line2) { return new StringBuilder() .append("The resources provided for comparison are different at lines: \n") .append(line1).append(" and \n") .append(line2).toString(); } private static String createMessage(List<String> res1, List<String> res2) { return new StringBuilder() .append("The resources provided for comparison are different: \n") .append("Resource 1 is: ").append(res1).append("\n and \n") .append("Resource 2 is: ").append(res2).append("\n").toString(); } } /** * A javac compiler caller method. */ public static int javac(JavaToolArgs params) throws CommandExecutionException, IOException { if (params.hasMinParams()) { if (params.argsArr != null) { return genericJavaCMD(JavaCMD.JAVAC, params); } else { return genericJavaCMD(JavaCMD.JAVAC_API, params); } } throw new AssertionError("javac command has been invoked with less parameters than needed"); } /** * A javap calling method. */ public static String javap(JavaToolArgs params) throws CommandExecutionException, IOException { if (params.hasMinParams()) { List<String> list = new ArrayList<>(); params.setErrOutput(list); genericJavaCMD(JavaCMD.JAVAP, params); return listToString(list); } throw new AssertionError("javap command has been invoked with less parameters than needed"); } /** * A javah calling method. */ public static int javah(JavaToolArgs params) throws CommandExecutionException, IOException { if (params.hasMinParams()) { return genericJavaCMD(JavaCMD.JAVAH, params); } throw new AssertionError("javah command has been invoked with less parameters than needed"); } /** * A enum class for langtools commands. */ enum JavaCMD { JAVAC { @Override int run(JavaToolArgs params, PrintWriter pw) { return com.sun.tools.javac.Main.compile(params.argsArr, pw); } }, JAVAC_API { @Override int run(JavaToolArgs params, PrintWriter pw) { JavacTask ct = (JavacTask)comp.getTask(pw, null, null, params.args, null, params.sources); return ((JavacTaskImpl)ct).doCall().exitCode; } @Override String getName() { return "javac"; } @Override List<String> getExceptionMsgContent(JavaToolArgs params) { List<String> result = super.getExceptionMsgContent(params); for (JavaFileObject source : params.sources) { if (source instanceof JavaSource) { result.add(((JavaSource)source).name); } } return result; } }, JAVAH { @Override int run(JavaToolArgs params, PrintWriter pw) { return com.sun.tools.javah.Main.run(params.argsArr, pw); } }, JAVAP { @Override int run(JavaToolArgs params, PrintWriter pw) { return com.sun.tools.javap.Main.run(params.argsArr, pw); } }; abstract int run(JavaToolArgs params, PrintWriter pw); String getName() { return this.name().toLowerCase(); } List<String> getExceptionMsgContent(JavaToolArgs params) { List<String> result = new ArrayList<>(); result.add(getName()); result.addAll(params.argsArr != null ? Arrays.asList(params.argsArr) : params.args); return result; } } /** * A helper method for executing langtools commands. */ private static int genericJavaCMD( JavaCMD cmd, JavaToolArgs params) throws CommandExecutionException, IOException { int rc = 0; StringWriter sw = null; try (PrintWriter pw = (params.errOutput == null) ? null : new PrintWriter(sw = new StringWriter())) { rc = cmd.run(params, pw); } String out = (sw == null) ? null : sw.toString(); if (params.errOutput != null && (out != null) && !out.isEmpty()) { params.errOutput.addAll(splitLines(out, lineSeparator)); } if ( (rc == 0 && params.whatToExpect == Expect.SUCCESS) || (rc != 0 && params.whatToExpect == Expect.FAIL) ) { return rc; } throw new CommandExecutionException(cmd.getExceptionMsgContent(params), params.whatToExpect); } /** * A jar calling method. */ public static boolean jar(String... params) throws CommandExecutionException { Main jarGenerator = new Main(System.out, System.err, "jar"); boolean result = jarGenerator.run(params); if (!result) { List<String> command = new ArrayList<>(); command.add("jar"); command.addAll(Arrays.asList(params)); throw new CommandExecutionException(command, Expect.SUCCESS); } return result; } /** * A general command calling method. */ public static int executeCommand(AnyToolArgs params) throws CommandExecutionException, IOException, InterruptedException { if (params.hasMinParams()) { List<String> cmd = (params.args != null) ? params.args : Arrays.asList(params.argsArr); return executeCommand(cmd, params.extraEnv, params.stdOutput, params.errOutput, params.whatToExpect); } throw new AssertionError("command has been invoked with less parameters than needed"); } /** * A helper method for calling a general command. */ private static int executeCommand( List<String> command, Map<String, String> extraEnv, WriterHelper stdOutput, WriterHelper errOutput, Expect whatToExpt) throws IOException, InterruptedException, CommandExecutionException { ProcessBuilder pb = new ProcessBuilder(command); if (stdOutput != null) stdOutput.pipeOutput(pb); if (errOutput != null) errOutput.pipeOutput(pb); if (extraEnv != null) { pb.environment().putAll(extraEnv); } Process p = pb.start(); if (stdOutput != null) stdOutput.readFromStream(p); if (errOutput != null) errOutput.readFromStream(p); int result = p.waitFor(); if ( (result == 0 && whatToExpt == Expect.SUCCESS) || (result != 0 && whatToExpt == Expect.FAIL) ) { return result; } throw new CommandExecutionException(command, whatToExpt); } /** * This set of methods can be used instead of diff when the only needed * result is the equality or inequality of the two given resources. * * A resource can be a file or a String list. */ public static void compareLines(Path aPath, Path otherPath, String encoding) throws FileNotFoundException, IOException, ResourcesNotEqualException { compareLines(aPath, otherPath, encoding, false); } public static void compareLines( Path aPath, Path otherPath, String encoding, boolean trim) throws FileNotFoundException, IOException, ResourcesNotEqualException { Charset charset = encoding != null ? Charset.forName(encoding) : defaultCharset; List<String> list1 = Files.readAllLines(aPath, charset); List<String> list2 = Files.readAllLines(otherPath, charset); compareLines(list1, list2, trim); } public static void compareLines(Path path, List<String> strings, String encoding) throws FileNotFoundException, IOException, ResourcesNotEqualException { compareLines(path, strings, encoding, false); } public static void compareLines(Path path, List<String> strings, String encoding, boolean trim) throws FileNotFoundException, IOException, ResourcesNotEqualException { Charset charset = encoding != null ? Charset.forName(encoding) : defaultCharset; List<String> list = Files.readAllLines(path, charset); compareLines(list, strings, trim); } public static void compareLines(List<String> list1, List<String> list2) throws ResourcesNotEqualException { compareLines(list1, list2, false); } public static void compareLines(List<String> list1, List<String> list2, boolean trim) throws ResourcesNotEqualException { if ((list1 == list2) || (list1 == null && list2 == null)) return; if (list1.size() != list2.size()) throw new ResourcesNotEqualException(list1, list2); int i = 0; int j = 0; while (i < list1.size() && j < list2.size() && equals(list1.get(i), list2.get(j), trim)) { i++; j++; } if (!(i == list1.size() && j == list2.size())) throw new ResourcesNotEqualException(list1, list2); } private static boolean equals(String s1, String s2, boolean trim) { return (trim ? s1.trim().equals(s2.trim()) : s1.equals(s2)); } /** * A set of simple grep-like methods, looks for regExpr in text. * The content of text is split using the new line character as a pattern * and later the regExpr is seek in every split line. If a match is found, * the whole line is added to the result. */ public static List<String> grep(String regExpr, String text, String sep) { return grep(regExpr, splitLines(text, sep)); } public static List<String> grep(String regExpr, List<String> text) { List<String> result = new ArrayList<>(); Pattern pattern = Pattern.compile(regExpr); for (String s : text) { if (pattern.matcher(s).find()) { result.add(s); } } return result; } public static List<String> grep(String regExpr, File f) throws IOException { List<String> lines = Files.readAllLines(f.toPath(), defaultCharset); return grep(regExpr, lines); } /** * A touch-like method. */ public static boolean touch(String fileName) { File file = new File(fileName); return touch(file); } public static boolean touch(File file) { if (file.exists()) { file.setLastModified(System.currentTimeMillis()); return true; } return false; } public static void createJavaFile(File outFile) throws IOException { createJavaFile(outFile, null); } /** * A method for creating a valid but very simple java file. */ public static void createJavaFile(File outFile, File superClass) throws IOException { String srcStr = "public class " + getSimpleName(outFile) + " "; if (superClass != null) { srcStr = srcStr.concat("extends " + getSimpleName(superClass) + " "); } srcStr = srcStr.concat("{}"); try (PrintWriter ps = new PrintWriter(new FileWriter(outFile))) { ps.println(srcStr); } } /** * Creates a java file name given its source. * The file is created in the working directory, creating a directory * tree if there is a package declaration. */ public static void createJavaFileFromSource(String source) throws IOException { createJavaFileFromSource(null, source); } /** * Creates a java file name given its source. * The file is created in the working directory, creating a directory * tree if there is a package declaration or the argument initialPath * has a valid path. * * e.i. if initialPath is foo/ and the source is: * package bar; * * public class bazz {} * * this method will create the file foo/bar/bazz.java in the working * directory. */ public static void createJavaFileFromSource(Path initialPath, String source) throws IOException { String fileName = getJavaFileNameFromSource(source); String dirTree = getDirTreeFromSource(source); Path path = (dirTree != null) ? Paths.get(dirTree, fileName) : Paths.get(fileName); path = (initialPath != null) ? initialPath.resolve(path): path; writeFile(path, source); } static Pattern publicClassPattern = Pattern.compile("public\\s+(?:class|enum|interface){1}\\s+(\\w+)"); static Pattern packageClassPattern = Pattern.compile("(?:class|enum|interface){1}\\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. */ private static String getJavaFileNameFromSource(String source) { String className = null; Matcher matcher = publicClassPattern.matcher(source); if (matcher.find()) { className = matcher.group(1) + ".java"; } else { matcher = packageClassPattern.matcher(source); if (matcher.find()) { className = matcher.group(1) + ".java"; } else { throw new AssertionError("Could not extract the java class " + "name from the provided source"); } } return className; } static Pattern packagePattern = Pattern.compile("package\\s+(((?:\\w+\\.)*)(?:\\w+))"); /** * Extracts the path from the package declaration if present. * This method is intended for simple files and uses regular expressions, * so comments matching the pattern can make the method fail. */ private static String getDirTreeFromSource(String source) { Matcher matcher = packagePattern.matcher(source); return matcher.find() ? matcher.group(1).replace(".", File.separator) : null; } /** * A method for creating a jar's manifest file with supplied data. */ public static void mkManifestWithClassPath(String mainClass, String... classes) throws IOException { List <String> lines = new ArrayList<>(); StringBuilder sb = new StringBuilder("Class-Path: ".length() + classes[0].length()).append("Class-Path: ").append(classes[0]); for (int i = 1; i < classes.length; i++) { sb.append(" ").append(classes[i]); } lines.add(sb.toString()); if (mainClass != null) { lines.add(new StringBuilder("Main-Class: ".length() + mainClass.length()) .append("Main-Class: ") .append(mainClass).toString()); } Files.write(Paths.get("MANIFEST.MF"), lines, null); } /** * A utility method to obtain the file name. */ static String getSimpleName(File inFile) { return inFile.toPath().getFileName().toString(); } /** * A method to write to a file, the directory tree is created if needed. */ public static File writeFile(Path path, String body) throws IOException { File result; if (path.getParent() != null) { Files.createDirectories(path.getParent()); } try (FileWriter out = new FileWriter(result = path.toAbsolutePath().toFile())) { out.write(body); } return result; } public static File writeFile(String path, String body) throws IOException { return writeFile(Paths.get(path), body); } /** * A rm-like method, the file is deleted only if it exists. */ public static void rm(Path path) throws Exception { Files.deleteIfExists(path); } public static void rm(String filename) throws Exception { rm(Paths.get(filename)); } public static void rm(File f) throws Exception { rm(f.toPath()); } /** * Copy source file to destination file. */ public static void copyFile(File destfile, File srcfile) throws IOException { copyFile(destfile.toPath(), srcfile.toPath()); } public static void copyFile(Path destPath, Path srcPath) throws IOException { Files.createDirectories(destPath); Files.copy(srcPath, destPath, REPLACE_EXISTING); } /** * Splits a String using the System's line separator character as splitting point. */ public static List<String> splitLines(String lines, String sep) { return Arrays.asList(lines.split(sep)); } /** * Converts a String list into one String by appending the System's line separator * character after each component. */ private static String listToString(List<String> lines) { StringBuilder sb = new StringBuilder(); for (String s : lines) { sb.append(s).append(lineSeparator); } return sb.toString(); } /** * Returns true if the OS is a Windows version. */ public static boolean isWindows() { String osName = System.getProperty("os.name"); return osName.toUpperCase().startsWith("WINDOWS"); } /** * Class representing an in-memory java source file. It is able to extract * the file name from simple source codes using regular expressions. */ public static class JavaSource extends SimpleJavaFileObject { String source; String name; public JavaSource(String className, String source) { super(URI.create(className), JavaFileObject.Kind.SOURCE); this.name = className; this.source = source; } public JavaSource(String source) { super(URI.create(getJavaFileNameFromSource(source)), JavaFileObject.Kind.SOURCE); this.name = getJavaFileNameFromSource(source); this.source = source; } @Override public CharSequence getCharContent(boolean ignoreEncodingErrors) { return source; } } }