/* * Copyright (c) 2014, 2016, 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 new SimpleJavaFileObject(getURIFromSource(source), JavaFileObject.Kind.SOURCE) { @Override public CharSequence getCharContent(boolean ignoreEncodingErrors) { return 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); } }