Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java compiler API ClassLoader

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;
      }
}
like image 843
Pavel S. Avatar asked Apr 28 '11 13:04

Pavel S.


1 Answers

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.

like image 55
Norswap Avatar answered Oct 11 '22 16:10

Norswap