diff --git a/src/main/java/spoon/SpoonModelBuilder.java b/src/main/java/spoon/SpoonModelBuilder.java index 5e66c50e24d..0f505679443 100644 --- a/src/main/java/spoon/SpoonModelBuilder.java +++ b/src/main/java/spoon/SpoonModelBuilder.java @@ -242,6 +242,25 @@ interface InputType { */ void setSourceClasspath(String... classpath); + /** + * Gets the module path used for sourcing the input modules. + * The returned list is immutable and does not contain null values. + * + * @return A list of strings representing the module path. Each string element + * is the path to a directory or a module jar file. + */ + List getSourceModulePath(); + + /** + * Sets the module path that is used to build/compile the input sources. + * This is the equivalent to the {@code --module-path} option of {@code javac} and {@code java} executables. + * + * @param sourceModulePath The new module path to be set. Each string element + * should be the path to a directory or a module jar file. + * @throws NullPointerException if the argument is null or an element of the list is null. + */ + void setSourceModulePath(List sourceModulePath); + /** * Gets the classpath that is used to build the template sources. * diff --git a/src/main/java/spoon/compiler/Environment.java b/src/main/java/spoon/compiler/Environment.java index 048a8472253..fc8db85e651 100644 --- a/src/main/java/spoon/compiler/Environment.java +++ b/src/main/java/spoon/compiler/Environment.java @@ -25,6 +25,7 @@ import java.io.File; import java.nio.charset.Charset; +import java.util.List; import java.util.function.Supplier; import org.jspecify.annotations.Nullable; @@ -308,6 +309,25 @@ public interface Environment { */ void setSourceClasspath(String[] sourceClasspath); + /** + * Gets the module path used for sourcing the input modules. + * The returned list is immutable and does not contain null values. + * + * @return A list of strings representing the module path. Each string element + * is the path to a directory or a module jar file. + */ + List getSourceModulePath(); + + /** + * Sets the module path that is used to build/compile the input sources. + * This is the equivalent to the {@code --module-path} option of {@code javac} and {@code java} executables. + * + * @param sourceModulePath The new module path to be set. Each string element + * should be the path to a directory or a module jar file. + * @throws NullPointerException if the argument is null or an element of the list is null. + */ + void setSourceModulePath(List sourceModulePath); + /** * Sets the option "noclasspath", use with caution (see explanation below). * diff --git a/src/main/java/spoon/compiler/builder/ClasspathOptions.java b/src/main/java/spoon/compiler/builder/ClasspathOptions.java index 605e4663441..c55e906f9e2 100644 --- a/src/main/java/spoon/compiler/builder/ClasspathOptions.java +++ b/src/main/java/spoon/compiler/builder/ClasspathOptions.java @@ -8,6 +8,7 @@ package spoon.compiler.builder; import java.io.File; +import java.util.List; public class ClasspathOptions> extends Options { public ClasspathOptions() { @@ -30,6 +31,30 @@ public T classpath(String... classpaths) { return classpath(join(File.pathSeparator, classpaths)); } + /** + * Adds the specified module path to the list of arguments. + * + * @param modulePath the module path to add + * @return the instance of the class calling this method + */ + public T modulePath(String modulePath) { + args.add("--module-path"); + args.add(modulePath); + return myself; + } + + /** + * Adds the specified list of module paths to the list of arguments. + * + * @param modulePaths the list of module paths to add + * @return the instance of the class calling this method + */ + public T modulePath(List modulePaths) { + args.add("--module-path"); + args.add(String.join(File.pathSeparator, modulePaths)); + return myself; + } + public T bootclasspath(String bootclasspath) { if (bootclasspath == null) { return myself; diff --git a/src/main/java/spoon/support/StandardEnvironment.java b/src/main/java/spoon/support/StandardEnvironment.java index e870002e521..cf7c3e31ce2 100644 --- a/src/main/java/spoon/support/StandardEnvironment.java +++ b/src/main/java/spoon/support/StandardEnvironment.java @@ -91,6 +91,7 @@ public void setPrettyPrintingMode(PRETTY_PRINTING_MODE prettyPrintingMode) { private int warningCount = 0; private String[] sourceClasspath = null; + private List sourceModulePath = List.of(); private boolean preserveLineNumbers = false; @@ -489,6 +490,16 @@ public void setSourceClasspath(String[] sourceClasspath) { this.inputClassloader = null; } + @Override + public List getSourceModulePath() { + return this.sourceModulePath; + } + + @Override + public void setSourceModulePath(List sourceModulePath) { + this.sourceModulePath = List.copyOf(sourceModulePath); // implicit null check on list and its elements + } + private void verifySourceClasspath(String[] sourceClasspath) throws InvalidClassPathException { for (String classPathElem : sourceClasspath) { // preconditions diff --git a/src/main/java/spoon/support/compiler/jdt/JDTBasedSpoonCompiler.java b/src/main/java/spoon/support/compiler/jdt/JDTBasedSpoonCompiler.java index 9036c180e0c..c2caf5162e5 100644 --- a/src/main/java/spoon/support/compiler/jdt/JDTBasedSpoonCompiler.java +++ b/src/main/java/spoon/support/compiler/jdt/JDTBasedSpoonCompiler.java @@ -317,6 +317,16 @@ public void setSourceClasspath(String... classpath) { getEnvironment().setSourceClasspath(classpath); } + @Override + public List getSourceModulePath() { + return getEnvironment().getSourceModulePath(); + } + + @Override + public void setSourceModulePath(List sourceModulePath) { + getEnvironment().setSourceModulePath(sourceModulePath); + } + @Override public String[] getTemplateClasspath() { return templateClasspath; @@ -333,7 +343,7 @@ public Factory getFactory() { } protected boolean buildSources(JDTBuilder jdtBuilder) { - return buildUnitsAndModel(jdtBuilder, sources, getSourceClasspath(), ""); + return buildUnitsAndModel(jdtBuilder, sources, getSourceClasspath(), getSourceModulePath(), ""); } protected JDTBatchCompiler createBatchCompiler() { @@ -353,7 +363,7 @@ protected JDTBatchCompiler createBatchCompiler(InputType... types) { } protected boolean buildTemplates(JDTBuilder jdtBuilder) { - CompilationUnitDeclaration[] units = buildUnits(jdtBuilder, templates, getTemplateClasspath(), "template "); + CompilationUnitDeclaration[] units = buildUnits(jdtBuilder, templates, getTemplateClasspath(), List.of(), "template "); buildModel(units, factory.Templates()); return true; } @@ -366,8 +376,13 @@ protected boolean buildTemplates(JDTBuilder jdtBuilder) { * @param debugMessagePrefix Useful to help debugging * @return true if the model has been built without errors */ - protected boolean buildUnitsAndModel(JDTBuilder jdtBuilder, SpoonFolder sourcesFolder, String[] classpath, String debugMessagePrefix) { - CompilationUnitDeclaration[] units = buildUnits(jdtBuilder, sourcesFolder, classpath, debugMessagePrefix); + protected boolean buildUnitsAndModel( + JDTBuilder jdtBuilder, + SpoonFolder sourcesFolder, + String[] classpath, + List modulePath, + String debugMessagePrefix) { + CompilationUnitDeclaration[] units = buildUnits(jdtBuilder, sourcesFolder, classpath, modulePath, debugMessagePrefix); // here we build the model in the template factory buildModel(units, factory); @@ -385,7 +400,12 @@ protected boolean buildUnitsAndModel(JDTBuilder jdtBuilder, SpoonFolder sourcesF * @param debugMessagePrefix Useful to help debugging * @return All compilationUnitDeclaration from JDT found in source folder */ - protected CompilationUnitDeclaration[] buildUnits(JDTBuilder jdtBuilder, SpoonFolder sourcesFolder, String[] classpath, String debugMessagePrefix) { + protected CompilationUnitDeclaration[] buildUnits( + JDTBuilder jdtBuilder, + SpoonFolder sourcesFolder, + String[] classpath, + List modulePath, + String debugMessagePrefix) { List sourceFiles = Collections.unmodifiableList(sourcesFolder.getAllJavaFiles()); if (sourceFiles.isEmpty()) { return EMPTY_RESULT; @@ -395,7 +415,10 @@ protected CompilationUnitDeclaration[] buildUnits(JDTBuilder jdtBuilder, SpoonFo String[] args; if (jdtBuilder == null) { - ClasspathOptions classpathOptions = new ClasspathOptions().encoding(this.getEnvironment().getEncoding().displayName()).classpath(classpath); + ClasspathOptions classpathOptions = new ClasspathOptions<>() + .encoding(this.getEnvironment().getEncoding().displayName()) + .classpath(classpath) + .modulePath(modulePath); ComplianceOptions complianceOptions = new ComplianceOptions().compliance(javaCompliance); if (factory.getEnvironment().isPreviewFeaturesEnabled()) { complianceOptions.enablePreview(); diff --git a/src/main/java/spoon/support/compiler/jdt/JDTSnippetCompiler.java b/src/main/java/spoon/support/compiler/jdt/JDTSnippetCompiler.java index b0980c244cd..a9ac5d9d5d5 100644 --- a/src/main/java/spoon/support/compiler/jdt/JDTSnippetCompiler.java +++ b/src/main/java/spoon/support/compiler/jdt/JDTSnippetCompiler.java @@ -66,7 +66,7 @@ public boolean build(JDTBuilder builder) { @Override protected boolean buildSources(JDTBuilder jdtBuilder) { - return buildUnitsAndModel(jdtBuilder, sources, getSourceClasspath(), "snippet "); + return buildUnitsAndModel(jdtBuilder, sources, getSourceClasspath(), getSourceModulePath(), "snippet "); } @Override diff --git a/src/test/java/spoon/LauncherTest.java b/src/test/java/spoon/LauncherTest.java index 1a79e61961d..61d6e6a9cf1 100644 --- a/src/test/java/spoon/LauncherTest.java +++ b/src/test/java/spoon/LauncherTest.java @@ -25,11 +25,13 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import org.junit.jupiter.api.Test; import spoon.compiler.Environment; import spoon.reflect.CtModel; +import spoon.reflect.declaration.CtModule; import spoon.reflect.visitor.DefaultJavaPrettyPrinter; import spoon.support.JavaOutputProcessor; import spoon.support.compiler.VirtualFile; @@ -161,4 +163,18 @@ public void testClasspathURLWithSpaces() throws MalformedURLException { assertTrue(model.getAllTypes().stream().anyMatch(ct -> ct.getQualifiedName().equals("Foo")), "CtTxpe 'Foo' not present in model"); } + + @Test + void testModulesInJars() { + Launcher spoon = new Launcher(); + Environment environment = spoon.getEnvironment(); + environment.setSourceModulePath(List.of("src/test/resources/modules/error-reporting-java-1.0.1.jar")); + environment.setNoClasspath(false); + environment.setComplianceLevel(11); + spoon.addInputResource(Path.of("src/test/resources/modules/5324").toString()); + CtModel ctModel = assertDoesNotThrow(spoon::buildModel); + // unnamed and dummy.module + assertEquals(2, ctModel.getAllModules().size()); + + } } diff --git a/src/test/java/spoon/support/compiler/jdt/JDTBasedSpoonCompilerTest.java b/src/test/java/spoon/support/compiler/jdt/JDTBasedSpoonCompilerTest.java index 518992b11f4..66dd300d556 100644 --- a/src/test/java/spoon/support/compiler/jdt/JDTBasedSpoonCompilerTest.java +++ b/src/test/java/spoon/support/compiler/jdt/JDTBasedSpoonCompilerTest.java @@ -32,7 +32,7 @@ public void testOrderCompilationUnits() { launcher.addInputResource("./src/main/java"); JDTBasedSpoonCompiler spoonCompiler = (JDTBasedSpoonCompiler) launcher.getModelBuilder(); - CompilationUnitDeclaration[] compilationUnitDeclarations = spoonCompiler.buildUnits(null, spoonCompiler.sources, spoonCompiler.getSourceClasspath(), ""); + CompilationUnitDeclaration[] compilationUnitDeclarations = spoonCompiler.buildUnits(null, spoonCompiler.sources, spoonCompiler.getSourceClasspath(), spoonCompiler.getSourceModulePath(), ""); List compilationUnitDeclarations1 = spoonCompiler.sortCompilationUnits(compilationUnitDeclarations); diff --git a/src/test/resources/modules/5324/module-info.java b/src/test/resources/modules/5324/module-info.java new file mode 100644 index 00000000000..49baec1cf33 --- /dev/null +++ b/src/test/resources/modules/5324/module-info.java @@ -0,0 +1,3 @@ +module dummy.module { + requires error.reporting.java; +} \ No newline at end of file diff --git a/src/test/resources/modules/5324/mypkg/Dummy.java b/src/test/resources/modules/5324/mypkg/Dummy.java new file mode 100644 index 00000000000..cef738e07de --- /dev/null +++ b/src/test/resources/modules/5324/mypkg/Dummy.java @@ -0,0 +1,4 @@ +package mypkg; + +class Dummy { +} \ No newline at end of file diff --git a/src/test/resources/modules/error-reporting-java-1.0.1.jar b/src/test/resources/modules/error-reporting-java-1.0.1.jar new file mode 100644 index 00000000000..2595837c503 Binary files /dev/null and b/src/test/resources/modules/error-reporting-java-1.0.1.jar differ