Renato
Renato

Reputation: 13690

How to get the JavaCompiler to use a provided classLoader to find a class?

I am using this code from an old IBM blog post about how to compile and use Java classes at runtime. The code mostly works great (and is quite well written, by the way), but unfortunately for me, it won't work in one of my use cases where the class being compiled refers to another class that can only be provided by the classLoader provided to the CharSequenceCompiler (from the blog post), not by the application classLoader.

To be more specific, The ClassLoader I pass into CharSequenceCompiler is a OSGi classLoader.

The bundle that owns this classLoader can find and return a class, say Foo.

class Foo { public static String FOO = "F"; }

I know this works if you do classLoader.findClass("Foo"); because when I call this from the debugger it works.

Now, from the class I compile at runtime, say Dynamic, I need to use Foo... so I pass the Foo bundle's ClassLoader to CharSequenceCompiler, then ask it to compile Dynamic:

class Dynamic { public static String D = Foo.FOO; }

This causes the following error:

error: cannot find symbol
Foo.FOO;
^
symbol:   variable Foo

If Foo is in the same project as CharSequenceCompiler, then it works... so it's clearly a problem loading the class from the right class loader.

I have debugged this code for days (or evenings, tbh) and can't find out why the classLoader I provide to the compiler never even gets asked about this class...

The FileManager is asked to list() the resources in each package, but even when I use the debugger to manually add the FileObject to the returned list, it still won't work.

As the debugger cannot penetrate the native classes used by javac internally, I cannot progress anymore on this... does anyone have inside knowledge of the compiler that could explain what's going on?

Upvotes: 2

Views: 1416

Answers (2)

Renato
Renato

Reputation: 13690

I have figured this out after a long battle.

The problem with pretty much all implementations I found of in-memory java compilers based on the java.tools API is that, even though they let you pass in a ClassLoader to load the classes you compile, the classLoader is used only to load new classes, but not to obtain classes that can be used in the Java code being compiled.

For this reason, my use case (as explained in the question) would not work with the code shown in the IBM blog post (or with other projects like OpenHFT Java-Runtime-Compiler).

If you want the classes loaded by the ClassLoader you give to the compiler (which are not visible in the application ClassLoader) to be usable by the class being compiled, you need two things.

First, the ClassLoader classes must be enumerable. This can then be used by the JavaFileManager to implement the list() method properly. For each package, this method must return which classes the ClassLoader could load if required.

Secondly, you need to be able to build JavaFileObjects from the ClassLoader resources, because this is the type of the objects you must return. To do this, you need to ask the ClassLoader for the class's bytecode stream (use getResourceAsStream("Class.class")) and then just create a JavaFileObjectImpl. Basically, this in pseudo-code:

fileObject = new JavaFileObjectImpl( pathMinusDotClass, JavaFileObject.Kind.CLASS );
fileObject.openOutputStream()
    .write( classLoader.getInputStream( path ) );

Now, the compiler will know which classes it can load from the provided classLoader and everything works.

I implemented this in a real compiler on my OSGiaaS project, which is not released yet but I plan to do it soon (writing in July 2016)... it includes a Java command in its shell which can run arbitrary Java code, and that's why I needed to get this working.

Upvotes: 3

Mac
Mac

Reputation: 538

What is the benefits of using real java compiler. Isn't byte code generation option for you ?

In example: Byte Buddy or cglib

Upvotes: 0

Related Questions