【问题标题】:A better way of code generator in Java?Java中代码生成器的更好方法?
【发布时间】:2012-05-04 03:51:51
【问题描述】:

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

编辑

我正在做一个正则表达式代码生成器,到目前为止,我已经将正则表达式转换为有向图中表示的 DFA(使用 grail 库)。当我拥有 DFA 时,下一步是生成一个具有三个方法的类,第一个构建相同的图 (DFA),第二个方法从一个节点移动到另一个节点,如果输入字符串被接受,则第三个方法匹配一个。只有第一种方法根据正则表达式输入而变化,其他两种方法是静态的,并且对于每个生成的 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);
}
}  

【问题讨论】:

  • 我们需要更多细节——“迭代图表”是什么意思?
  • 你说的是这种类型的图吗? en.wikipedia.org/wiki/Graph_%28mathematics%29
  • 阅读编辑过的文章。 @eabraham 是的,它就是这样一个图表,只是它是有向的

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


【解决方案1】:

另一种解决方案是坚持当前的技术,但提供一个带有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

【讨论】:

  • 我看起来很有希望。谢谢你。我会试一试,稍后通知您。
  • 非常感谢。我仔细查看了您的代码,并认识到图形对象是在 ctor 中创建的,这是我以前没有注意到的。我会将创建移到 GraphBuilder.build() 中,在这种情况下,您不需要实例 var/function 参数而是返回值。您可以将其用作graph = new GraphBuilder()..
  • DSL 是一个公正的建议,如果将 setLabelProperty 重命名为 setLabel,将 setDescriptionProperty 重命名为 setDescription,将 node 重命名为 createNode,可能会更具可读性。
  • 相应地更改了两个 cmets 的帖子。
  • 如果所有边都只有一个标签属性,而不是.createEdge(2, 1).setLabel("0").add(),您还可以启用以下DSL:.addEdge(2, 1, "0")
【解决方案2】:

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

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

你可能想看看它。

jcodemodel 的问题在于它被 JAX-B 等流行的代码生成器在内部使用,并且没有很好的文档记录。它也没有任何教程。但是如果你想使用这个库,你可以看看不同的博客,用户记录了他们的经验/问题描述和解决方案。

祝你好运

【讨论】:

    【解决方案3】:

    Java 中代码生成器的一种更好的方式...像ANTLR 这样的工具怎么样,它是一种现代工具,专门用于实现具有代码生成支持的词法分析器/解析器。它有很好的文档,包括两本书:

    即使不使用 ANTLR,最后一个也很有用。

    【讨论】:

      【解决方案4】:

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

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

      【讨论】:

      • 是的,这是个好主意,但实际上我正在寻找有关如何使用一些代码生成器库作为 CodeModel 的提示。
      • @sm13294,我建议使用ASM 而不是 CodeModel,因为 ASM 有据可查并经过实战测试。我仍然会遵循相同的一般模式。创建一个基类,它具有所有三个函数并具有一个私有成员,即有向图对象。将派生类生成为将有向图作为私有静态类成员的派生类,并通过构造函数将其提供给父类。
      • 我用过一点ASM,但我不知道我们可以生成java代码,那很好。你能给我提供任何关于如何使用它生成代码的简单示例吗:$ 提前谢谢
      • @sm13294:ASM 创建字节码,而不是 java 源代码,但我认为您要生成的代码足够规则,因此生成字节码不会比生成源代码更难。 ASM 框架包括一个“asmifier”工具,它显示了创建给定类文件的 asm 调用,这可能是一个很好的起点。
      【解决方案5】:

      我在几个需要代码生成的项目(例如消息的编码/解码类)中使用了一个鲜为人知的产品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 指南。

      【讨论】:

      • 你能提供一个简单的例子吗?
      猜你喜欢
      • 1970-01-01
      • 2011-11-18
      • 2012-04-21
      • 1970-01-01
      • 2010-10-23
      • 2010-12-08
      • 1970-01-01
      • 2018-04-11
      • 1970-01-01
      相关资源
      最近更新 更多