INRIA/spoon

[Bug]: Parsing Java sources with module-info.java fails

Closed this issue · 3 comments

Describe the bug

I am using the Spoon java lib to parse Java sources. When I add a module-info.java file with content module dummy.module {requires error.reporting.java;} parsing fails with the following exception:

Caused by: spoon.compiler.ModelBuildingException: error.reporting.java cannot be resolved to a module at /path/to/project/src/main/java/module-info.java:1

The library jar com.exasol:error-reporting-java I am referencing contains a module-info.class (see source). I configure the library JAR using Spoon's Environment.setSourceClasspath().

Using an empty module-info.java file without requires works, Spoon parses the sources without a problem.

Does Spoon support the module path at all? I didn't find any relevant configuration option in Environment.

Source code you are trying to analyze/transform

// src/main/java/module-info.java:
module dummy.module {
    requires error.reporting.java;
}

// src/main/java/mypkg/Main.java:
class Dummy {
}

Source code for your Spoon processing

final SpoonAPI spoon = new Launcher();
final var environment = spoon.getEnvironment();
environment.setSourceClasspath(this.classPath);
environment.setNoClasspath(false);
environment.setComplianceLevel(11);
for (final Path path : pathsToCrawl) {
    spoon.addInputResource(path.toString());
}
spoon.buildModel();

Actual output

Caused by: spoon.compiler.ModelBuildingException: error.reporting.java cannot be resolved to a module at /path/to/project/src/main/java/module-info.java:1
    at spoon.support.compiler.jdt.JDTBasedSpoonCompiler.report (JDTBasedSpoonCompiler.java:640)
    at spoon.support.compiler.jdt.JDTBasedSpoonCompiler.reportProblems (JDTBasedSpoonCompiler.java:622)
    at spoon.support.compiler.jdt.JDTBasedSpoonCompiler.build (JDTBasedSpoonCompiler.java:119)
    at spoon.support.compiler.jdt.JDTBasedSpoonCompiler.build (JDTBasedSpoonCompiler.java:100)
    at spoon.Launcher.buildModel (Launcher.java:782)

Expected output

Successful parsing of Java sources

Spoon Version

10.3.0

JVM Version

OpenJDK Runtime Environment Temurin-17.0.6+10 (build 17.0.6+10)

What operating system are you using?

macOS Ventura 13.4.1

This basically comes down to the fact that JDT doesn't look for module-info files for jars in the classpath. For jars in the module path, this works fine.

However, Spoon currently doesn't have a direct way to add module-path sources, means we need to expand the API in this area. From my very early experiments, this likely means we'll have breaking changes in some form, so I assume a proper solution will take a while. If you look for a quick fix, I adapted your example to something that's somewhat hacky but allows you to use a module path with spoon:

    final Launcher spoon = new Launcher(); // we use getModelBuilder which isn't present in the SpoonAPI interface
    final var environment = spoon.getEnvironment();
    // note: Create an array `myJars` that contains the paths to the jar files and use it for classpath and module path. Adjust for your needs
    String[] myJars = {"./error-reporting-java-1.0.1.jar"};
    environment.setSourceClasspath(myJars);
    environment.setNoClasspath(false);
    environment.setComplianceLevel(11);
    for (final Path path : pathsToCrawl) {
      spoon.addInputResource(path.toString());
    }
    SpoonModelBuilder modelBuilder = spoon.getModelBuilder();
    ClasspathOptions<?> classpathOptions = new ClasspathOptions<>().encoding(environment.getEncoding().displayName()).classpath(environment.getSourceClasspath());
    ComplianceOptions<?> complianceOptions = new ComplianceOptions<>().compliance(environment.getComplianceLevel());
    AdvancedOptions<?> advancedOptions = new AdvancedOptions<>().preserveUnusedVars().continueExecution().enableJavadoc();
    SourceOptions<?> sourceOptions = new SourceOptions<>().sources(((JDTBasedSpoonCompiler) modelBuilder).getSource().getAllJavaFiles());
    class ModuleAwareJDTBuilder extends JDTBuilderImpl {
      @Override
      public String[] build() {
        // we reuse the original arguments but append the module path
        String[] original = super.build();
        String[] copy = Arrays.copyOf(original, original.length + 2);
        copy[original.length] = "--module-path";
        copy[original.length + 1] = String.join(File.pathSeparator, myJars);
        return copy;
      }
    }
    modelBuilder.build(new ModuleAwareJDTBuilder()
        .classpathOptions(classpathOptions)
        .complianceOptions(complianceOptions)
        .advancedOptions(advancedOptions)
        .sources(sourceOptions)
    );
    System.out.println(spoon.getModel().getAllModules()); // use spoon.getModel() now instead of spoon.buildModel()

(There are more ways to achieve that, overriding the build() method just seems very simple while keeping the other code close to the original)

@SirYwell Wow, you are really an expert!
Thank you very much for your helpful example, I will try to include it in our code.

I successfully updated our code based on your example and it works. It's important to add jars only to classpath OR modulepath. Adding them to both will cause an error.

Thanks again for your help!