| /* | |
| * Copyright (c) 2014, 2024, 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. | |
| */ | |
| /* | |
| * @test | |
| * @bug 7026941 | |
| * @summary path options ignored when reusing filemanager across tasks | |
| * @modules java.compiler | |
| * jdk.compiler | |
| */ | |
| import java.io.File; | |
| import java.io.FileWriter; | |
| import java.io.IOException; | |
| import java.io.PrintWriter; | |
| import java.net.URI; | |
| import java.nio.file.Files; | |
| import java.util.ArrayList; | |
| import java.util.Arrays; | |
| import java.util.Collections; | |
| import java.util.EnumSet; | |
| import java.util.List; | |
| import java.util.Objects; | |
| import java.util.jar.JarEntry; | |
| import java.util.jar.JarOutputStream; | |
| import java.util.regex.Matcher; | |
| import java.util.regex.Pattern; | |
| import javax.tools.JavaCompiler; | |
| import javax.tools.JavaCompiler.CompilationTask; | |
| import javax.tools.JavaFileObject; | |
| import javax.tools.SimpleJavaFileObject; | |
| import javax.tools.StandardJavaFileManager; | |
| import javax.tools.StandardLocation; | |
| import javax.tools.ToolProvider; | |
| import static javax.tools.StandardLocation.*; | |
| /** | |
| * Test for combinations of using javac command-line options and fileManager setLocation | |
| * calls to affect the locations available in the fileManager. | |
| * | |
| * Using a single Java compiler and file manager, for each of the standard locations, | |
| * a series of operations is performed, using either compiler options or setLocation | |
| * calls. Each operation includes a compilation, and then a check for the value of | |
| * the standard location available in the file manager. | |
| * | |
| * The operations generate and use unique files to minimize the possibility of false | |
| * positive results. | |
| */ | |
| public class TestSearchPaths { | |
| public static void main(String... args) throws Exception { | |
| TestSearchPaths t = new TestSearchPaths(); | |
| t.run(); | |
| } | |
| void run() throws Exception { | |
| compiler = ToolProvider.getSystemJavaCompiler(); | |
| fileManager = compiler.getStandardFileManager(null, null, null); | |
| try { | |
| // basic output path | |
| testClassOutput(); | |
| // basic search paths | |
| testClassPath(); | |
| testSourcePath(); | |
| testPlatformClassPath(); | |
| // annotation processing | |
| testAnnotationProcessorPath(); | |
| testSourceOutput(); | |
| // javah equivalent | |
| testNativeHeaderOutput(); | |
| // future-proof: guard against new StandardLocations being added | |
| if (!tested.equals(EnumSet.allOf(StandardLocation.class))) { | |
| // FIXME: need to update for JDK 9 locations | |
| // error("not all standard locations have been tested"); | |
| out.println("not yet tested: " + EnumSet.complementOf(tested)); | |
| } | |
| if (errors > 0) { | |
| throw new Exception(errors + " errors occurred"); | |
| } | |
| } finally { | |
| fileManager.close(); | |
| } | |
| } | |
| void testClassOutput() throws IOException { | |
| String test = "testClassOutput"; | |
| System.err.println("test: " + test); | |
| for (int i = 1; i <= 5; i++) { | |
| File classes = createDir(test + "/" + i + "/classes"); | |
| List<String> options; | |
| switch (i) { | |
| default: | |
| options = getOptions("-d", classes.getPath()); | |
| break; | |
| case 3: | |
| setLocation(CLASS_OUTPUT, classes); | |
| options = null; | |
| break; | |
| } | |
| List<JavaFileObject> sources = getSources("class C" + i + " { }"); | |
| callTask(options, sources); | |
| checkPath(CLASS_OUTPUT, Mode.EQUALS, classes); | |
| checkFile(CLASS_OUTPUT, "C" + i + ".class"); | |
| } | |
| tested.add(CLASS_OUTPUT); | |
| } | |
| void testClassPath() throws IOException { | |
| String test = "testClassPath"; | |
| System.err.println("test: " + test); | |
| for (int i = 1; i <= 5; i++) { | |
| File classes = createDir(test + "/" + i + "/classes"); | |
| File classpath = new File("testClassOutput/" + i + "/classes"); | |
| List<String> options; | |
| switch (i) { | |
| default: | |
| options = getOptions("-d", classes.getPath(), "-classpath", classpath.getPath()); | |
| break; | |
| case 3: | |
| setLocation(CLASS_PATH, classpath); | |
| options = getOptions("-d", classes.getPath()); | |
| break; | |
| case 4: | |
| options = getOptions("-d", classes.getPath(), "-cp", classpath.getPath()); | |
| break; | |
| } | |
| List<JavaFileObject> sources = getSources("class D" + i + " { C" + i + " c; }"); | |
| callTask(options, sources); | |
| checkPath(CLASS_PATH, Mode.EQUALS, classpath); | |
| checkFile(CLASS_OUTPUT, "D" + i + ".class"); | |
| } | |
| tested.add(CLASS_PATH); | |
| System.err.println(); | |
| } | |
| void testSourcePath() throws IOException { | |
| String test = "testSourcePath"; | |
| System.err.println("test: " + test); | |
| setLocation(CLASS_PATH); // empty | |
| for (int i = 1; i <= 5; i++) { | |
| File src = createDir(test + "/" + i + "/src"); | |
| writeFile(src, "C" + i + ".java", "class C" + i + "{ }"); | |
| File classes = createDir(test + "/" + i + "/classes"); | |
| File srcpath = src; | |
| List<String> options; | |
| switch (i) { | |
| default: | |
| options = getOptions("-d", classes.getPath(), "-sourcepath", srcpath.getPath()); | |
| break; | |
| case 3: | |
| setLocation(SOURCE_PATH, srcpath); | |
| options = getOptions("-d", classes.getPath()); | |
| break; | |
| } | |
| List<JavaFileObject> sources = getSources("class D" + i + " { C" + i + " c; }"); | |
| callTask(options, sources); | |
| checkPath(SOURCE_PATH, Mode.EQUALS, srcpath); | |
| checkFile(CLASS_OUTPUT, "D" + i + ".class"); | |
| } | |
| tested.add(SOURCE_PATH); | |
| System.err.println(); | |
| } | |
| void testPlatformClassPath() throws IOException { | |
| String test = "testPlatformClassPath"; | |
| System.err.println("test: " + test); | |
| List<File> defaultPath = getLocation(PLATFORM_CLASS_PATH); | |
| StringBuilder sb = new StringBuilder(); | |
| for (File f: defaultPath) { | |
| if (sb.length() > 0) | |
| sb.append(File.pathSeparator); | |
| sb.append(f); | |
| } | |
| String defaultPathString = sb.toString(); | |
| setLocation(CLASS_PATH); // empty | |
| setLocation(SOURCE_PATH); // empty | |
| // Use -source 8 -target 8 to enable use of platform class path options | |
| // FIXME: temporarily exclude cases referring to default bootclasspath | |
| // for (int i = 1; i <= 10; i++) { | |
| int[] cases = new int[] { 1, 2, 4, 5, 6, 7 }; | |
| for (int i : cases) { | |
| File classes = createDir(test + "/" + i + "/classes"); | |
| File testJars = createDir(test + "/" + i + "/testJars"); | |
| File testClasses = createDir(test + "/" + i + "/testClasses"); | |
| callTask(getOptions("-d", testClasses.getPath()), getSources("class C" + i + " { }")); | |
| List<String> options; | |
| Mode mode; | |
| List<File> match; | |
| String reference = "C" + i + " c;"; | |
| File jar; | |
| switch (i) { | |
| case 1: | |
| options = getOptions("-d", classes.getPath(), | |
| "-source", "8", "-target", "8", | |
| "-Xbootclasspath/p:" + testClasses); | |
| mode = Mode.STARTS_WITH; | |
| match = Arrays.asList(testClasses); | |
| break; | |
| case 2: | |
| // the default values for -extdirs and -endorseddirs come after the bootclasspath; | |
| // so to check -Xbootclasspath/a: we specify empty values for those options. | |
| options = getOptions("-d", classes.getPath(), | |
| "-source", "8", "-target", "8", | |
| "-Xbootclasspath/a:" + testClasses, | |
| "-extdirs", "", | |
| "-endorseddirs", ""); | |
| mode = Mode.ENDS_WITH; | |
| match = Arrays.asList(testClasses); | |
| break; | |
| case 3: | |
| options = getOptions("-d", classes.getPath(), "-Xbootclasspath:" + defaultPathString); | |
| mode = Mode.EQUALS; | |
| match = defaultPath; | |
| reference = ""; | |
| break; | |
| case 4: | |
| fileManager.setLocation(PLATFORM_CLASS_PATH, null); | |
| jar = new File(testJars, "j" + i + ".jar"); | |
| writeJar(jar, testClasses, "C" + i + ".class"); | |
| options = getOptions("-d", classes.getPath(), | |
| "-source", "8", "-target", "8", | |
| "-endorseddirs", testJars.getPath()); | |
| mode = Mode.CONTAINS; | |
| match = Arrays.asList(jar); | |
| break; | |
| case 5: | |
| fileManager.setLocation(PLATFORM_CLASS_PATH, null); | |
| jar = new File(testJars, "j" + i + ".jar"); | |
| writeJar(jar, testClasses, "C" + i + ".class"); | |
| options = getOptions("-d", classes.getPath(), | |
| "-source", "8", "-target", "8", | |
| "-Djava.endorsed.dirs=" + testJars.getPath()); | |
| mode = Mode.CONTAINS; | |
| match = Arrays.asList(jar); | |
| break; | |
| case 6: | |
| fileManager.setLocation(PLATFORM_CLASS_PATH, null); | |
| jar = new File(testJars, "j" + i + ".jar"); | |
| writeJar(jar, testClasses, "C" + i + ".class"); | |
| options = getOptions("-d", classes.getPath(), | |
| "-source", "8", "-target", "8", | |
| "-extdirs", testJars.getPath()); | |
| mode = Mode.CONTAINS; | |
| match = Arrays.asList(jar); | |
| break; | |
| case 7: | |
| fileManager.setLocation(PLATFORM_CLASS_PATH, null); | |
| jar = new File(testJars, "j" + i + ".jar"); | |
| writeJar(jar, testClasses, "C" + i + ".class"); | |
| options = getOptions("-d", classes.getPath(), | |
| "-source", "8", "-target", "8", | |
| "-Djava.ext.dirs=" + testJars.getPath()); | |
| mode = Mode.CONTAINS; | |
| match = Arrays.asList(jar); | |
| break; | |
| case 8: | |
| setLocation(PLATFORM_CLASS_PATH, defaultPath); | |
| options = getOptions("-d", classes.getPath()); | |
| mode = Mode.EQUALS; | |
| match = defaultPath; | |
| reference = ""; | |
| break; | |
| default: | |
| options = getOptions("-d", classes.getPath(), | |
| "-source", "8", "-target", "8", | |
| "-bootclasspath", defaultPathString); | |
| mode = Mode.EQUALS; | |
| match = defaultPath; | |
| reference = ""; | |
| break; | |
| } | |
| List<JavaFileObject> sources = getSources("class D" + i + " { " + reference + " }"); | |
| callTask(options, sources); | |
| checkPath(PLATFORM_CLASS_PATH, mode, match); | |
| checkFile(CLASS_OUTPUT, "D" + i + ".class"); | |
| } | |
| tested.add(PLATFORM_CLASS_PATH); | |
| System.err.println(); | |
| } | |
| void testAnnotationProcessorPath() throws IOException { | |
| String test = "testAnnotationProcessorPath"; | |
| System.err.println("test: " + test); | |
| fileManager.setLocation(PLATFORM_CLASS_PATH, null); | |
| String template = | |
| "import java.util.*;\n" | |
| + "import javax.annotation.processing.*;\n" | |
| + "import javax.lang.model.*;\n" | |
| + "import javax.lang.model.element.*;\n" | |
| + "@SupportedAnnotationTypes(\"*\")\n" | |
| + "public class A%d extends AbstractProcessor {\n" | |
| + " public boolean process(Set<? extends TypeElement> annos, RoundEnvironment rEnv) {\n" | |
| + " return true;\n" | |
| + " }\n" | |
| + " public SourceVersion getSupportedSourceVersion() {\n" | |
| + " return SourceVersion.latest();\n" | |
| + " }\n" | |
| + "}"; | |
| for (int i = 1; i <= 5; i++) { | |
| File classes = createDir(test + "/" + i + "/classes"); | |
| File annodir = createDir(test + "/" + i + "/processors"); | |
| callTask(getOptions("-d", annodir.getPath()), getSources(String.format(template, i))); | |
| File annopath = annodir; | |
| List<String> options; | |
| switch (i) { | |
| default: | |
| options = getOptions("-d", classes.getPath(), | |
| "-processorpath", annopath.getPath(), | |
| "-processor", "A" + i); | |
| break; | |
| case 3: | |
| setLocation(ANNOTATION_PROCESSOR_PATH, annopath); | |
| options = getOptions("-d", classes.getPath(), | |
| "-processor", "A" + i); | |
| break; | |
| } | |
| List<JavaFileObject> sources = getSources("class D" + i + " { }"); | |
| callTask(options, sources); | |
| checkPath(ANNOTATION_PROCESSOR_PATH, Mode.EQUALS, annopath); | |
| checkFile(CLASS_OUTPUT, "D" + i + ".class"); | |
| } | |
| tested.add(ANNOTATION_PROCESSOR_PATH); | |
| System.err.println(); | |
| } | |
| void testSourceOutput() throws IOException { | |
| String test = "testAnnotationProcessorPath"; | |
| System.err.println("test: " + test); | |
| String source = | |
| "import java.io.*;\n" | |
| + "import java.util.*;\n" | |
| + "import javax.annotation.processing.*;\n" | |
| + "import javax.lang.model.*;\n" | |
| + "import javax.lang.model.element.*;\n" | |
| + "import javax.tools.*;\n" | |
| + "@SupportedOptions(\"name\")\n" | |
| + "@SupportedAnnotationTypes(\"*\")\n" | |
| + "public class A extends AbstractProcessor {\n" | |
| + " int round = 0;\n" | |
| + " public boolean process(Set<? extends TypeElement> annos, RoundEnvironment rEnv) {\n" | |
| + " if (round++ == 0) try {\n" | |
| + " String name = processingEnv.getOptions().get(\"name\");\n" | |
| + " JavaFileObject fo = processingEnv.getFiler().createSourceFile(name);\n" | |
| + " try (Writer out = fo.openWriter()) {\n" | |
| + " out.write(\"class \" + name + \" { }\");\n" | |
| + " }\n" | |
| + " } catch (IOException e) { throw new Error(e); }\n" | |
| + " return true;\n" | |
| + " }\n" | |
| + " public SourceVersion getSupportedSourceVersion() {\n" | |
| + " return SourceVersion.latest();\n" | |
| + " }\n" | |
| + "}"; | |
| File annodir = createDir(test + "/processors"); | |
| callTask(getOptions("-d", annodir.getPath()), getSources(source)); | |
| setLocation(ANNOTATION_PROCESSOR_PATH, annodir); | |
| for (int i = 1; i <= 5; i++) { | |
| File classes = createDir(test + "/" + i + "/classes"); | |
| File genSrc = createDir(test + "/" + "/genSrc"); | |
| List<String> options; | |
| switch (i) { | |
| default: | |
| options = getOptions("-d", classes.getPath(), | |
| "-processor", "A", "-Aname=G" + i, | |
| "-s", genSrc.getPath()); | |
| break; | |
| case 3: | |
| setLocation(SOURCE_OUTPUT, genSrc); | |
| options = getOptions("-d", classes.getPath(), | |
| "-processor", "A", "-Aname=G" + i); | |
| break; | |
| } | |
| List<JavaFileObject> sources = getSources("class D" + i + " { }"); | |
| callTask(options, sources); | |
| checkPath(SOURCE_OUTPUT, Mode.EQUALS, genSrc); | |
| checkFile(CLASS_OUTPUT, "D" + i + ".class"); | |
| checkFile(CLASS_OUTPUT, "G" + i + ".class"); | |
| } | |
| tested.add(SOURCE_OUTPUT); | |
| System.err.println(); | |
| } | |
| void testNativeHeaderOutput() throws IOException { | |
| String test = "testNativeHeaderOutput"; | |
| System.err.println("test: " + test); | |
| for (int i = 1; i <= 5; i++) { | |
| File classes = createDir(test + "/" + i + "/classes"); | |
| File headers = createDir(test + "/" + i + "/hdrs"); | |
| List<String> options; | |
| switch (i) { | |
| default: | |
| options = getOptions("-d", classes.getPath(), "-h", headers.getPath()); | |
| break; | |
| case 3: | |
| setLocation(NATIVE_HEADER_OUTPUT, headers); | |
| options = getOptions("-d", classes.getPath()); | |
| break; | |
| } | |
| List<JavaFileObject> sources = getSources("class C" + i + " { native void m(); }"); | |
| callTask(options, sources); | |
| checkPath(NATIVE_HEADER_OUTPUT, Mode.EQUALS, headers); | |
| checkFile(NATIVE_HEADER_OUTPUT, "C" + i + ".h"); | |
| } | |
| tested.add(StandardLocation.NATIVE_HEADER_OUTPUT); | |
| System.err.println(); | |
| } | |
| List<String> getOptions(String... args) { | |
| return Arrays.asList(args); | |
| } | |
| List<JavaFileObject> getSources(String... sources) { | |
| List<JavaFileObject> list = new ArrayList<>(); | |
| for (String s: sources) | |
| list.add(getSource(s)); | |
| return list; | |
| } | |
| JavaFileObject getSource(final String source) { | |
| return SimpleJavaFileObject.forSource(getURIFromSource(source), | |
| source); | |
| } | |
| void callTask(List<String> options, List<JavaFileObject> files) { | |
| out.print("compile: "); | |
| if (options != null) { | |
| for (String o: options) { | |
| if (o.length() > 64) { | |
| o = o.substring(0, 32) + "..." + o.substring(o.length() - 32); | |
| } | |
| out.print(" " + o); | |
| } | |
| } | |
| for (JavaFileObject f: files) | |
| out.print(" " + f.getName()); | |
| out.println(); | |
| CompilationTask t = compiler.getTask(out, fileManager, null, options, null, files); | |
| boolean ok = t.call(); | |
| if (!ok) | |
| error("compilation failed"); | |
| } | |
| enum Mode { EQUALS, CONTAINS, STARTS_WITH, ENDS_WITH }; | |
| void checkFile(StandardLocation l, String path) { | |
| if (!l.isOutputLocation()) { | |
| error("Not an output location: " + l); | |
| return; | |
| } | |
| List<File> files = getLocation(l); | |
| if (files == null) { | |
| error("location is unset: " + l); | |
| return; | |
| } | |
| if (files.size() != 1) | |
| error("unexpected number of entries on " + l + ": " + files.size()); | |
| File f = new File(files.get(0), path); | |
| if (!f.exists()) | |
| error("file not found: " + f); | |
| } | |
| void checkPath(StandardLocation l, Mode m, File expect) { | |
| checkPath(l, m, Arrays.asList(expect)); | |
| } | |
| void checkPath(StandardLocation l, Mode m, List<File> expect) { | |
| List<File> files = getLocation(l); | |
| if (files == null) { | |
| error("location is unset: " + l); | |
| return; | |
| } | |
| switch (m) { | |
| case EQUALS: | |
| if (!Objects.equals(files, expect)) { | |
| error("location does not match the expected files: " + l); | |
| out.println("found: " + files); | |
| out.println("expect: " + expect); | |
| } | |
| break; | |
| case CONTAINS: | |
| int containsIndex = Collections.indexOfSubList(files, expect); | |
| if (containsIndex == -1) { | |
| error("location does not contain the expected files: " + l); | |
| out.println("found: " + files); | |
| out.println("expect: " + expect); | |
| } | |
| break; | |
| case STARTS_WITH: | |
| int startsIndex = Collections.indexOfSubList(files, expect); | |
| if (startsIndex != 0) { | |
| error("location does not start with the expected files: " + l); | |
| out.println("found: " + files); | |
| out.println("expect: " + expect); | |
| } | |
| break; | |
| case ENDS_WITH: | |
| int endsIndex = Collections.lastIndexOfSubList(files, expect); | |
| if (endsIndex != files.size() - expect.size()) { | |
| error("location does not end with the expected files: " + l); | |
| out.println("found: " + files); | |
| out.println("expect: " + expect); | |
| } | |
| break; | |
| } | |
| } | |
| List<File> getLocation(StandardLocation l) { | |
| Iterable<? extends File> iter = fileManager.getLocation(l); | |
| if (iter == null) | |
| return null; | |
| List<File> files = new ArrayList<>(); | |
| for (File f: iter) | |
| files.add(f); | |
| return files; | |
| } | |
| void setLocation(StandardLocation l, File... files) throws IOException { | |
| fileManager.setLocation(l, Arrays.asList(files)); | |
| } | |
| void setLocation(StandardLocation l, List<File> files) throws IOException { | |
| fileManager.setLocation(l, files); | |
| } | |
| void writeFile(File dir, String path, String body) throws IOException { | |
| try (FileWriter w = new FileWriter(new File(dir, path))) { | |
| w.write(body); | |
| } | |
| } | |
| void writeJar(File jar, File dir, String... entries) throws IOException { | |
| try (JarOutputStream j = new JarOutputStream(Files.newOutputStream(jar.toPath()))) { | |
| for (String entry: entries) { | |
| j.putNextEntry(new JarEntry(entry)); | |
| j.write(Files.readAllBytes(dir.toPath().resolve(entry))); | |
| } | |
| } | |
| } | |
| private static final Pattern packagePattern | |
| = Pattern.compile("package\\s+(((?:\\w+\\.)*)(?:\\w+))"); | |
| private static final Pattern classPattern | |
| = Pattern.compile("(?:public\\s+)?(?:class|enum|interface)\\s+(\\w+)"); | |
| private static URI getURIFromSource(String source) { | |
| String packageName = null; | |
| Matcher matcher = packagePattern.matcher(source); | |
| if (matcher.find()) { | |
| packageName = matcher.group(1).replace(".", "/"); | |
| } | |
| matcher = classPattern.matcher(source); | |
| if (matcher.find()) { | |
| String className = matcher.group(1); | |
| String path = ((packageName == null) ? "" : packageName + "/") + className + ".java"; | |
| return URI.create("myfo:///" + path); | |
| } else { | |
| throw new Error("Could not extract the java class " | |
| + "name from the provided source"); | |
| } | |
| } | |
| File createDir(String path) { | |
| File dir = new File(path); | |
| dir.mkdirs(); | |
| return dir; | |
| } | |
| JavaCompiler compiler; | |
| StandardJavaFileManager fileManager; | |
| /** | |
| * Map for recording which standard locations have been tested. | |
| */ | |
| EnumSet<StandardLocation> tested = EnumSet.noneOf(StandardLocation.class); | |
| /** | |
| * Logging stream. Used directly with test and for getTask calls. | |
| */ | |
| final PrintWriter out = new PrintWriter(System.err, true); | |
| /** | |
| * Count of errors so far. | |
| */ | |
| int errors; | |
| void error(String message) { | |
| errors++; | |
| out.println("Error: " + message); | |
| } | |
| } |