A better way of code generator in Java?
Asked Answered
E

5

9

I have a class with a graph inside. I iterate the graph and create a string that builds the graph, and then I just write that string into a Java file. Is there a better way of doing this, i read about JDT and CodeModel but I really am needing some hint of how to get used with it.

EDIT

I am doing a regular expression code generator, so far I have converted the Regular Expression into a DFA represented in a directedgraph (using grail library). When I have the DFA the next step is to generate a class that have three methods, 1st one builds the same graph (DFA), 2nd method moves from one node to another, and the third method matches if the input string is accepted one. Only first method is changing depending on the regularexpression input, the other two are static and same for each generated java class.

My string based approach looks like:

 import grail.interfaces.DirectedEdgeInterface;
 import grail.interfaces.DirectedGraphInterface;
 import grail.interfaces.DirectedNodeInterface;
 import grail.interfaces.EdgeInterface;
 import grail.iterators.EdgeIterator;
 import grail.iterators.NodeIterator;
 import grail.properties.GraphProperties;
 import grail.setbased.SetBasedDirectedGraph;

 public class ClassName {

private SetBasedDirectedGraph graph = new SetBasedDirectedGraph();
private static DirectedNodeInterface state;
private static DirectedNodeInterface currentState;
protected DirectedEdgeInterface edge;

public ClassName() {
    buildGraph();
}

protected void buildGraph() {

    // Creating Graph Nodes (Automaton States)

    state = graph.createNode(3);
    state.setProperty(GraphProperties.LABEL, "3");
    state.setProperty(GraphProperties.DESCRIPTION, "null");
    graph.addNode(state);
    state = graph.createNode(2);
    state.setProperty(GraphProperties.LABEL, "2");
    state.setProperty(GraphProperties.DESCRIPTION, "null");
    graph.addNode(state);
    state = graph.createNode(1);
    state.setProperty(GraphProperties.LABEL, "1");
    state.setProperty(GraphProperties.DESCRIPTION, "Accepted");
    graph.addNode(state);
    state = graph.createNode(0);
    state.setProperty(GraphProperties.LABEL, "0");
    state.setProperty(GraphProperties.DESCRIPTION, "Initial");
    graph.addNode(state);
            .....


    // Creating Graph Edges (Automaton Transitions)

    edge = graph.createEdge(null, (DirectedNodeInterface) graph.getNode(2),
            (DirectedNodeInterface) graph.getNode(1));
    edge.setProperty((GraphProperties.LABEL), "0");
    graph.addEdge(edge);
    edge = graph.createEdge(null, (DirectedNodeInterface) graph.getNode(2),
            (DirectedNodeInterface) graph.getNode(2));
    edge.setProperty((GraphProperties.LABEL), "1");
    graph.addEdge(edge);
    edge = graph.createEdge(null, (DirectedNodeInterface) graph.getNode(1),
            (DirectedNodeInterface) graph.getNode(1));
    edge.setProperty((GraphProperties.LABEL), "0");
    graph.addEdge(edge);
    edge = graph.createEdge(null, (DirectedNodeInterface) graph.getNode(1),
            (DirectedNodeInterface) graph.getNode(3));
    edge.setProperty((GraphProperties.LABEL), "1");
    graph.addEdge(edge);
    edge = graph.createEdge(null, (DirectedNodeInterface) graph.getNode(0),
            (DirectedNodeInterface) graph.getNode(1));
    edge.setProperty((GraphProperties.LABEL), "0");
    graph.addEdge(edge);
    edge = graph.createEdge(null, (DirectedNodeInterface) graph.getNode(0),
            (DirectedNodeInterface) graph.getNode(2));
    edge.setProperty((GraphProperties.LABEL), "1");
    graph.addEdge(edge);
}
}  
Euphonious answered 23/4, 2012 at 14:8 Comment(3)
We need more detail - what does 'iterate the graph' mean?Yoon
Are you talking about this type of graph? en.wikipedia.org/wiki/Graph_%28mathematics%29Kella
read the edited article. @Kella yes it is such a graph except that it is directedEuphonious
E
4

Another solution would be to stick to the current technology but provide a small layer with the builder pattern. To implement the builder you need a small one time effort, but get much better readable code.

I implemented the first part of your code. With the proper builder you could write:

graph = new GraphBuilder()
    .createNode(3).setLabel("3").setDescription("null").add()
    .createNode(2).setLabel("2").setDescription("null").add()
    .createNode(1).setLabel("1").setDescription("Accepted").add()
    .createNode(0).setLabel("0").setDescription("Initial").add()
    // unimplemented start
    .createEdge(2, 1).setLabel("0").add()
    .createEdge(2, 2).setLabel("1").add()
    .createEdge(1, 1).setLabel("0").add()
    .createEdge(1, 3).setLabel("1").add()
    .createEdge(0, 1).setLabel("0").add()
    .createEdge(0, 2).setLabel("1").add()
    // unimplemented end
    .build();

