Wednesday, January 6, 2010

Use CodeModel to generate Java Source Code

CodeModel is a library that allows you to generate Java source code in a type-safe fashion.

When to use CodeModel:

If you have huge chunk of data in terms of text file or XML and want to generate some Java class based on those data you can use CodeModel. It also helps when your data changes frequently.

To know more about the CodeModel visit following link:

http://fisheye5.atlassian.com/browse/~raw,r=1.601/jaxb-architecture-document/www/doc/com/sun/codemodel/package-summary.html

Giving you small sample how to use CodeModel to generate your own class.



Example: CodeFactory.java

import com.sun.codemodel.JAnnotationUse;
import com.sun.codemodel.JBlock;
import com.sun.codemodel.JClass;
import com.sun.codemodel.JCodeModel;
import com.sun.codemodel.JDefinedClass;
import com.sun.codemodel.JDocComment;
import com.sun.codemodel.JExpr;
import com.sun.codemodel.JMethod;
import com.sun.codemodel.JMod;
import com.sun.codemodel.JPackage;
import com.sun.codemodel.JType;
import com.sun.codemodel.JVar;

/**
*
* @author naman
*/
public class CodeFactory {

// Method to get JType based on any String Value
public JType getTypeDetailsForCodeModel(JCodeModel jCodeModel, String type) {
if (type.equals("Unsigned32")) {
return jCodeModel.LONG;
} else if (type.equals("Unsigned64")) {
return jCodeModel.LONG;
} else if (type.equals("Integer32")) {
return jCodeModel.INT;
} else if (type.equals("Integer64")) {
return jCodeModel.LONG;
} else if (type.equals("Enumerated")) {
return jCodeModel.INT;
} else if (type.equals("Float32")) {
return jCodeModel.FLOAT;
} else if (type.equals("Float64")) {
return jCodeModel.DOUBLE;
} else {
return null;
}
}

// Function to generate CodeModel Class
public void writeCodeModel(String factroyPackage) {
try {

/* Creating java code model classes */
JCodeModel jCodeModel = new JCodeModel();

/* Adding packages here */
JPackage jp = jCodeModel._package(factroyPackage);

/* Giving Class Name to Generate */
JDefinedClass jc = jp._class("GeneratedFactory");

/* Adding annotation for the Class */
jc.annotate(com.myannotation.AnyXYZ.class);

/* Adding class level coment */
JDocComment jDocComment = jc.javadoc();
jDocComment.add("Class Level Java Docs");


/* Adding method to the Class which is public static and returns com.somclass.AnyXYZ.class */
String mehtodName = "myFirstMehtod";
JMethod jmCreate = jc.method(JMod.PUBLIC | JMod.STATIC, com.somclass.AnyXYZ.class, "create" + mehtodName);

/* Addign java doc for method */
jmCreate.javadoc().add("Method Level Java Docs");

/* Adding method body */
JBlock jBlock = jmCreate.body();

/* Defining method parameter */
JType jt = getTypeDetailsForCodeModel(jCodeModel, "Unsigned32");
if (jt != null) {
jmCreate.param(jt, "data");
} else {
jmCreate.param(java.lang.String.class, "data");
}

/* Defining some class Variable in mthod body */
JClass jClassavpImpl = jCodeModel.ref(com.somclass.AnyXYZ.class);
jvarAvpImpl = jBlock.decl(jClassavpImpl, "varName");
jvarAvpImpl.init(JExpr._new(jClassavpImpl));


/* Adding some direct statement */
jBlock.directStatement("varName.setCode(100);");

/* returning varibalbe */
jBlock._return(jvarAvpImpl);

/* Building class at given location */
jCodeModel.build(new File("generated/src"));

} catch (JAXBException ex) {
logger.log(Level.SEVERE, "JAXBException:" + ex);
ex.printStackTrace();
} catch (Exception ex) {
logger.log(Level.SEVERE, "Other Exception which in not catched:" + ex);
ex.printStackTrace();
}
}

// Wirte main mehtod and call writeCodeModel("com.test") function to generate class
}


After running above class it generates GeneratedFactory class under generated/src/com/test folder. It includes all required imports and also format the class as per Java Standard. It generates as described below.


Generated Class: GeneratedFactory.java

package com.test;

import com.myannotation.AnyXYZ;
import com.somclass.AnyXYZ;

/**
* Class Level Java Docs
*
*/
@com.myannotation.AnyXYZ
public class GeneratedFactory {

/**
* Method Level Java Docs
*
*/
public static com.somclass.AnyXYZ myFirstMehtod(long data) {
com.somclass.AnyXYZ varName = new com.somclass.AnyXYZ();
varName.setCode(100);
return varName;
}
}

15 comments:

  1. May you explain, how you can declare this Annotation:
    @RunWith(Parameterized.class)

    Thanks alot.

    Kai

    ReplyDelete
  2. Hi, great example thank you. Is it possible to add code level comments. i.e

    /*
    *TODO something
    */

    ReplyDelete
  3. yes you can add as directStatement ("/* your comment */")... I have not tried by my guess...

    ReplyDelete
  4. A late reply, but I have just started looking into this.

    Kai - annotation with parameter. Use code as below, but note that although in the codeModel you write name and value, codeModel drops the default parameter name when the code is generated:

    JClass runWith = codeModel.ref("org.junit.runner.RunWith");
    JClass runnerRef = codeModel.ref("com.solstoneplus.global.guice.GuiceJUnitRunner");
    JAnnotationUse annotationUse = testClass.annotate(runWith);
    annotationUse.param("value", runnerRef);

    The code generated:

    package com.wiggly;

    import com.solstoneplus.global.guice.GuiceJUnitRunner;
    import org.junit.runner.RunWith;

    @RunWith(GuiceJUnitRunner.class)
    public class WidgetTest {


    }

    ReplyDelete
  5. Hi, thanks for the great articles.
    Is there a way to override generic type of return argument in method declaration?
    For example,

    public List< String > getLsColumn() {

    As I end up with this;

    public ArrayList getLsColumn() {

    with this code;

    JMethod m = def.method(JMod.PUBLIC, List.class, "getLsColumn");

    ReplyDelete
  6. I guess i have the answer.

    By using narrow method,

    JMethod m = def.method(JMod.PUBLIC, codemodel.ref(List.class).narrow(codemodel.ref(String.class), "getLsColumn");

    output List

    :)
    Cheers!

    ReplyDelete
  7. Hi,
    Is there a way to generate parameterized constructors using Java code model?.Like
    class A
    {
    A(String a,int b)
    {
    }
    }
    Thanks,
    manjusha

    ReplyDelete
  8. Yes it should be possible just look at the API.

    ReplyDelete
  9. Hi,

    I want to create a static constructor, using the statement:
    JMethod constructor = createdClass.constructor(JMod.STATIC);

    But the result is:

    public class A {
    static A() {

    }
    }

    How do I generate code like:
    public class A {
    static {
    // code
    }
    }

    Please reply, thank you!

    ReplyDelete
  10. Hi,
    I want add a couple of import statements to my class how do i do that?

    -Su

    ReplyDelete
  11. you cant add import statements directly they get added automatically when you ref classes

    ReplyDelete
  12. Is it possible to have a reference to actual class and then adding another method?

    ReplyDelete
  13. Is it possible to write comments above the package name like...
    /*license header
    */
    package myPack;
    ....
    ....

    ReplyDelete