Java Compiler API
Java Compiler API
07)
This is the first chapter of a two chapter section on how to convert a Java program
into a PNG image which can be compiled and executed like the original source code.
This JavaArt project may appear a bit frivolous, but actually illustrates a wide range
of useful techniques, including Java 2D graphics generation, dynamic compilation,
byte code manipulation, class loaders, reflection, and issues with creating drag-and-
drop applications.
A typical JavaArt image is shown in Figure 1 (enlarged by a factor of 4 to make it
easier to see).
The image’s original Java program is a small drawing application, which can be
executed by dropping the PNG file onto a ExecutePixels desktop icon. ExecutePixels
dynamically translates the image back into Java, compiles it to byte codes, before
passing it to the JVM for execution.
The translation, compilation, and execution are carried out “on-the-fly” without
generating any temporary files on the machine (e.g. Java or class files). This approach
is often used when executing scripts coded in domain-specific languages: very large
speed-ups can be obtained by compiling a script, but it’s often not possible to generate
temporary files in the process due to space or security restrictions on the device.
This chapter looks at two ways of implementing on-the-fly dynamic compilation.
Initially I utilize Java 6’s Compiler API, and then try out the Janino compiler API
(http://www.janino.net/), which focusses on run-time compilation tasks.
One of my Compiler API examples uses Apache Jakarta BCEL (Byte Code
Engineering Library) to examine byte code output. I also employ Java class loaders
and reflection to load compiled code into the JVM and execute it.
The second JavaArt chapter concentrates on the translation process for converting
Java source code into an image (and back again), and some of the ways that the
ExecutePixels can be utilized as a desktop icon which reacts automatically to an
image being dropped on top of it.
JavaArt was partially inspired by the Piet language
(http://www.dangermouse.net/esoteric/piet.html), which is based around ‘painting’ a
program using colored blocks. A block may be any shape and have holes of other
colors inside it. Program instructions are defined by the color transitions from one
block to the next in the image. JavaArt is much more conventional since a
programmer writes an ordinary Java program first, then translates it into an image as a
separate step.
import javax.tools.*;
The catch is that the JRE, where java.exe is located (for most users), does not include
a compiler. When the JRE’s java.exe calls ToolProvider.getSystemJavaCompiler() to
obtain a compiler reference, null is returned instead.
We can give java.exe a helping hand by including the JDK’s compiler in the classpath
used by JCompiler0:
JavaFileObject acts as a file abstraction for Java source input and byte code output
(e.g. to class files). It’s a sub-interface of FileObject, an abstraction for more general
forms of data, such as text files and databases.
JavaFileManager specifies the IO interface for the compiler. Most applications use the
StandardJavaFileManager sub-interface, which handles IO based around java.io.File
objects.
JavaCompiler can use the run() method to immediately compile input (as in my earlier
JCompiler0 example), but greater flexibility is possible by utilizing CompilationTask
objects which can have their own file managers and diagnostics listeners.
DiagnosticListener responds to diagnostic messages generated by the compiler or file
manager. My examples use an alternative approach, the Compiler API’s
DiagnosticCollector class, which collects Diagnostic objects in an easily printable list.
A single Java file is input as a JavaFileObject instance, and class files are output. The
Compiler API’s StandardJavaFileManager is utilized for file management, and
DiagnosticsCollector for reporting problems.
JCompiler’s main() function creates a single compilation task, executes it with
CompilationTask.call(), and prints any diagnostic messages:
// global
private static DiagnosticCollector<JavaFileObject> diagnostics;
makeCompilerTask() handles the creation of the input Java file object, the file
manager, compiler, and diagnostics collector shown in Figure 3.
Compiling Painter.java
Note: Painter.java uses or overrides a deprecated API.
Note: Recompile with -Xlint:deprecation for details.
>
Despite the deprecation warning, Painter.java is compiled, and the resulting class files
are saved in the current directory. Painter can be called in the usual way:
> java Painter
// in makeCompilerTask()
Iterable<String> options =
Arrays.asList( new String[]{"-Xlint:deprecation"} );
return compiler.getTask(null, fileMan, diagnostics,
options, null, fileObjs);
The offending line is a call to Window.show(), rather than the use of setVisible(true).
A comparison with Figure 3 (which gives the components of JCompiler) shows that
only the input JavaFileObject instance is different in StringJCompiler. The string is
stored in a new subclass of JavaFileObject, called StringJFO, so it can be manipulated
by the file manager and compiler.
The main() function of StringJCompiler creates a StringJFO instance, compiles it, and
prints any diagnostics.
The important difference from the previous example is the makeCode() method,
which builds a string Java file object for a class, using args[0] as the class name.
makeCode() returns a StringJFO object (called src), which is passed to
makeCompilerTask() to be used in the compilation task.
The class created in makeCode() contains a main() method which prints “Hello”
followed by the input argument:
The codeStr string is passed to the StringJFO constructor to build the Java file object.
The class name is also supplied as the string’s ‘filename’. A filename is needed for
the class file generated by the compiler to hold the resulting byte codes.
New kinds of Java file objects (such as StringJFO) can be created relatively easily by
subclassing the Compiler API’s SimpleJavaFileObject class which provides default
implementations for most methods in the JavaFileObject interface.
StringJViewer also reports on the byte code contents of the objects in the HashMap,
to confirm that the compiler is working correctly. I utilize Apache Jakarta BCEL
(Byte Code Engineering Library), available from http://jakarta.apache.org/bcel/.
StringJViewer’s main() function carries out three tasks: string Java file object
generation, compilation, and byte code viewing using BCEL.
// global
private static final String CLASS_NAME = "Foo";
// name of the generated class
// global
private static DiagnosticCollector<JavaFileObject> diagnostics;
// a collector for diagnostic messages
System.out.println("Compiling...");
boolean hasCompiled = task.call(); // carry out the compilation
if (!hasCompiled) {
System.out.println("Compilation failed");
System.exit(1);
}
else // list generated class names
System.out.println("Generated Classes: " + store.keySet());
} // end of compileCode()
A successful compilation triggers the listing of the keys in the store HashMap. There
will be one key for each generated class.
// global
private Map<String, JavaFileObject> store =
new HashMap<String, JavaFileObject>();
// maps class names to JFOs containing the classes' byte codes
BCEL has two main parts: a static API for analyzing existing compiled code, and a
dynamic API for creating or transforming class files at runtime. viewClass() utilizes
the static part, which represents a compiled Java class as a JavaClass object.
viewClass() creates a JavaClass instance by parsing the byte codes in a byte[] array
with BCEL’s ClassParser:
viewClass() prints the JavaClass object, which contains enough information for my
needs (to confirm that the Java code string was compiled correctly). The details
printed for the Foo class used in StringJViewer are:
Attribute(s):
SourceFile(Foo.java from StringJFO)
2 methods:
public void <init>()
public static void main(String[] arg0)
The output shows that Foo comes from a StringJFO object, and contains a default
init() method, and a main() function.
It’s quite straightforward to access more information about the methods, including
their byte code instructions, by utilizing BCEL’s Method and Code classes, as in
viewMethods():
viewMethods() can be called from viewClass(), but the call is commented out at
present, since I don’t need that level of detail.
The StringJExecutor example based on Figure 6 discards the BCEL viewing code
from StringJViewer to simplify the program.
The main() function of StringJExecutor is:
// global
private Map<String, JavaFileObject> store;
The overridden findClass() finds byte codes by looking for the named class in the
HashMap, and extracting them from the associated file object. The byte codes are
passed to the JVM via ClassLoader.defineClass().
} // end of findClass()
The loader is initialized with the HashMap store, and then a class called className is
loaded into the application as the Class object cl. Note that ClassLoader.loadClass() is
called, not findClass().
6.2. Reflection
Reflection allows classes which have been loaded into the JVM to be examined and
manipulation at runtime: objects can be constructed, fields accessed, and methods
called.
The starting point for reflection is a java.lang.Class instance. A Class object holds
details on the loaded class’ constructors, fields, and methods, which are accessed via
the Constructor, Field, and Method classes in the java.lang.reflect package.
I want to build a main() method call for the class loaded by ByteClassLoader. That
requires Class.getMethod(), whose prototype is:
Method getMethod(String methodName, Class<?>[] parameterTypes);
parameterTypes are the method's formal parameter types, which in the case of main()
are an array of strings (i.e. the command line arguments). Therefore, a main() method
is represented by:
The arg String passed into runCode() comes from the command line when
StringJExecutor is called. It is supplied as an argument to the main() method of the
byte code class.
Method.invoke() throws exceptions generated by the invoked code wrapped up as
InvocationTargetExceptions, and the actual exceptions can be accessed by calling
getTargetException().
A class called Foo is dynamically generated, compiled, loaded, and executed, without
the creation of any temporary files on the machine. The Foo class prints “Hello”
7. Janino
The preceding sections have shown that Java 6’s Compiler API is quite capable of
programming on-the-fly dynamic compilation, but with some drawbacks. The most
major is the need to include tools.jar in the application since the JRE doesn’t come
with a compiler. This adds nearly 12 MB to the application’s size, and there may be
legal issues with using code from Sun Microsystems’ JDK. However, these issue will
soon disappear with the release of a stable version of OpenJDK
(http://openjdk.java.net/).
A stylistic quibble with the Compiler API is that implementing on-the-fly dynamic
compilation requires quite a lot of coding, especially for such a standard kind of
compilation task.
For these reasons, I decided to investigate the use of an alternative compilation
approach, the Janino API (http://www.janino.net/), developed by Arno Unkrig. The
JAR file for the entire API weighs in at a light 440 KB, and is open source. It offers
more direct support for on-the-fly dynamic compilation than the Compiler API, to the
extent that I don’t need to implement any additional support classes and data
structures.
Janino is used quite widely, for example in Ant, the Apache Commons JCI (a Java
compiler interface), Tomcat, Groovy, and JBoss Rules (formerly Drools).
Perhaps the most significant drawback of Janino is that it’s only compatible with Java
1.4, so programs using most Java 5 and 6 features can’t be compiled. However, static
imports, autoboxing and unboxing, and StringBuilder are available.
I downloaded Janino version 2.5.8 from http://www.janino.net/ as a zipped file
containing the janino.jar library, source code, examples, and API documentation. I
placed the unzipped directory, janino-2.5.8/ on my d: drive.
The call to Janino’s Compiler class has to include its package name to distinguish it
from Java’s java.lang.Compiler class.
This example is compiled and run like so:
The classpath refers to the location of the janino.jar JAR file on my machine.
String codeStr =
"public class " + CLASS_NAME + " {" +
"public static void main(String[] args){" +
"System.out.println(\"Hello \" + args[0]);}}";
String codeStr =
"public static void main(String[] args){" +
"System.out.println(\"Hello \" + args[0]);}";
The class body is the same as the one in my StringExecutor example, but there’s no
need to invent a dummy class name (in StringExecutor I used “Foo”).
Painter
Painter()
-> getContentPane()
-> c.add(new PaintPanel(), "Center")
-> c.add(new Label("Drag the mouse to draw"), BorderLayout.SOUTH)
-> setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE)
-> setSize(300, 150)
-> show()
main(String[] args)
PaintPanel
PaintPanel()
-> addMouseMotionListener(new MouseMotionAdapter() { ... })
mouseDragged(MouseEvent e)
-> e.getX()
-> e.getY()
-> repaint()
paintComponent(Graphics g)
-> g.fillOval(xCoord, yCoord, 4, 4)
This shows that Painter.java contains two classes (Painter and PaintPanel). Painter
contains a constructor and main() method, while PaintPanel has a constructor and an
implementation of paintComponent().
9. Where Next?
This chapter looked at implementing on-the-fly dynamic compilation with Java 6’s
Compiler API, and the Janino compiler API. Class loaders and reflection were
employed for dynamic execution, and BCEL was used to view class information.
Due to the small size of the Janino API, its open source license, and its more direct
support for dynamic compilation, I’ll use Janino from here on.
In the next JavaArt chapter, I’ll concentrate on the translation process for converting
Java source code into an image (and back again). I’ll also examine various ways of
making a drag-and-drop application, which allows a user to drop a JavaArt image
onto a desktop icon, and have it execute automatically.