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;
}
}