Java中代码生成器的更好方法?

时间:2012-04-23 14:08:13

标签: java code-generation eclipse-jdt sun-codemodel

我有一个里面有图表的课程。我迭代图形并创建一个构建图形的字符串,然后我将该字符串写入Java文件。 有没有更好的方法,我读到JDT和CodeModel,但我真的需要一些如何使用它的提示。

修改

我正在做一个正则表达式代码生成器,到目前为止,我已将正则表达式转换为有向图中表示的DFA(使用grail库)。当我有DFA时,下一步是生成一个有三个方法的类,第一个构建相同的图形(DFA),第二个方法从一个节点移动到另一个节点,第三个方法匹配输入字符串是否被接受。只有第一种方法根据regularrexpression输入而改变,另外两种方法是静态的,并且对于每个生成的java类都是相同的。

我基于字符串的方法如下:

 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);
}
}  

5 个答案:

答案 0 :(得分:4)

另一种解决方案是坚持当前的技术,但提供一个builder pattern的小层。要实现构建器,您需要花费一小段时间,但可以获得更好的可读代码。

我实现了代码的第一部分。使用适当的构建器,您可以写:

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();

更具可读性,不是吗?要得到这个,你需要两个建设者。首先是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;
    }
}

而不是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;
    }
}

你会对边缘做同样的事,但我没有实现这个:-)。 在Groovy中,创建构建器更容易(我的实现是用Java编写的构建器),例如参见Make a builder

答案 1 :(得分:2)

以下博客给出了一个非常简单的例子:

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

你可能想看看它。

jcodemodel的问题在于它是由JAX-B之类的流行代码生成器内部使用的,并且没有很好的文档记录。它也没有任何教程。但是,如果您想使用此库,您可以查看用户记录其体验/问题描述和解决方案的不同博客。

祝你好运

答案 2 :(得分:1)

这个问题还有点模糊,但这里有一些建议:

  • 创建一个包含静态函数的基类,并使生成的类扩展它。这样你就不必继续重写静态函数了。
  • 每个图表真的需要一个类吗?通常你会有一个Class,它将图形作为构造函数的参数,只是具有相同类的不同对象实例
  • 你可以serialize定向图吗?如果是这样,那就是存储和恢复它的更好方法。

答案 3 :(得分:1)

我使用了一个名为FreeMarker的鲜为人知的产品,用于需要代码生成的几个项目(例如消息的编码/解码类)。它是一种基于Java的解决方案,您可以在其中生成内存模型并将其提供给模板。从他们的主页:

  

FreeMarker是一个“模板引擎”;生成文本的通用工具   输出(从HTML到自动生成的源代码)基于   模板。它是一个Java包,是Java程序员的类库。   它本身并不是最终用户的应用程序,而是一种应用程序   程序员可以嵌入他们的产品中。

要使用FreeMarker,请创建数据模型和模板,以便为您要构建的类生成代码。该解决方案具有额外的学习开销,但应该易于学习,并且对将来的代码生成要求和其他项目非常有用。

更新:这是问题中指定的类的模板(注意:我还没有测试过):

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>
}
}

您的数据模型应该相当简单 - 包含一个键的Map,其值是节点列表。如果您以后发现模板需要更多信息,则可以随时更改数据模型。只要必需的字段是公共的或具有公共getter,任何Java对象都应该在数据模型中工作。

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

请参阅FreeMarker手册中的this页面,了解使用地图的数据模型的一个很好的示例。

下一步是使用FreeMarker API组合模板+数据模型来创建类。以下是我为您的案例修改的FreeMarker手册中的example

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();
    }
}  

FreeMarker手册非常有用,包含许多有用的示例。如果您对此方法感兴趣,请参阅Getting Started指南。

答案 4 :(得分:1)

Java中代码生成器的更好方法... ANTLR等工具如何,这是一个专门为实现具有代码生成支持的词法分析器/解析器而创建的现代工具。它有很好的文档,包括两本书:

即使不使用ANTLR,最后一个如果有用。