Wednesday, April 16, 2014

Metaprogramming in Java : Java code that writes code

Metaprogramming is the ability of the program to write or modify the other program (or themselves) as their data.
It is a programming approach which outputs another program. There are many ways in which metaprogramming can be implemented however we are going to discuss the one way to do so in java using byte code manipulation.

The code that we are going to generate programmatically is below
package in.xebia.metaprogramming;

public class ToBeGeneratedCode implements Cloneable {
   public String name;
   public ToBeGeneratedCode() {
      System.out.println("Constructor");
   }
   public String lowerCaseName() {
      return name.toLowerCase();
  }
}
In this approach we are going to generate a program from our code using byte code manipulation, utilizing javassist library.

Through Javassist Java programs can create a new class file or modify an existing one at run time when the JVM loads. Javassist provides two kinds of API, one is source code level while the other is byte code level. The major advantage of Javassist is that it can ease the developers works who are not comfortable with Java bytecodes hence giving the power of ByteCode instrumentation to everyone. Javassist methods helps in compiling the source code to Java bytecode while instrumenting hence one can avoid writing ByteCode directly.

Javassit is currently heavily used by hibernate framework for constructing proxies which are lazily loaded along with various different frameworks

The starting point of Javassist program is to get a CtClass which represents a compile class through javassist.ClassPool class.
javassist.ClassPool class acts much like a classloader moreover providing us ability to instrument classes.

In Javassist CtClass stands for compile time class. It is a handle for dealing with a class file.CtClass object is obtained through ClassPool in Javassist.

ClassPool pool = ClassPool.getDefault();
CtClass toBeGeneratedCode = pool.makeClass("output.GeneratedCode");


Once we get a CtClass object we add to it whatever we want our generated code to be like.

To add a modifier to our class we use setModifiers method.


toBeGeneratedCode.setModifiers(Modifier.PUBLIC);


Now we will make our newly generated class to implement interface by using addInterface method


toBeGeneratedCode.addInterface(pool.get("java.lang.Cloneable"));


In the similar way constructor, field and methods are added.


// Creating a constructor
CtConstructor toBeGeneratedCodeConstructor = new CtConstructor(
new CtClass[] {}, toBeGeneratedCode);
toBeGeneratedCodeConstructor .setBody("System.out.println(\"Constructor\");");
toBeGeneratedCodeConstructor.setModifiers(Modifier.PUBLIC);
toBeGeneratedCode.addConstructor(toBeGeneratedCodeConstructor);

// Creating a new field
CtField toBeGeneratedField = new CtField(pool.get(String.class
.getName()), "name", toBeGeneratedCode);
toBeGeneratedField.setModifiers(Modifier.PUBLIC);
toBeGeneratedCode.addField(toBeGeneratedField);

// Creating a method
CtMethod toBeGeneratedMethod = CtNewMethod.make(
"public String lowerCaseName() {return name.toLowerCase();}",toBeGeneratedCode);
toBeGeneratedCode.addMethod(toBeGeneratedMethod);


After adding all these we write the code to file


toBeGeneratedCode.writeFile();


Complete code


package in.xebia.metaprogramming;

import java.io.IOException;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import javassist.CtField;
import javassist.CtMethod;
import javassist.CtNewMethod;
import javassist.Modifier;
import javassist.NotFoundException;

public class UsingJavassist {

   public static void main(String[] args) throws NotFoundException,
   IOException, CannotCompileException {
      UsingJavassist ja = new UsingJavassist();
      ja.generateProgram();
   }

   public void generateProgram() throws NotFoundException, IOException,
   CannotCompileException {

      ClassPool pool = ClassPool.getDefault();
      CtClass toBeGeneratedCode = pool
      .makeClass("output.GeneratedCode");
      toBeGeneratedCode.setModifiers(Modifier.PUBLIC);

      // Implementing the Interface
      toBeGeneratedCode.addInterface(pool.get("java.lang.Cloneable"));

      // Creating a constructor
      CtConstructor toBeGeneratedCodeConstructor = new CtConstructor(
      new CtClass[] {}, toBeGeneratedCode);
      toBeGeneratedCodeConstructor
      .setBody("System.out.println(\"Constructor\");");
      toBeGeneratedCodeConstructor.setModifiers(Modifier.PUBLIC);
      toBeGeneratedCode.addConstructor(toBeGeneratedCodeConstructor);

      // Creating a new field
      CtField toBeGeneratedField = new CtField(pool.get(String.class
      .getName()), "name", toBeGeneratedCode);
      toBeGeneratedField.setModifiers(Modifier.PUBLIC);
      toBeGeneratedCode.addField(toBeGeneratedField);

      // Creating a method
      CtMethod toBeGeneratedMethod = CtNewMethod.make(
      "public String lowerCaseName() {return name.toLowerCase();}",
      toBeGeneratedCode);
      toBeGeneratedCode.addMethod(toBeGeneratedMethod);

      toBeGeneratedCode.writeFile();
   }
}


When we run this program a newly generated class file is available to us which can be loaded at run time and is available to use.

1 comment: