On-the-fly compilation in Java6
One of interesting novelty of Java6 is a possibility to access compiler via special API.
Let’s look on this feature a bit closer.
In order to have an access to compilation subsystem we should use classes located at javax.tools package [http://java.sun.com/javase/6/docs/api/javax/tools/package-summary.html].
In the future in this package possibly appear classes to work with different external utilities,
but at this moment we have only access to the compiler.
As simple example let’s look on request to compile:
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
Iterable<SimpleJavaFileObject> srcList = Arrays.asList(new SimpleJavaFileObject[]{
new SimpleJavaFileObject(URI.create("string:///myclass.java"), Kind.SOURCE) {
@Override
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
return "class myclass {}";
}
}
});
JavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
compiler.getTask(null, fileManager, null, null, null, srcList).call();
Let’s try to understand how it’s work:
The compiler access input data not directly but via objects inherited form JavaFileObject.
Therefore depending on needs of developer information for compilation can be received from any place:
network, file, memory; for that we need to implement successor of JavaFileObject.
It is better to do it by inheriting class-gag SimpleJavaFileObject
In our example implementation of SimpleJavaFileObject returns source of class as constant string.
It is slightly harder with output data. There is another abstraction layer called JavaFileManager that is
object factory per se.
Standard file manager that we receive in our example let us work with files on the disk. If you need to place output data
on the network on in the memory you need to override JavaFileManager. It is better to do it inheriting
ForwardingJavaFileManager, this class retarget request to provided during creation file manager.
At the same time you can handle only those request that you need.
Last string of example creates compilation command and execute it at once. As a result of execution we have file
in the execution directory of example with name myclass.class that contains bytecode of appropriate class.
It is noteworthy that classloaders of system know nothing about existence of given class and so call
Class.forName(“myclass”) throws ClassNotFoundException.
Let’s complete our example to get class bytecode as byte array. For that we need to implement our own file manager:
public class JavaMemFileManager extends ForwardingJavaFileManager {
class ClassMemFileObject extends SimpleJavaFileObject {
ByteArrayOutputStream os = new ByteArrayOutputStream();
ClassMemFileObject(String className) {
super(URI.create("mem:///" + className + Kind.CLASS.extension), Kind.CLASS);
}
byte[] getBytes() {
return os.toByteArray();
}
@Override
public OutputStream openOutputStream() throws IOException {
return os;
}
}
private HashMap<String, ClassMemFileObject> classes =
new HashMap<String, ClassMemFileObject>();
public JavaMemFileManager() {
super(ToolProvider.getSystemJavaCompiler().getStandardFileManager(null, null, null));
}
@Override
public JavaFileObject getJavaFileForOutput(Location location,
String className, Kind kind, FileObject sibling) throws IOException {
if (StandardLocation.CLASS_OUTPUT == location && JavaFileObject.Kind.CLASS == kind) {
ClassMemFileObject clazz = new ClassMemFileObject(className);
classes.put(className, clazz);
return clazz;
} else {
return super.getJavaFileForOutput(location, className, kind, sibling);
}
}
public byte[] getClassBytes(String className) {
if (classes.containsKey(className)) {
return classes.get(className).getBytes();
}
return null;
}
}
As you see our file manager override method getJavaFileForOutput that complier calls to receive output file object.
Here we check destination, for new classes it should be StandardLocation.CLASS_OUTPUT and type.
If it correspond to newly compiled class then we create new file object: we save it and pass it to the compiler.
Then we can receive access to the bytecode with method getClassBytes(className) passing the name of the class.
Let us change previous example to use new functionality:
.... JavaFileManager fileManager = new JavaMemFileManager(); compiler.getTask(null, fileManager, null, null, null, srcList).call(); byte[] myClassBytes = ((JavaMemFileManager)fileManager).getClassBytes(“myclass”); ....
And yet one feature of example execution in our case will be absence of file on the disk.
Finally I made a library that makes easier access to Java6 Compiler API.
See details on: [ http://opensource.helion-prime.com/jruntime/ ]











Hi!
The really good possibility! And also your great explanation of its mechanisms. Especially, the illustrations with examples are very useful. Thanks. Will try this definitly.