diff -r 011cf7e0a148 -r dc8b7aa7cef3 test/tools/javac/lib/ToolBox.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/tools/javac/lib/ToolBox.java Tue Feb 19 17:53:16 2013 +0000 @@ -0,0 +1,911 @@ +/* + * 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.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 String testVMOpts = System.getProperty("test.tool.vm.opts"); + public static final String javaBinary = Paths.get(jdkUnderTest, "bin", "java").toString(); + //why this one private. Because the function which provide also the test options should be used + private static final String javacBinary = Paths.get(jdkUnderTest, "bin", "javac").toString(); + + private static final Charset defaultCharset = Charset.defaultCharset(); + + static final JavaCompiler comp = ToolProvider.getSystemJavaCompiler(); + + /** + * 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 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 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 list; + + public ListWriterHelper(List 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 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 list, OutputKind kind) { + return new ListWriterHelper(list, kind); + } + } + + /** + * A generic class for holding command's arguments. + */ + public static abstract class GenericArgs { + protected static List> minAcceptedParams; + + protected Set currentParams = + EnumSet.noneOf(AcceptedParams.class); + + protected Expect whatToExpect; + protected WriterHelper stdOutput; + protected WriterHelper errOutput; + protected List options; + protected String[] optionsArr; + + protected GenericArgs() { + set(Expect.SUCCESS); + } + + public T set(Expect whatToExpt) { + currentParams.add(AcceptedParams.EXPECT); + this.whatToExpect = whatToExpt; + return (T)this; + } + + public T setStdOutput(List 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 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.optionsArr = args; + return (T)this; + } + + public T setOptions(List options) { + currentParams.add(AcceptedParams.OPTIONS); + this.options = options; + return (T)this; + } + + public T setOptions(String... options) { + currentParams.add(AcceptedParams.OPTIONS); + this.options = Arrays.asList(options); + return (T)this; + } + + public boolean hasMinParams() { + for (Set 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 { + + static { + minAcceptedParams = new ArrayList<>(); + minAcceptedParams.add(EnumSet.of( + AcceptedParams.EXPECT, AcceptedParams.OPTIONS)); + minAcceptedParams.add(EnumSet.of( + AcceptedParams.EXPECT, AcceptedParams.SOURCES)); + } + + protected List sources; + + public JavaToolArgs() { + super(); + } + + public JavaToolArgs(Expect whatToExpt) { + super.set(whatToExpt); + } + + public JavaToolArgs setSources(List 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 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 { + + static { + minAcceptedParams = new ArrayList<>(); + minAcceptedParams.add(EnumSet.of( + AcceptedParams.EXPECT, AcceptedParams.OPTIONS)); + } + + Map extraEnv; + + public AnyToolArgs() { + super(); + } + + public AnyToolArgs(Expect whatToExpt) { + set(whatToExpt); + } + + public AnyToolArgs set(Map 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 command, Expect whatToExpt) { + super(createMessage(command, whatToExpt)); + } + + CommandExecutionException(Expect whatToExpt, String... command) { + this(Arrays.asList(command), whatToExpt); + } + + private static String createMessage(List 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() { + super("The resources provided for comparison are different"); + } + + 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(); + } + } + + /** + * Method to get the a path to the javac command available at the jdk being + * tested along with the test vm options. + * @return a String[] with the two components mentioned. + */ + public static String[] getJavacBin() { + return new String[]{javacBinary, testVMOpts}; + } + + /** + * A javac compiler caller method. + */ + public static int javac(JavaToolArgs params) + throws CommandExecutionException, IOException { + if (params.hasMinParams()) { + if (params.optionsArr != 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 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.optionsArr, pw); + } + }, + JAVAC_API { + @Override + int run(JavaToolArgs params, PrintWriter pw) { + JavacTask ct = (JavacTask)comp.getTask(pw, null, null, + params.options, null, params.sources); + return ((JavacTaskImpl)ct).doCall().exitCode; + } + + @Override + String getName() { + return "javac"; + } + + @Override + List getExceptionMsgContent(JavaToolArgs params) { + List 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.optionsArr, pw); + } + }, + JAVAP { + @Override + int run(JavaToolArgs params, PrintWriter pw) { + return com.sun.tools.javap.Main.run(params.optionsArr, pw); + } + }; + + abstract int run(JavaToolArgs params, PrintWriter pw); + + String getName() { + return this.name().toLowerCase(); + } + + List getExceptionMsgContent(JavaToolArgs params) { + List result = new ArrayList<>(); + result.add(getName()); + result.addAll(params.optionsArr != null ? + Arrays.asList(params.optionsArr) : + params.options); + 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)); + } + + 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 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 cmd = (params.options != null) ? + params.options : + Arrays.asList(params.optionsArr); + 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 command, + Map 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 list1 = Files.readAllLines(aPath, charset); + List list2 = Files.readAllLines(otherPath, charset); + compareLines(list1, list2, trim); + } + + public static void compareLines(Path path, List strings, String encoding) + throws FileNotFoundException, IOException, ResourcesNotEqualException { + compareLines(path, strings, encoding, false); + } + + public static void compareLines(Path path, List strings, + String encoding, boolean trim) + throws FileNotFoundException, IOException, ResourcesNotEqualException { + Charset charset = encoding != null ? + Charset.forName(encoding) : + defaultCharset; + List list = Files.readAllLines(path, charset); + compareLines(list, strings, trim); + } + + public static void compareLines(List list1, List list2) + throws ResourcesNotEqualException { + compareLines(list1, list2, false); + } + + public static void compareLines(List list1, + List list2, boolean trim) throws ResourcesNotEqualException { + if ((list1 == list2) || (list1 == null && list2 == null)) return; + if (list1.size() != list2.size()) + throw new ResourcesNotEqualException(); + 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(); + } + + 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 grep(String regExpr, String text) { + return grep(regExpr, splitLines(text)); + } + + public static List grep(String regExpr, List text) { + List 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 grep(String regExpr, File f) + throws IOException { + List 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 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 splitLines(String lines) { + return Arrays.asList(lines.split(lineSeparator)); + } + + /** + * Converts a String list into one String by appending the System's line separator + * character after each component. + */ + private static String listToString(List lines) { + StringBuilder sb = new StringBuilder(); + for (String s : lines) { + sb.append(s).append(lineSeparator); + } + return sb.toString(); + } + + /** + * 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; + } + } +}