Much more readable, isn't it? To get this you need two builders. First comes the GraphBuilder:

package at.corba.test.builder;

import java.util.LinkedHashMap;
import java.util.Map;

/**
 * Builder for generating graphs.
 * @author ChrLipp
 */
public class GraphBuilder {
    /** List of StateBuilder, accesable via nodeNumber. */
    Map<Integer, StateBuilder> stateBuilderMap = new LinkedHashMap<Integer, StateBuilder>();

    /**
     * Delegates node-specific building to NodeBuilder.
     * @param nodeNumber Number of node to create
     * @return NodeBuilder for the node instance to create.
     */
    public StateBuilder createNode(final int nodeNumber) {
        StateBuilder builder = new StateBuilder(this);
        stateBuilderMap.put(nodeNumber, builder);
        return  builder;
    }

    /**
     * Builder function to initialise the graph.
     */
    public SetBasedDirectedGraph build() {
        SetBasedDirectedGraph graph = new SetBasedDirectedGraph();

        for (int key : stateBuilderMap.keySet()) {
            StateBuilder builder = stateBuilderMap.get(key);
            State state = graph.createNode(key);
            state = builder.build(state);
            graph.addNode(state);
        }

        return graph;
    }
}

and than the StateBuilder:

package at.corba.test.builder;

import java.util.HashMap;
import java.util.Map;

/**
 * Builder for generating states.
 * @author ChrLipp
 */
public class StateBuilder {
    /** Parent builder */
    private final GraphBuilder graphBuilder;

    /** Properties for this node */
    Map<GraphProperties, String> propertyMap = new HashMap<GraphProperties, String>();

    /**
     * ctor.
     * @param graphBuilder  Link to parent builder
     * @param nodeNumber    Node to create
     */
    public StateBuilder(final GraphBuilder graphBuilder)  {
        this.graphBuilder = graphBuilder;
    }

    /**
     * Property setter for property Label.
     * @param label value for property label
     * @return current NodeBuilder instance for method chaining
     */
    public StateBuilder setLabel(final String label) {
        propertyMap.put(GraphProperties.LABEL, label);
        return this;
    }

    /**
     * Property setter for description Label.
     * @param description value for description label
     * @return current NodeBuilder instance for method chaining
     */
    public StateBuilder setDescription(final String description) {
        propertyMap.put(GraphProperties.DESCRIPTION, description);
        return this;
    }

    /**
     * DSL function to close the node section and to return control to the parent builder.
     * @return
     */
    public GraphBuilder add() {
        return graphBuilder;
    }

    /**
     * Builder function to initialise the node.
     * @return newly generated node
     */
    public State build(final State state) {
        for (GraphProperties key : propertyMap.keySet()) {
            String value = propertyMap.get(key);
            state.setProperty(key, value);
        }

        return state;
    }
}

You would do the same for edges, but I did not implement this :-) . In Groovy it is even more easier to create builders (my implementation is a builder written in Java), see for example Make a builder.

Emilie answered 2/5, 2012 at 12:29 Comment(5)
I looks promising. Thank you. I will give a try, and let you know later on.Euphonious
Thank you very much. I looked closer to your code an recognized that the graph object is created in the ctor what I didn't notice before. I would move the creation into GraphBuilder.build(), in that case you don't need an instance var/function parameter but a return value. You would use it as graph = new GraphBuilder()..Emilie
The DSL is a just suggestion, maybe it is more readable if you rename setLabelProperty to setLabel, setDescriptionProperty to setDescription and node to createNode.Emilie
Changed post accordingly the two comments.Emilie
If all edges have only a label property, instead of .createEdge(2, 1).setLabel("0").add() you could also enable the following DSL: .addEdge(2, 1, "0")Emilie
S
2

A better way of code generator in Java... How about tools like ANTLR, which is a modern tool created specifically for implementing lexers/parsers with code generation support. It has great documentation, including two books:

The last one is useful even when not using ANTLR.

Spicebush answered 1/5, 2012 at 19:12 Comment(0)
E
2

A very simple example is given on the following blog :

http://namanmehta.blogspot.in/2010/01/use-codemodel-to-generate-java-source.html

You might want to have a look at it.

The problem with jcodemodel is that it is internally used by popular code generators like JAX-B and is not well documented. It also don't have any tutorials. But if you want to use this library, you can have a look at different blogs where the users have documented their experience / problem description and resolution .

Best of luck

Eau answered 2/5, 2012 at 12:10 Comment(0)
C
1

Still a little fuzzy on the question, but here are some suggestions:

  • Create a base class that includes the static functions and make your generated classes extend it. That way you don't have to keep rewriting the static functions.
  • Do you really need one Class per graph? Normally you would have one Class that takes the graph as a parameter to the constructor and just have different object instances of the same Class
  • Can you serialize the Directed Graph? If so, that is a better way to store and revive it.
