| /* | |
| * Copyright (c) 2015, 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 8154283 8167320 8171098 8172809 8173068 8173117 8176045 8177311 8241519 8297988 | |
| * @summary tests for multi-module mode compilation | |
| * @library /tools/lib | |
| * @modules | |
| * jdk.compiler/com.sun.tools.javac.api | |
| * jdk.compiler/com.sun.tools.javac.code | |
| * jdk.compiler/com.sun.tools.javac.main | |
| * jdk.compiler/com.sun.tools.javac.processing | |
| * jdk.compiler/com.sun.tools.javac.util | |
| * @build toolbox.ToolBox toolbox.JarTask toolbox.JavacTask ModuleTestBase | |
| * @run main EdgeCases | |
| */ | |
| import java.io.BufferedWriter; | |
| import java.io.Writer; | |
| import java.nio.file.Files; | |
| import java.nio.file.FileSystems; | |
| import java.nio.file.Path; | |
| import java.nio.file.Paths; | |
| import java.util.Arrays; | |
| import java.util.HashSet; | |
| import java.util.List; | |
| import java.util.Objects; | |
| import java.util.Set; | |
| import javax.annotation.processing.AbstractProcessor; | |
| import javax.annotation.processing.RoundEnvironment; | |
| import javax.annotation.processing.SupportedAnnotationTypes; | |
| import javax.annotation.processing.SupportedOptions; | |
| import javax.lang.model.SourceVersion; | |
| import javax.lang.model.element.Element; | |
| import javax.lang.model.element.ModuleElement; | |
| import javax.lang.model.element.ModuleElement.RequiresDirective; | |
| import javax.lang.model.element.PackageElement; | |
| import javax.lang.model.element.TypeElement; | |
| import javax.lang.model.util.ElementFilter; | |
| import javax.lang.model.util.Elements; | |
| import javax.tools.JavaCompiler; | |
| import javax.tools.JavaFileObject; | |
| import javax.tools.StandardJavaFileManager; | |
| import javax.tools.ToolProvider; | |
| import com.sun.source.tree.CompilationUnitTree; | |
| import com.sun.source.util.TaskEvent; | |
| import com.sun.source.util.TaskListener; | |
| //import com.sun.source.util.JavacTask; // conflicts with toolbox.JavacTask | |
| import com.sun.tools.javac.api.JavacTaskImpl; | |
| import com.sun.tools.javac.code.Symbol.ModuleSymbol; | |
| import com.sun.tools.javac.code.Symtab; | |
| import java.util.ArrayList; | |
| import java.util.concurrent.atomic.AtomicBoolean; | |
| import java.util.function.Consumer; | |
| import javax.lang.model.element.ModuleElement.DirectiveKind; | |
| import toolbox.JarTask; | |
| import toolbox.JavacTask; | |
| import toolbox.Task; | |
| import toolbox.Task.Expect; | |
| import toolbox.Task.OutputKind; | |
| public class EdgeCases extends ModuleTestBase { | |
| public static void main(String... args) throws Exception { | |
| new EdgeCases().runTests(); | |
| } | |
| @Test | |
| public void testAddExportUndefinedModule(Path base) throws Exception { | |
| Path src = base.resolve("src"); | |
| tb.writeJavaFiles(src, "package test; import undefPackage.Any; public class Test {}"); | |
| Path classes = base.resolve("classes"); | |
| tb.createDirectories(classes); | |
| List<String> log = new JavacTask(tb) | |
| .options("--add-exports", "undefModule/undefPackage=ALL-UNNAMED", | |
| "-XDrawDiagnostics") | |
| .outdir(classes) | |
| .files(findJavaFiles(src)) | |
| .run(Task.Expect.FAIL) | |
| .writeAll() | |
| .getOutputLines(Task.OutputKind.DIRECT); | |
| List<String> expected = Arrays.asList("- compiler.warn.module.for.option.not.found: --add-exports, undefModule", | |
| "Test.java:1:34: compiler.err.doesnt.exist: undefPackage", | |
| "1 error", "1 warning"); | |
| if (!expected.equals(log)) | |
| throw new Exception("expected output not found: " + log); | |
| } | |
| @Test | |
| public void testModuleSymbolOutterMostClass(Path base) throws Exception { | |
| JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); | |
| try (StandardJavaFileManager fm = compiler.getStandardFileManager(null, null, null)) { | |
| Path moduleSrc = base.resolve("module-src"); | |
| Path m1 = moduleSrc.resolve("m1x"); | |
| tb.writeJavaFiles(m1, "module m1x { }"); | |
| Iterable<? extends JavaFileObject> files = fm.getJavaFileObjects(findJavaFiles(moduleSrc)); | |
| com.sun.source.util.JavacTask task = | |
| (com.sun.source.util.JavacTask) compiler.getTask(null, fm, null, null, null, files); | |
| task.analyze(); | |
| ModuleSymbol msym = (ModuleSymbol) task.getElements().getModuleElement("m1x"); | |
| msym.outermostClass(); | |
| } | |
| } | |
| @Test | |
| public void testParseEnterAnalyze(Path base) throws Exception { | |
| JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); | |
| try (StandardJavaFileManager fm = compiler.getStandardFileManager(null, null, null)) { | |
| Path moduleSrc = base.resolve("module-src"); | |
| Path m1 = moduleSrc.resolve("m1x"); | |
| tb.writeJavaFiles(m1, "module m1x { }", | |
| "package p;", | |
| "package p; class T { }"); | |
| Path classes = base.resolve("classes"); | |
| Iterable<? extends JavaFileObject> files = fm.getJavaFileObjects(findJavaFiles(moduleSrc)); | |
| List<String> options = Arrays.asList("-d", classes.toString(), "-Xpkginfo:always"); | |
| JavacTaskImpl task = (JavacTaskImpl) compiler.getTask(null, fm, null, options, null, files); | |
| Iterable<? extends CompilationUnitTree> parsed = task.parse(); | |
| Iterable<? extends Element> entered = task.enter(parsed); | |
| Iterable<? extends Element> analyzed = task.analyze(entered); | |
| Iterable<? extends JavaFileObject> generatedFiles = task.generate(analyzed); | |
| Set<String> generated = new HashSet<>(); | |
| for (JavaFileObject jfo : generatedFiles) { | |
| generated.add(jfo.getName()); | |
| } | |
| Set<String> expected = new HashSet<>( | |
| Arrays.asList(Paths.get("testParseEnterAnalyze", "classes", "p", "package-info.class").toString(), | |
| Paths.get("testParseEnterAnalyze", "classes", "module-info.class").toString(), | |
| Paths.get("testParseEnterAnalyze", "classes", "p", "T.class").toString()) | |
| ); | |
| if (!Objects.equals(expected, generated)) | |
| throw new AssertionError("Incorrect generated files: " + generated); | |
| } | |
| } | |
| @Test | |
| public void testModuleImplicitModuleBoundaries(Path base) throws Exception { | |
| Path src = base.resolve("src"); | |
| Path src_m1 = src.resolve("m1x"); | |
| tb.writeJavaFiles(src_m1, | |
| "module m1x { exports api1; }", | |
| "package api1; public class Api1 { public void call() { } }"); | |
| Path src_m2 = src.resolve("m2x"); | |
| tb.writeJavaFiles(src_m2, | |
| "module m2x { requires m1x; exports api2; }", | |
| "package api2; public class Api2 { public static api1.Api1 get() { return null; } }"); | |
| Path src_m3 = src.resolve("m3x"); | |
| tb.writeJavaFiles(src_m3, | |
| "module m3x { requires m2x; }", | |
| "package test; public class Test { { api2.Api2.get().call(); api2.Api2.get().toString(); } }"); | |
| Path classes = base.resolve("classes"); | |
| tb.createDirectories(classes); | |
| String log = new JavacTask(tb) | |
| .options("-XDrawDiagnostics", | |
| "--module-source-path", src.toString()) | |
| .outdir(classes) | |
| .files(findJavaFiles(src)) | |
| .run(Task.Expect.FAIL) | |
| .writeAll() | |
| .getOutput(Task.OutputKind.DIRECT); | |
| if (!log.contains("Test.java:1:52: compiler.err.not.def.access.class.intf.cant.access.reason: call(), api1.Api1, api1, (compiler.misc.not.def.access.does.not.read: m3x, api1, m1x)") || | |
| !log.contains("Test.java:1:76: compiler.err.not.def.access.class.intf.cant.access: toString(), java.lang.Object")) | |
| throw new Exception("expected output not found"); | |
| } | |
| @Test | |
| public void testAssignClassToAutomaticModule(Path base) throws Exception { | |
| //check that if a ClassSymbol belongs to an automatic module, it is properly assigned and not | |
| //duplicated when being accessed through a classfile. | |
| Path automaticSrc = base.resolve("automaticSrc"); | |
| tb.writeJavaFiles(automaticSrc, "package api1; public class Api1 {}"); | |
| Path automaticClasses = base.resolve("automaticClasses"); | |
| tb.createDirectories(automaticClasses); | |
| String automaticLog = new JavacTask(tb) | |
| .outdir(automaticClasses) | |
| .files(findJavaFiles(automaticSrc)) | |
| .run() | |
| .writeAll() | |
| .getOutput(Task.OutputKind.DIRECT); | |
| if (!automaticLog.isEmpty()) | |
| throw new Exception("expected output not found: " + automaticLog); | |
| Path modulePath = base.resolve("module-path"); | |
| Files.createDirectories(modulePath); | |
| Path automaticJar = modulePath.resolve("a-1.0.jar"); | |
| new JarTask(tb, automaticJar) | |
| .baseDir(automaticClasses) | |
| .files("api1/Api1.class") | |
| .run(); | |
| Path src = base.resolve("src"); | |
| Path src_m2 = src.resolve("m2x"); | |
| tb.writeJavaFiles(src_m2, | |
| "module m2x { requires a; exports api2; }", | |
| "package api2; public class Api2 { public static api1.Api1 get() { return null; } }"); | |
| Path src_m3 = src.resolve("m3x"); | |
| tb.writeJavaFiles(src_m3, | |
| "module m3x { requires a; requires m2x; }", | |
| "package test; public class Test { { api2.Api2.get(); api1.Api1 a1; } }"); | |
| Path classes = base.resolve("classes"); | |
| tb.createDirectories(classes); | |
| new JavacTask(tb) | |
| .options("--module-path", modulePath.toString(), | |
| "--module-source-path", src.toString()) | |
| .outdir(classes) | |
| .files(findJavaFiles(src_m2)) | |
| .run() | |
| .writeAll(); | |
| new JavacTask(tb) | |
| .options("--module-path", modulePath.toString(), | |
| "--module-source-path", src.toString()) | |
| .outdir(classes) | |
| .files(findJavaFiles(src_m3)) | |
| .run() | |
| .writeAll(); | |
| } | |
| @Test | |
| public void testEmptyImplicitModuleInfo(Path base) throws Exception { | |
| Path src = base.resolve("src"); | |
| Path src_m1 = src.resolve("m1x"); | |
| Files.createDirectories(src_m1); | |
| try (Writer w = Files.newBufferedWriter(src_m1.resolve("module-info.java"))) {} | |
| tb.writeJavaFiles(src_m1, | |
| "package test; public class Test {}"); | |
| Path classes = base.resolve("classes"); | |
| tb.createDirectories(classes); | |
| List<String> log = new JavacTask(tb) | |
| .options("--source-path", src_m1.toString(), | |
| "-XDrawDiagnostics") | |
| .outdir(classes) | |
| .files(findJavaFiles(src_m1.resolve("test"))) | |
| .run(Task.Expect.FAIL) | |
| .writeAll() | |
| .getOutputLines(OutputKind.DIRECT); | |
| List<String> expected = Arrays.asList( | |
| "- compiler.err.cant.access: module-info, (compiler.misc.bad.source.file.header: module-info.java, (compiler.misc.file.does.not.contain.module))", | |
| "1 error"); | |
| if (!expected.equals(log)) { | |
| throw new AssertionError("Unexpected output: " + log); | |
| } | |
| tb.writeJavaFiles(src_m1, | |
| "module m1x {}"); | |
| new JavacTask(tb) | |
| .options("--source-path", src_m1.toString()) | |
| .outdir(classes) | |
| .files(findJavaFiles(src_m1.resolve("test"))) | |
| .run() | |
| .writeAll(); | |
| } | |
| @Test | |
| public void testClassPackageClash(Path base) throws Exception { | |
| Path src = base.resolve("src"); | |
| Path src_m1 = src.resolve("m1x"); | |
| tb.writeJavaFiles(src_m1, | |
| "module m1x { exports test.m1x; }", | |
| """ | |
| package test.m1x; | |
| public class Test {} | |
| """); | |
| Path src_m2 = src.resolve("m2x"); | |
| tb.writeJavaFiles(src_m2, | |
| "module m2x { requires m1x; }", | |
| """ | |
| package test; | |
| public class m1x {} | |
| """); | |
| Path classes = base.resolve("classes"); | |
| tb.createDirectories(classes); | |
| List<String> log = new JavacTask(tb) | |
| .options("--module-source-path", src.toString(), | |
| "-XDrawDiagnostics") | |
| .outdir(classes) | |
| .files(findJavaFiles(src)) | |
| .run(Task.Expect.FAIL) | |
| .writeAll() | |
| .getOutputLines(Task.OutputKind.DIRECT); | |
| List<String> expected = Arrays.asList( | |
| "m1x.java:2:8: compiler.err.clash.with.pkg.of.same.name: kindname.class, test.m1x", | |
| "1 error" | |
| ); | |
| if (!expected.equals(log)) { | |
| throw new IllegalStateException(log.toString()); | |
| } | |
| } | |
| @Test | |
| public void testImplicitJavaBase(Path base) throws Exception { | |
| Path src = base.resolve("src"); | |
| Path src_java_base = src.resolve("java.base"); | |
| Files.createDirectories(src_java_base); | |
| tb.writeJavaFiles(src_java_base, "module java.base { exports java.lang; }"); | |
| tb.writeJavaFiles(src_java_base, | |
| "package java.lang; public class Object {}"); | |
| Path classes = base.resolve("classes"); | |
| tb.createDirectories(classes); | |
| //module-info from source: | |
| new JavacTask(tb) | |
| .options("-sourcepath", src_java_base.toString()) | |
| .outdir(classes) | |
| .files(findJavaFiles(src_java_base.resolve("java").resolve("lang").resolve("Object.java"))) | |
| .run() | |
| .writeAll(); | |
| //module-info from class: | |
| if (!Files.exists(classes.resolve("module-info.class"))) { | |
| throw new AssertionError("module-info.class not created!"); | |
| } | |
| new JavacTask(tb) | |
| .outdir(classes) | |
| .files(findJavaFiles(src_java_base.resolve("java").resolve("lang").resolve("Object.java"))) | |
| .run() | |
| .writeAll(); | |
| //broken module-info.class: | |
| Files.newOutputStream(classes.resolve("module-info.class")).close(); | |
| List<String> log = new JavacTask(tb) | |
| .options("-XDrawDiagnostics") | |
| .outdir(classes) | |
| .files(findJavaFiles(src_java_base.resolve("java").resolve("lang").resolve("Object.java"))) | |
| .run(Expect.FAIL) | |
| .writeAll() | |
| .getOutputLines(OutputKind.DIRECT); | |
| List<String> expected = Arrays.asList( | |
| "- compiler.err.cant.access: <error>.module-info, (compiler.misc.bad.class.file.header: module-info.class, (compiler.misc.bad.class.truncated.at.offset: 0))", | |
| "1 error"); | |
| if (!expected.equals(log)) { | |
| throw new AssertionError("Unexpected output: " + log); | |
| } | |
| //broken module-info.java: | |
| Files.delete(classes.resolve("module-info.class")); | |
| try (Writer out = Files.newBufferedWriter(src_java_base.resolve("module-info.java"))) { | |
| out.write("class Broken {}"); | |
| } | |
| log = new JavacTask(tb) | |
| .options("-sourcepath", src_java_base.toString(), | |
| "-XDrawDiagnostics") | |
| .outdir(classes) | |
| .files(findJavaFiles(src_java_base.resolve("java").resolve("lang").resolve("Object.java"))) | |
| .run(Expect.FAIL) | |
| .writeAll() | |
| .getOutputLines(OutputKind.DIRECT); | |
| expected = Arrays.asList("X"); | |
| if (expected.equals(log)) { | |
| throw new AssertionError("Unexpected output: " + log); | |
| } | |
| } | |
| @Test | |
| public void testModuleInfoNameMismatchSource(Path base) throws Exception { | |
| Path src = base.resolve("src"); | |
| Path m1 = src.resolve("m1x"); | |
| Files.createDirectories(m1); | |
| tb.writeJavaFiles(m1, "module other { }", | |
| "package test; public class Test {}"); | |
| Path classes = base.resolve("classes"); | |
| tb.createDirectories(classes); | |
| List<String> log = new JavacTask(tb) | |
| .options("--module-source-path", src.toString(), | |
| "-XDrawDiagnostics") | |
| .outdir(classes) | |
| .files(findJavaFiles(m1.resolve("test").resolve("Test.java"))) | |
| .run(Expect.FAIL) | |
| .writeAll() | |
| .getOutputLines(OutputKind.DIRECT); | |
| List<String> expected = Arrays.asList( | |
| "module-info.java:1:1: compiler.err.module.name.mismatch: other, m1x", | |
| "- compiler.err.cant.access: m1x.module-info, (compiler.misc.cant.resolve.modules)", | |
| "2 errors"); | |
| if (!expected.equals(log)) { | |
| throw new AssertionError("Unexpected output: " + log); | |
| } | |
| } | |
| @Test | |
| public void testModuleInfoNameMismatchClass(Path base) throws Exception { | |
| Path src = base.resolve("src"); | |
| Files.createDirectories(src); | |
| tb.writeJavaFiles(src, "module other { }", | |
| "package test; public class Test {}"); | |
| Path classes = base.resolve("classes"); | |
| Path m1Classes = classes.resolve("m1x"); | |
| tb.createDirectories(m1Classes); | |
| new JavacTask(tb) | |
| .outdir(m1Classes) | |
| .files(findJavaFiles(src)) | |
| .run() | |
| .writeAll() | |
| .getOutputLines(OutputKind.DIRECT); | |
| Path src2 = base.resolve("src2"); | |
| Files.createDirectories(src2); | |
| tb.writeJavaFiles(src2, "module use { requires m1x; }"); | |
| Path classes2 = base.resolve("classes2"); | |
| tb.createDirectories(classes2); | |
| List<String> log = new JavacTask(tb) | |
| .options("--module-path", classes.toString(), | |
| "-XDrawDiagnostics") | |
| .outdir(classes2) | |
| .files(findJavaFiles(src2)) | |
| .run(Expect.FAIL) | |
| .writeAll() | |
| .getOutputLines(OutputKind.DIRECT); | |
| List<String> expected = Arrays.asList( | |
| "- compiler.err.cant.access: m1x.module-info, (compiler.misc.bad.class.file.header: module-info.class, (compiler.misc.module.name.mismatch: other, m1x))", | |
| "module-info.java:1:1: compiler.err.module.not.found: m1x", | |
| "2 errors"); | |
| if (!expected.equals(log)) { | |
| throw new AssertionError("Unexpected output: " + log); | |
| } | |
| } | |
| @Test | |
| public void testGetDirectivesComplete(Path base) throws Exception { | |
| JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); | |
| JavacTaskImpl task = (JavacTaskImpl) compiler.getTask(null, null, null, null, null, null); | |
| Symtab syms = Symtab.instance(task.getContext()); | |
| syms.java_base.getDirectives(); | |
| } | |
| @Test | |
| public void testPackageInModuleInfo(Path base) throws Exception { | |
| Path src = base.resolve("src"); | |
| Files.createDirectories(src); | |
| tb.writeJavaFiles(src, "package p; module foo { }"); | |
| Path classes = base.resolve("classes"); | |
| tb.createDirectories(classes); | |
| List<String> log = new JavacTask(tb) | |
| .options("-XDrawDiagnostics", "-XDshould-stop.ifError=FLOW") | |
| .outdir(classes) | |
| .files(findJavaFiles(src)) | |
| .run(Expect.FAIL) | |
| .writeAll() | |
| .getOutputLines(OutputKind.DIRECT); | |
| List<String> expected = Arrays.asList( | |
| "module-info.java:1:1: compiler.err.no.pkg.in.module-info.java", | |
| "1 error"); | |
| if (!expected.equals(log)) { | |
| throw new AssertionError("Unexpected output: " + log); | |
| } | |
| } | |
| @Test | |
| public void testInvisibleClassVisiblePackageClash(Path base) throws Exception { | |
| Path src = base.resolve("src"); | |
| Path src_m1 = src.resolve("m1x"); | |
| tb.writeJavaFiles(src_m1, | |
| "module m1x { }", | |
| """ | |
| package m1x; | |
| import m1x.a.*; public class Test { A a; } | |
| """, | |
| """ | |
| package m1x.a; | |
| public class A { } | |
| """); | |
| Path src_m2 = src.resolve("m2x"); | |
| tb.writeJavaFiles(src_m2, | |
| "module m2x { }", | |
| """ | |
| package m1x; | |
| public class a { public static class A { } } | |
| """); | |
| Path classes = base.resolve("classes"); | |
| tb.createDirectories(classes); | |
| new JavacTask(tb) | |
| .options("--module-source-path", src.toString(), | |
| "-XDrawDiagnostics") | |
| .outdir(classes) | |
| .files(findJavaFiles(src)) | |
| .run() | |
| .writeAll(); | |
| } | |
| @Test | |
| public void testStripUnknownRequired(Path base) throws Exception { | |
| Path src = base.resolve("src"); | |
| Path src_m1 = src.resolve("m1x"); | |
| tb.writeJavaFiles(src_m1, | |
| "module m1x { }"); | |
| Path src_m2 = src.resolve("m2x"); | |
| tb.writeJavaFiles(src_m2, | |
| "module m2x { }"); | |
| Path src_m3 = src.resolve("m3x"); | |
| tb.writeJavaFiles(src_m3, | |
| "module m3x { }"); | |
| Path src_m4 = src.resolve("m4x"); | |
| tb.writeJavaFiles(src_m4, | |
| "module m4x { }"); | |
| Path src_test = src.resolve("test"); | |
| tb.writeJavaFiles(src_test, | |
| "module test { requires m1x; requires m2x; requires java.base; requires m3x; requires m4x; }"); | |
| Path src_compile = src.resolve("compile"); | |
| tb.writeJavaFiles(src_compile, | |
| "module compile { exports p to test; }", | |
| "package p; public class Test { }"); | |
| Path classes = base.resolve("classes"); | |
| tb.createDirectories(classes); | |
| List<String> log = new JavacTask(tb) | |
| .options("-processor", ListRequires.class.getName(), | |
| "--module-source-path", src.toString(), | |
| "--limit-modules", "compile", | |
| "-XDaccessInternalAPI=true") | |
| .outdir(classes) | |
| .files(findJavaFiles(src_compile)) | |
| .run(Expect.FAIL) | |
| .writeAll() | |
| .getOutputLines(Task.OutputKind.STDOUT); | |
| List<String> expected = Arrays.asList( | |
| "from directives:", | |
| "java.base", | |
| "from requires:", | |
| "java.base" | |
| ); | |
| if (!Objects.equals(log, expected)) | |
| throw new AssertionError("Unexpected output: " + log); | |
| } | |
| @SupportedAnnotationTypes("*") | |
| @SupportedOptions("expectedEnclosedElements") | |
| public static final class ListRequires extends AbstractProcessor { | |
| private int round; | |
| @Override | |
| public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { | |
| if (round++ == 0) { | |
| ModuleElement compileE = processingEnv.getElementUtils().getModuleElement("compile"); | |
| ModuleElement testE = ElementFilter.exportsIn(compileE.getDirectives()).get(0).getTargetModules().get(0); | |
| System.out.println("from directives:"); | |
| for (RequiresDirective rd : ElementFilter.requiresIn(testE.getDirectives())) { | |
| System.out.println(rd.getDependency().getQualifiedName()); | |
| } | |
| System.out.println("from requires:"); | |
| for (RequiresDirective rd : ((ModuleSymbol) testE).requires) { | |
| System.out.println(rd.getDependency().getQualifiedName()); | |
| } | |
| } | |
| return false; | |
| } | |
| @Override | |
| public SourceVersion getSupportedSourceVersion() { | |
| return SourceVersion.latest(); | |
| } | |
| } | |
| @Test | |
| public void testOnDemandCompletionModuleInfoJava(Path base) throws Exception { | |
| Path src = base.resolve("src"); | |
| Path src_m1 = src.resolve("m1x"); | |
| tb.writeJavaFiles(src_m1, | |
| "@Deprecated module m1x { }"); | |
| Path src_m2 = src.resolve("m2x"); | |
| tb.writeJavaFiles(src_m2, | |
| "module m2x { requires m1x; }"); | |
| Path src_m3 = src.resolve("m3x"); | |
| tb.writeJavaFiles(src_m3, | |
| "module m3x { requires m2x; requires m1x; }"); | |
| Path classes = base.resolve("classes"); | |
| tb.createDirectories(classes); | |
| List<String> log; | |
| List<String> expected; | |
| log = new JavacTask(tb) | |
| .options("--module-source-path", src.toString()) | |
| .outdir(classes) | |
| .files(findJavaFiles(src_m1)) | |
| .run() | |
| .writeAll() | |
| .getOutputLines(Task.OutputKind.DIRECT); | |
| expected = Arrays.asList(""); | |
| if (!expected.equals(log)) { | |
| throw new IllegalStateException(log.toString()); | |
| } | |
| log = new JavacTask(tb) | |
| .options("--module-source-path", src.toString(), | |
| "-XDrawDiagnostics", | |
| "-Xlint:deprecation") | |
| .outdir(classes) | |
| .files(findJavaFiles(src_m3)) | |
| .run() | |
| .writeAll() | |
| .getOutputLines(Task.OutputKind.DIRECT); | |
| expected = Arrays.asList( | |
| "module-info.java:1:23: compiler.warn.has.been.deprecated.module: m1x", | |
| "module-info.java:1:37: compiler.warn.has.been.deprecated.module: m1x", | |
| "2 warnings" | |
| ); | |
| if (!expected.equals(log)) { | |
| throw new IllegalStateException(log.toString()); | |
| } | |
| } | |
| @Test | |
| public void testUnnamedPackage(Path base) throws Exception { | |
| List<String> out; | |
| List<String> expected; | |
| //-source 8: | |
| Path src8 = base.resolve("src8"); | |
| Files.createDirectories(src8); | |
| tb.writeJavaFiles(src8, | |
| "package test; public class Test {}"); | |
| Path classes = base.resolve("classes"); | |
| tb.createDirectories(classes); | |
| out = new JavacTask(tb) | |
| .options("--source-path", src8.toString(), | |
| "-processor", UnnamedPackageProcessor.class.getName(), | |
| "-source", "8") | |
| .outdir(classes) | |
| .files(findJavaFiles(src8)) | |
| .run() | |
| .writeAll() | |
| .getOutputLines(OutputKind.STDOUT); | |
| expected = Arrays.asList("noModule"); | |
| if (!expected.equals(out)) { | |
| throw new AssertionError("Unexpected output: " + out); | |
| } | |
| //-source 9, unnamed: | |
| Path srcUnnamed = base.resolve("srcUnnamed"); | |
| Files.createDirectories(srcUnnamed); | |
| tb.writeJavaFiles(srcUnnamed, | |
| "public class Test {}"); | |
| Path classesUnnamed = base.resolve("classesUnnamed"); | |
| tb.createDirectories(classesUnnamed); | |
| out = new JavacTask(tb) | |
| .options("--source-path", srcUnnamed.toString(), | |
| "-processor", UnnamedPackageProcessor.class.getName()) | |
| .outdir(classesUnnamed) | |
| .files(findJavaFiles(srcUnnamed)) | |
| .run() | |
| .writeAll() | |
| .getOutputLines(OutputKind.STDOUT); | |
| expected = Arrays.asList("unnamedModule"); | |
| if (!expected.equals(out)) { | |
| throw new AssertionError("Unexpected output: " + out); | |
| } | |
| //-source 9, named: | |
| Path srcNamed = base.resolve("srcNamed"); | |
| Files.createDirectories(srcNamed); | |
| tb.writeJavaFiles(srcNamed, | |
| "module m {}", | |
| "public class Test {}"); | |
| Path classesNamed = base.resolve("classesNamed"); | |
| tb.createDirectories(classesNamed); | |
| out = new JavacTask(tb) | |
| .options("--source-path", srcNamed.toString(), | |
| "-classpath", "", | |
| "-processorpath", System.getProperty("test.class.path"), | |
| "-processor", UnnamedPackageProcessor.class.getName()) | |
| .outdir(classesNamed) | |
| .files(findJavaFiles(srcNamed)) | |
| .run() | |
| .writeAll() | |
| .getOutputLines(OutputKind.STDOUT); | |
| expected = Arrays.asList("m"); | |
| if (!expected.equals(out)) { | |
| throw new AssertionError("Unexpected output: " + out); | |
| } | |
| //-source 9, conflict: | |
| Path srcNamed2 = base.resolve("srcNamed2"); | |
| Path srcNamed2m1 = srcNamed2.resolve("m1x"); | |
| Files.createDirectories(srcNamed2m1); | |
| tb.writeJavaFiles(srcNamed2m1, | |
| "module m1x {}", | |
| "public class Test {}"); | |
| Path srcNamed2m2 = srcNamed2.resolve("m2x"); | |
| Files.createDirectories(srcNamed2m2); | |
| tb.writeJavaFiles(srcNamed2m2, | |
| "module m2x {}", | |
| "public class Test {}"); | |
| Path classesNamed2 = base.resolve("classesNamed2"); | |
| tb.createDirectories(classesNamed2); | |
| out = new JavacTask(tb) | |
| .options("--module-source-path", srcNamed2.toString(), | |
| "-classpath", "", | |
| "-processorpath", System.getProperty("test.class.path"), | |
| "-processor", UnnamedPackageProcessor.class.getName(), | |
| "-XDshould-stop.ifError=FLOW") | |
| .outdir(classesNamed2) | |
| .files(findJavaFiles(srcNamed2)) | |
| .run(Expect.FAIL) | |
| .writeAll() | |
| .getOutputLines(OutputKind.STDOUT); | |
| expected = Arrays.asList("null", | |
| "m1x: true", | |
| "m2x: true"); | |
| if (!expected.equals(out)) { | |
| throw new AssertionError("Unexpected output: " + out); | |
| } | |
| } | |
| @SupportedAnnotationTypes("*") | |
| public static final class UnnamedPackageProcessor extends AbstractProcessor { | |
| int round = 0; | |
| @Override | |
| public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { | |
| if (round++ != 0) | |
| return false; | |
| Elements elements = processingEnv.getElementUtils(); | |
| PackageElement pe = elements.getPackageElement(""); | |
| if (pe == null) { | |
| System.out.println("null"); | |
| } else { | |
| ModuleElement mod = (ModuleElement) pe.getEnclosingElement(); | |
| if (mod == null) { | |
| System.out.println("noModule"); | |
| } else if (mod.isUnnamed()) { | |
| System.out.println("unnamedModule"); | |
| } else { | |
| System.out.println(mod); | |
| } | |
| } | |
| ModuleElement m1x = elements.getModuleElement("m1x"); | |
| ModuleElement m2x = elements.getModuleElement("m2x"); | |
| if (m1x != null && m2x != null) { | |
| System.out.println("m1x: " + (elements.getPackageElement(m1x, "") != null)); | |
| System.out.println("m2x: " + (elements.getPackageElement(m2x, "") != null)); | |
| } | |
| return false; | |
| } | |
| } | |
| @Test | |
| public void testEmptyInExportedPackage(Path base) throws Exception { | |
| Path src = base.resolve("src"); | |
| Path m = src.resolve("m"); | |
| tb.writeJavaFiles(m, | |
| "module m { exports api; }"); | |
| Path apiFile = m.resolve("api").resolve("Api.java"); | |
| Files.createDirectories(apiFile.getParent()); | |
| try (BufferedWriter w = Files.newBufferedWriter(apiFile)) { | |
| w.write("//no package decl"); | |
| } | |
| Path classes = base.resolve("classes"); | |
| tb.createDirectories(classes); | |
| List<String> log; | |
| List<String> expected = | |
| Arrays.asList("module-info.java:1:20: compiler.err.package.empty.or.not.found: api", | |
| "1 error"); | |
| System.err.println("file explicitly specified:"); | |
| log = new JavacTask(tb) | |
| .options("-XDrawDiagnostics", | |
| "--module-source-path", src.toString()) | |
| .outdir(classes) | |
| .files(findJavaFiles(src)) | |
| .run(Task.Expect.FAIL) | |
| .writeAll() | |
| .getOutputLines(Task.OutputKind.DIRECT); | |
| if (!expected.equals(log)) | |
| throw new Exception("expected output not found: " + log); | |
| System.err.println("file not specified:"); | |
| tb.cleanDirectory(classes); | |
| log = new JavacTask(tb) | |
| .options("-XDrawDiagnostics", | |
| "--module-source-path", src.toString()) | |
| .outdir(classes) | |
| .files(findJavaFiles(m.resolve("module-info.java"))) | |
| .run(Task.Expect.FAIL) | |
| .writeAll() | |
| .getOutputLines(Task.OutputKind.DIRECT); | |
| if (!expected.equals(log)) | |
| throw new Exception("expected output not found: " + log); | |
| } | |
| @Test | |
| public void testJustPackageInExportedPackage(Path base) throws Exception { | |
| Path src = base.resolve("src"); | |
| Path m = src.resolve("m"); | |
| tb.writeJavaFiles(m, | |
| "module m { exports api; }"); | |
| Path apiFile = m.resolve("api").resolve("Api.java"); | |
| Files.createDirectories(apiFile.getParent()); | |
| try (BufferedWriter w = Files.newBufferedWriter(apiFile)) { | |
| w.write("package api;"); | |
| } | |
| Path classes = base.resolve("classes"); | |
| tb.createDirectories(classes); | |
| System.err.println("file explicitly specified:"); | |
| new JavacTask(tb) | |
| .options("-XDrawDiagnostics", | |
| "--module-source-path", src.toString()) | |
| .outdir(classes) | |
| .files(findJavaFiles(src)) | |
| .run() | |
| .writeAll(); | |
| System.err.println("file not specified:"); | |
| tb.cleanDirectory(classes); | |
| new JavacTask(tb) | |
| .options("-XDrawDiagnostics", | |
| "--module-source-path", src.toString()) | |
| .outdir(classes) | |
| .files(findJavaFiles(m.resolve("module-info.java"))) | |
| .run() | |
| .writeAll(); | |
| } | |
| @Test | |
| public void testWrongPackageInExportedPackage(Path base) throws Exception { | |
| Path src = base.resolve("src"); | |
| Path m = src.resolve("m"); | |
| tb.writeJavaFiles(m, | |
| "module m { exports api; }"); | |
| Path apiFile = m.resolve("api").resolve("Api.java"); | |
| Files.createDirectories(apiFile.getParent()); | |
| try (BufferedWriter w = Files.newBufferedWriter(apiFile)) { | |
| w.write("package impl; public class Api { }"); | |
| } | |
| Path classes = base.resolve("classes"); | |
| tb.createDirectories(classes); | |
| List<String> log; | |
| List<String> expected = | |
| Arrays.asList("module-info.java:1:20: compiler.err.package.empty.or.not.found: api", | |
| "1 error"); | |
| System.err.println("file explicitly specified:"); | |
| log = new JavacTask(tb) | |
| .options("-XDrawDiagnostics", | |
| "--module-source-path", src.toString()) | |
| .outdir(classes) | |
| .files(findJavaFiles(src)) | |
| .run(Task.Expect.FAIL) | |
| .writeAll() | |
| .getOutputLines(Task.OutputKind.DIRECT); | |
| if (!expected.equals(log)) | |
| throw new Exception("expected output not found: " + log); | |
| System.err.println("file not specified:"); | |
| tb.cleanDirectory(classes); | |
| log = new JavacTask(tb) | |
| .options("-XDrawDiagnostics", | |
| "--module-source-path", src.toString()) | |
| .outdir(classes) | |
| .files(findJavaFiles(m.resolve("module-info.java"))) | |
| .run(Task.Expect.FAIL) | |
| .writeAll() | |
| .getOutputLines(Task.OutputKind.DIRECT); | |
| if (!expected.equals(log)) | |
| throw new Exception("expected output not found: " + log); | |
| } | |
| @Test | |
| public void testDependOnUnnamedAccessibility(Path base) throws Exception { | |
| Path unnamedSrc = base.resolve("unnamed-src"); | |
| tb.writeJavaFiles(unnamedSrc, | |
| "package p1; public class First { public static p2.Second get() { return null; } }", | |
| "package p2; public class Second { public void test() { } }"); | |
| Path unnamedClasses = base.resolve("unnamed-classes"); | |
| tb.createDirectories(unnamedClasses); | |
| System.err.println("compiling unnamed sources:"); | |
| new JavacTask(tb) | |
| .outdir(unnamedClasses) | |
| .files(findJavaFiles(unnamedSrc)) | |
| .run() | |
| .writeAll(); | |
| //test sources: | |
| Path src = base.resolve("src"); | |
| Path m = src.resolve("m"); | |
| tb.writeJavaFiles(m, | |
| "module m { }", | |
| "package p; public class Test { { p1.First.get().test(); } }"); | |
| Path classes = base.resolve("classes"); | |
| tb.createDirectories(classes); | |
| System.err.println("compiling test module:"); | |
| new JavacTask(tb) | |
| .options("-classpath", unnamedClasses.toString(), | |
| "--add-reads", "m=ALL-UNNAMED") | |
| .outdir(classes) | |
| .files(findJavaFiles(src)) | |
| .run() | |
| .writeAll(); | |
| } | |
| @Test | |
| public void testMisnamedModuleInfoClass(Path base) throws Exception { | |
| Path src = base.resolve("src"); | |
| Path a = src.resolve("a"); | |
| tb.writeJavaFiles(a, | |
| "module a {}"); | |
| Path b = src.resolve("b"); | |
| tb.writeJavaFiles(b, | |
| "module b { uses com.example.c; }"); | |
| Path classes = base.resolve("classes"); | |
| tb.createDirectories(classes); | |
| Path aClasses = classes.resolve("x"); | |
| tb.createDirectories(aClasses); | |
| new JavacTask(tb) | |
| .outdir(aClasses) | |
| .files(findJavaFiles(a)) | |
| .run() | |
| .writeAll(); | |
| Path bClasses = classes.resolve("b"); | |
| tb.createDirectories(bClasses); | |
| List<String> log; | |
| log = new JavacTask(tb) | |
| .outdir(bClasses) | |
| .options("-p", classes.toString(), | |
| "-XDrawDiagnostics") | |
| .files(findJavaFiles(b)) | |
| .run(Task.Expect.FAIL) | |
| .writeAll() | |
| .getOutputLines(Task.OutputKind.DIRECT); | |
| List<String> expected = List.of("module-info.java:1:28: compiler.err.doesnt.exist: com.example", | |
| "1 error"); | |
| if (!expected.equals(log)) | |
| throw new Exception("expected output not found: " + log); | |
| } | |
| @Test //JDK-8297988 | |
| public void testExportedNameCheckFromSourceNoEvent(Path base) throws Exception { | |
| //when validating "exports", javac may parse source(s) from the package to check their | |
| //package name. The AST produced by this parse are thrown away, so listeners should not | |
| //be notified: | |
| Path src = base.resolve("src"); | |
| Path m = src.resolve("m"); | |
| tb.writeJavaFiles(m, | |
| """ | |
| module m { | |
| exports test; | |
| } | |
| """, | |
| """ | |
| package test; | |
| public class Test {} | |
| """, | |
| """ | |
| package impl; | |
| public class Impl { | |
| void t() { | |
| test.Test t; | |
| } | |
| } | |
| """); | |
| Path classes = base.resolve("classes"); | |
| tb.createDirectories(classes); | |
| record TestCase(Path[] files, String... expectedLog){} | |
| String nameSeparator = FileSystems.getDefault().getSeparator(); | |
| TestCase[] testCases = new TestCase[] { | |
| new TestCase(new Path[] {m.resolve("module-info.java")}, | |
| "COMPILATION:started:<none>", | |
| "PARSE:started:testExportedNameCheckFromSourceNoEvent/src/m/module-info.java", | |
| "PARSE:finished:testExportedNameCheckFromSourceNoEvent/src/m/module-info.java", | |
| "ENTER:started:testExportedNameCheckFromSourceNoEvent/src/m/module-info.java", | |
| "ENTER:finished:testExportedNameCheckFromSourceNoEvent/src/m/module-info.java", | |
| "ANALYZE:started:testExportedNameCheckFromSourceNoEvent/src/m/module-info.java", | |
| "ANALYZE:finished:testExportedNameCheckFromSourceNoEvent/src/m/module-info.java", | |
| "COMPILATION:finished:<none>"), | |
| new TestCase(new Path[] {m.resolve("module-info.java"), | |
| m.resolve("impl").resolve("Impl.java")}, | |
| "COMPILATION:started:<none>", | |
| "PARSE:started:testExportedNameCheckFromSourceNoEvent/src/m/module-info.java", | |
| "PARSE:finished:testExportedNameCheckFromSourceNoEvent/src/m/module-info.java", | |
| "PARSE:started:testExportedNameCheckFromSourceNoEvent/src/m/impl/Impl.java", | |
| "PARSE:finished:testExportedNameCheckFromSourceNoEvent/src/m/impl/Impl.java", | |
| "ENTER:started:testExportedNameCheckFromSourceNoEvent/src/m/module-info.java", | |
| "ENTER:started:testExportedNameCheckFromSourceNoEvent/src/m/impl/Impl.java", | |
| "ENTER:finished:testExportedNameCheckFromSourceNoEvent/src/m/module-info.java", | |
| "ENTER:finished:testExportedNameCheckFromSourceNoEvent/src/m/impl/Impl.java", | |
| "ANALYZE:started:testExportedNameCheckFromSourceNoEvent/src/m/module-info.java", | |
| "ANALYZE:finished:testExportedNameCheckFromSourceNoEvent/src/m/module-info.java", | |
| "ANALYZE:started:testExportedNameCheckFromSourceNoEvent/src/m/impl/Impl.java", | |
| "PARSE:started:testExportedNameCheckFromSourceNoEvent/src/m/test/Test.java", | |
| "PARSE:finished:testExportedNameCheckFromSourceNoEvent/src/m/test/Test.java", | |
| "ENTER:started:testExportedNameCheckFromSourceNoEvent/src/m/test/Test.java", | |
| "ENTER:finished:testExportedNameCheckFromSourceNoEvent/src/m/test/Test.java", | |
| "ANALYZE:finished:testExportedNameCheckFromSourceNoEvent/src/m/impl/Impl.java", | |
| "ANALYZE:started:testExportedNameCheckFromSourceNoEvent/src/m/test/Test.java", | |
| "ANALYZE:finished:testExportedNameCheckFromSourceNoEvent/src/m/test/Test.java", | |
| "COMPILATION:finished:<none>") | |
| }; | |
| for (TestCase tc : testCases) { | |
| List<String> log = new ArrayList<>(); | |
| new JavacTask(tb) | |
| .outdir(classes) | |
| .options("--source-path", m.toString(), | |
| "-XDshould-stop.ifNoError=FLOW") | |
| .callback(task -> { | |
| task.addTaskListener(new TaskListener() { | |
| @Override | |
| public void started(TaskEvent e) { | |
| record(e, "started"); | |
| } | |
| @Override | |
| public void finished(TaskEvent e) { | |
| record(e, "finished"); | |
| } | |
| private void record(TaskEvent e, String phase) { | |
| JavaFileObject source = e.getSourceFile(); | |
| String sourceName = source != null ? source.getName() | |
| .replace(nameSeparator, "/") | |
| : "<none>"; | |
| log.add(e.getKind() + ":" + phase + ":" + sourceName); | |
| } | |
| }); | |
| }) | |
| .files(tc.files) | |
| .run() | |
| .writeAll(); | |
| if (!List.of(tc.expectedLog).equals(log)) { | |
| throw new AssertionError("Unexpected log, got: " + log + | |
| ", expected: " + List.of(tc.expectedLog)); | |
| } | |
| } | |
| } | |
| @Test | |
| public void testJavaSEHasRequiresTransitiveJavaBase(Path base) throws Exception { | |
| Path src = base.resolve("src"); | |
| Path a = src.resolve("a"); | |
| tb.writeJavaFiles(a, | |
| "module a { requires java.se; }", | |
| """ | |
| package test; | |
| import module java.se; | |
| public class Test { | |
| ArrayList<String> l; | |
| } | |
| """); | |
| Path classes = base.resolve("classes"); | |
| tb.createDirectories(classes); | |
| AtomicBoolean seenJavaSEDependency = new AtomicBoolean(); | |
| List<String> log; | |
| log = new JavacTask(tb) | |
| .outdir(classes) | |
| .options("-XDrawDiagnostics", "-XDshould-stop.at=FLOW", | |
| "--release", "24") | |
| .callback(verifyJavaSEDependency(true, seenJavaSEDependency)) | |
| .files(findJavaFiles(src)) | |
| .run(Task.Expect.FAIL) | |
| .writeAll() | |
| .getOutputLines(Task.OutputKind.DIRECT); | |
| List<String> expected = List.of( | |
| "Test.java:2:8: compiler.err.feature.not.supported.in.source.plural: (compiler.misc.feature.module.imports), 24, 25", | |
| "1 error"); | |
| if (!expected.equals(log)) | |
| throw new Exception("expected output not found: " + log); | |
| if (!seenJavaSEDependency.get()) { | |
| throw new AssertionError("Didn't find the java.se dependency!"); | |
| } | |
| seenJavaSEDependency.set(false); | |
| new JavacTask(tb) | |
| .outdir(classes) | |
| .options("--enable-preview", | |
| "--source", System.getProperty("java.specification.version")) | |
| .callback(verifyJavaSEDependency(true, seenJavaSEDependency)) | |
| .files(findJavaFiles(src)) | |
| .run(Task.Expect.SUCCESS) | |
| .writeAll() | |
| .getOutputLines(Task.OutputKind.DIRECT); | |
| if (!seenJavaSEDependency.get()) { | |
| throw new AssertionError("Didn't find the java.se dependency!"); | |
| } | |
| } | |
| private Consumer<com.sun.source.util.JavacTask> verifyJavaSEDependency( | |
| boolean expectedTransitive, | |
| AtomicBoolean seenJavaSEDependency) { | |
| return t -> { | |
| t.addTaskListener(new TaskListener() { | |
| @Override | |
| public void finished(TaskEvent e) { | |
| if (e.getKind() == TaskEvent.Kind.ANALYZE) { | |
| ModuleElement javaBase = | |
| t.getElements().getModuleElement("java.base"); | |
| ModuleElement javaSE = | |
| t.getElements().getModuleElement("java.se"); | |
| RequiresDirective requiresJavaBase = | |
| javaSE.getDirectives() | |
| .stream() | |
| .filter(d -> d.getKind() == DirectiveKind.REQUIRES) | |
| .map(d -> (RequiresDirective) d) | |
| .filter(d -> d.getDependency() == javaBase) | |
| .findAny() | |
| .orElseThrow(); | |
| if (requiresJavaBase.isTransitive() != expectedTransitive) { | |
| throw new AssertionError("Expected: " + expectedTransitive + ", " + | |
| "but got: " + requiresJavaBase.isTransitive()); | |
| } | |
| seenJavaSEDependency.set(true); | |
| } | |
| } | |
| }); | |
| }; | |
| } | |
| } |