I am trying to use Java Compiler API to compile some java class. That class imports some packages from the jar files which can be loaded by context ClassLoader, let's call him X, which is NOT the system classloader. When I run the compilation, the compiler complains about not recognizing the imports. I have tried to specify the fileManager to pass the classloader, but it does not help.
When compile method is called, it first prints "CLASS LOADED", so the context ClassLoader CAN find the dependency class. However, the compilation itself fails (I get "Compilation FAILED" message) and during the compilation I get errors like this:
/path/to/my/Source.java:3: package my.dependency does not exist import my.dependency.MyClass; ^
What am I doing wrong? What's the correct way to pass custom classloader to the compilationTask? I can't extract the URLs from the ClassLoader since it's not URLClassLoader.
My methods are here:
public void compile(List<File> filesToCompile) {
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager stdFileManager =
compiler.getStandardFileManager(null, null, null);
Iterable<? extends JavaFileObject> fileObjects = stdFileManager
.getJavaFileObjectsFromFiles(filesToCompile);
FileManagerImpl fileManager = new FileManagerImpl(stdFileManager);
CompilationTask task = compiler.getTask(null, fileManager, null, null, null, fileObjects);
Boolean result = task.call();
if (result == true) {
System.out.println("Compilation has succeeded");
} else {
System.out.println("Compilation FAILED");
}
}
private final class FileManagerImpl extends ForwardingJavaFileManager<JavaFileManager> {
public FileManagerImpl(JavaFileManager fileManager) {
super(fileManager);
}
@Override
public ClassLoader getClassLoader(JavaFileManager.Location location) {
ClassLoader def = getContextClassLoader();
try {
def.loadClass("my.dependency.MyClass");
System.out.println("CLASS LOADED");
} catch (ClassNotFoundException ex) {
System.out.println("NOT LOADED");
}
return def;
}
}
The main point is that, while a class loader loads classes, javac will call JavaFileManager#list()
to get a listing of all the files in a package.
So to use a custom class loader you need to modify (or extend) it to override JavaFileManager#list()
. Hopefully you can reuse some of the logic used for class loading.
You might want to use your own implementations of JavaFileObject
to represent class objects. You will then need to override JavaFileManager#inferBinaryName()
(else the javac version will crash). Your implementations of JavaFileObject
also needs to override (at least) JavaFileObject#openInputStream
.
Here are some pointers: http://atamur.blogspot.be/2009/10/using-built-in-javacompiler-with-custom.html
Also, don't make your life harder than it should and extend ForwardingJavaFileManager
and SimpleJavaFileObject
.
For reference, here is an example implementation:
@Override public Iterable<JavaFileObject> list(Location location,
String packageName, Set<JavaFileObject.Kind> kinds, boolean recurse)
throws IOException
{
Iterable<JavaFileObject> stdResults =
fileManager.list(location, packageName, kinds, recurse);
if (location != StandardLocation.CLASS_PATH
|| !kinds.contains(JavaFileObject.Kind.CLASS))
{
return stdResults;
}
Set<JavaFileObject> additional = pkgObjects.get(packageName);
if (additional == null || additional.isEmpty()) {
return stdResults;
}
List<JavaFileObject> out = new ArrayList<>();
for (JavaFileObject obj : additional) {
out.add(obj);
}
for (JavaFileObject obj : stdResults) {
out.add(obj);
}
return out;
}
Where pkgObjects
is a map from package names to JavaFileObject
. The way you fill this map depends on how your class loader works.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With