Condescending answered 25/4, 2012 at 18:27 Comment(4)
Yes it is a good idea, but actually I am looking for hints on how to do it using some code generator library as CodeModel.Euphonious
@sm13294, I'd suggest using ASM rather than CodeModel as ASM is well documented and battle tested. I'd still follow the same general pattern. Create a base class that has all three functions and has a private member that is the Directed Graph object. Generate your derived class as one that has the Directed Graph as a private static class member and feeds it to the parent class via the constructor.Condescending
I have used a little bit ASM, but i didn't knew that we can generate java code, that is good. Can you provide me with any simple example on how to generate code using it :$ Thanks in advanceEuphonious
@sm13294: ASM creates bytecode, not java sources, but I think the code you want to generate is regular enough so that generating bytecode won't be more difficult than generating sources. The ASM framework includes an "asmifier" tool that shows the asm calls to create a given class file, that could be a good starting point.Manganin
P
1

I've used a lesser known product called FreeMarker for several projects that needed code generation (such as encode/decode classes for messages). It is a Java based solution in which you generate a memory model and feed it to a template. From their home page:

FreeMarker is a "template engine"; a generic tool to generate text output (anything from HTML to autogenerated source code) based on templates. It's a Java package, a class library for Java programmers. It's not an application for end-users in itself, but something that programmers can embed into their products.

To use FreeMarker, create a data model and a template to produce the code for the class you are trying to build. This solution has extra learning overhead, but should be easy to learn, and is incredibly useful for future code generation requirements and other projects in the future.

Update: Here is a template for the class specified in the question (Note: I have not tested it):

import grail.interfaces.DirectedEdgeInterface;
import grail.interfaces.DirectedGraphInterface;
import grail.interfaces.DirectedNodeInterface;
import grail.interfaces.EdgeInterface;
import grail.iterators.EdgeIterator;
import grail.iterators.NodeIterator;
import grail.properties.GraphProperties;
import grail.setbased.SetBasedDirectedGraph;

public class ClassName {

private SetBasedDirectedGraph graph = new SetBasedDirectedGraph();
private static DirectedNodeInterface state;
private static DirectedNodeInterface currentState;
protected DirectedEdgeInterface edge;

public ClassName() {
    buildGraph();
}

protected void buildGraph() {

    // Creating Graph Nodes (Automaton States)
<#list nodes as node>
    state = graph.createNode(${node.id});
    state.setProperty(GraphProperties.LABEL, "${node.id}");
    state.setProperty(GraphProperties.DESCRIPTION, "null");
    graph.addNode(state);
</#list>

    // Creating Graph Edges (Automaton Transitions)
<#assign edgeCount = 0>
<#list nodes as node1>
<#list nodes as node2>
    edge = graph.createEdge(null, (DirectedNodeInterface) graph.getNode(${node1.id}),
            (DirectedNodeInterface) graph.getNode(${node2.id}));
    edge.setProperty((GraphProperties.LABEL), "${edgeCount}");
    graph.addEdge(edge);
<#assign edgeCount = edgeCount + 1>
</#list>
</#list>
}
}

Your data model should be fairly simple--a Map containing one key who's value is a List of nodes. If you later find your template needs more information, you can change your data model at any time. Any Java object should work within the data model so long as the required fields are public or have public getters.

Map<String, Object> root = new HashMap<String, Object>();
List<Integer> nodes = new ArrayList<Integer>();
nodes.add(1);
nodes.add(2);
...
root.put("nodes", nodes);

See this page in the FreeMarker manual for a great example for data models using Maps.

The next step would be to use the FreeMarker API to combine the template + data model to create the class. Here is an example from the FreeMarker manual I have modified for your case:

import freemarker.template.*;
import java.util.*;
import java.io.*;

public class Test {

    public static void main(String[] args) throws Exception {

        /* ------------------------------------------------------------------- */    
        /* You should do this ONLY ONCE in the whole application life-cycle:   */    

        /* Create and adjust the configuration */
        Configuration cfg = new Configuration();
        cfg.setDirectoryForTemplateLoading(
                new File("/where/you/store/templates"));
        cfg.setObjectWrapper(new DefaultObjectWrapper());

        /* ------------------------------------------------------------------- */    
        /* You usually do these for many times in the application life-cycle:  */    

        /* Get or create a template */
        Template temp = cfg.getTemplate("test.ftl");

        /* Create a data-model */
        Map<String, Object> root = new HashMap<String, Object>();
        List<Integer> nodes = new ArrayList<Integer>();
        nodes.add(1);
        nodes.add(2);
        ...
        root.put("nodes", nodes);    

        /* Merge data-model with template */
        Writer out = new OutputStreamWriter(System.out);
        temp.process(root, out);
        out.flush();
    }
}  

The FreeMarker manual is very helpful and contains many useful examples. See the Getting Started guide if you're interested in this approach.

Puissant answered 30/4, 2012 at 19:38 Comment(1)
Can you provide with any simple example?Euphonious

© 2022 - 2024 — McMap. All rights reserved.