1

I have developed an application that generates Java source at runtime from an external source. The generated source code implements an interface residing in say org.foo and also calls other classes residing in org.foo. The application compiled the generated Java source to a specific package, say org.foo.generated. The compiler provided by ToolProvider.getSystemJavaCompiler() found the source files using a ForwardingJavaFileManager, compiled the source files and the application was able to run them.

Now I have split up my application in JPMS modules and the compiler is complaining it cannot find the org.foo package where the interface implemented by the generated source code lives. I call the compiler from within the same module (org.foo) as where the generated code lives. The error message looks like this:

package org.foo does not exist

I changed my ForwardingFileManager by adding some methods to lookup modules, as described here. It seems like the compiler, residing in the java.compiler module cannot access the org.foo module. So, I added

--add-reads java.compiler=org.foo

to the command line, but that did not change anything. Any clues? Does somebody have any positive experiences combining runtime generated source files and JPMS with Janino instead of the ToolProvider compiler?

Below is a minimal reproducable example as requested in the comments. The source files can also be retrieved from github:

https://github.com/evg/demo-jpms-compiler.git

I get the following output without the module-info.java:

All files compiled without errors: true
0 diagnostics reported by compiler task

I get the following output with the module-info.java:

All files compiled without errors: false
3 diagnostics reported by compiler task
DIAGNOSTIC /org/foo/generated/MyGreeter.java:5: error: cannot find symbol
public class MyGreeter implements IGreeter {
                                  ^
  symbol: class IGreeter
DIAGNOSTIC /org/foo/generated/MyGreeter.java:3: error: package org.foo does not exist
import org.foo.*;
^
DIAGNOSTIC /org/foo/generated/MyGreeter.java:6: error: method does not override or implement a method from a supertype
  @Override
  ^

The command lines Eclipse uses. Without module-info.java:

C:\Program Files\Java\jdk-11.0.2\bin\javaw.exe -Dfile.encoding=Cp1252 -classpath "H:\git\demo-jpms-compiler\demo-jpms-compiler\target\classes" org.foo.Main

With module-info.java:

C:\Program Files\Java\jdk-11.0.2\bin\javaw.exe -Dfile.encoding=Cp1252 -p "H:\git\demo-jpms-compiler\demo-jpms-compiler\target\classes" -m org.foo/org.foo.Main

The files used:

module-info.java

module org.foo {
  requires java.compiler;
}

Main.java

package org.foo;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import javax.tools.DiagnosticCollector;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.ToolProvider;

public class Main {

    public static void main(String[] args) {
        new Main().run();
    }

    private void run() {
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        Iterable<? extends JavaFileObject> compilationUnits = List.of(new JavaSourceFromString("org.foo.generated.MyGreeter", getJavaSource()));
        JavaCompiler.CompilationTask task = compiler.getTask(null, null, diagnosticCollector , compilerOptions, null, compilationUnits);
        boolean allFilesCompiledWithoutErrors = task.call();
        log("All files compiled without errors: " + allFilesCompiledWithoutErrors);
        log(diagnosticCollector.getDiagnostics().size() + " diagnostics reported by compiler task");
        diagnosticCollector.getDiagnostics().stream().forEach(diagnostic->{
            log("DIAGNOSTIC " + diagnostic);
        });
    }

    private void log(String text) {
        System.out.println(text);
    }

    private String getJavaSource() {
        String[] lines = new String[] {
                "package org.foo.generated;",
                "",
                "import org.foo.*;",
                "",
                "public class MyGreeter implements IGreeter {",
                "  @Override",
                "  public String getGreeting() {",
                "    return \"Hello, generated world\";",
                "  }",
                "",
                "}"
        };
        return Stream.of(lines).collect(Collectors.joining(System.lineSeparator()));
    }

    private DiagnosticCollector<JavaFileObject> diagnosticCollector = new DiagnosticCollector<>();
    private List<String> compilerOptions = Arrays.asList("-Xlint:deprecation");
}

IGreeter.java

package org.foo;

public interface IGreeter {
  public String getGreeting();
}

JavaSourceFromString.java

package org.foo;

import java.net.URI;

import javax.tools.SimpleJavaFileObject;

/**
 * A file object used to represent source coming from a string.
 */
public class JavaSourceFromString extends SimpleJavaFileObject {
    /**
     * The source code of this "file".
     */
    final String code;

    /**
     * Constructs a new JavaSourceFromString.
     * 
     * @param name
     *            the name of the compilation unit represented by this file object
     * @param code
     *            the source code for the compilation unit represented by this file
     *            object
     */
    public JavaSourceFromString(String name, String code) {
        super(URI.create("string:///" + name.replace('.', '/') + Kind.SOURCE.extension), Kind.SOURCE);
        this.code = code;
    }

    @Override
    public CharSequence getCharContent(boolean ignoreEncodingErrors) {
        return code;
    }
}
eddy
  • 63
  • 6
  • Could you provide a [mre] demonstrating the problem, please? – Slaw Feb 01 '20 at 12:13
  • @Slaw A minimal reproducible example has been added – eddy Feb 01 '20 at 13:57
  • 1
    Try using `List.of("-p", System.getProperty("jdk.module.path"))` as the compiler options (to `JavaCompiler#getTask`). Then use `task.addModules(List.of("org.foo"))` before invoking `#call()`. – Slaw Feb 01 '20 at 14:34
  • 1
    And add an `exports org.foo;` directive to your module-info file. – Slaw Feb 01 '20 at 14:34
  • Keep in mind that your runtime generated class is not part of the `org.foo` module. Not with your current setup anyway. – Slaw Feb 01 '20 at 14:35

0 Answers0