Thursday, April 14, 2011

Programmatic Compilation with Java

Till date we all know that Java compilation is not a runtime business. Write code, compile and generate byte code, deploy and run it on any jvm. But this restriction is removed from java 6. Now compilation is no more a pre-runtime business. Using APIs provided by java 6, we can generate classes runtime and compile to generate class files, which can be used further.Let us see the important classes we need in writing a programmatic compiler.

javax.tools.Diagnostic: Helps to receive compilation results.

javax.tools.DiagnosticListener: Listener can be configured to receive diagnostic results. If there is no listener configured then results will be delivered through System.err.

javax.tools.JavaCompiler: These compilers will be invoked from program. One of the implementation will be used to do the job.

javax.tools.CompilationTask: Does actual compilation business.

javax.tools.JavaFileManager: It manages the source and class files.

javax.tools.JavaFileObject: Represents source and class files.

There are multiple implementations of these interfaces which serve specific purpose. Let us take a look at an example and understand the details.

In this example, we write two classes.

TestClass.java: This is the class to be compiled.

CodeCompiler.java: Does the compilation business.

In addition to this we have one more task. Let us check if the generated class file has three methods overridden – equals, hashCode and toString. Our coding standard expects this in all classes. Here also we take support of javax.tools package.

TestClass.java

public class TestClass {

public static void main(String[] args) {
TestClass testClass = new TestClass();
testClass.processString("String");
}

private void processString(String str){
for(int count=0; count<10;count++){
String myStr = "";
myStr = str;
}
System.out.println("Finished." + str);
}
}

 

CodeCompiler.java

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;

import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import javax.tools.JavaCompiler.CompilationTask;
import sun.tools.java.ClassFile;

public class CodeCompiler {

public static void main(String[] args) {
try{
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileManager = compiler.getStandardFileManager(
null, null, null);
Iterable compilationUnits1 = fileManager
.getJavaFileObjects("D:\\TestClass.java");
CompilationTask task = compiler.getTask(null, fileManager, null, null,
null, compilationUnits1);

// Perform the compilation task.
task.call();
fileManager.close();
ClassFile cf = new ClassFile(new File("TestClass.class"));
validateDefaultMethods(cf);

}catch(IOException ioe){
ioe.printStackTrace();
}
}

protected static void validateDefaultMethods(ClassFile clazzInfo) {
boolean hasEquals = false;
boolean hasHashCode = false;
boolean hasToString = false;
for (Method method : clazzInfo.getClass().getMethods()) {
String methodName = method.getName();
Class[] types = method.getParameterTypes();
if ("equals".equals(methodName) && types.length == 1) {
if ("java.lang.Object".equals(types[0])) {
hasEquals = true;
}
} else if ("hashCode".equals(methodName) &&
types.length == 0) {
hasHashCode = true;
}else if("toString".equals(methodName)&&
types.length == 0){
hasToString = true;

}
}
if(hasEquals&&hasHashCode&&hasToString){
System.out.println("Valid Code");
}else{
System.out.println("Invalid Code");
}
}

}

 

You may have to add tools.jar explicitly to your class path to use ClassFile class.

The class file will be generated in class path with same package structure if the destination is not specified.

What Else Can be Done?

1. Generate a java file on file server and compile it.

2. Generate a java file string and compile it.

3. Generate java files, compile and generate a jar file out of such class files.

4. You can write your own ide using this feature.




No comments:

Post a Comment

Chitika