Лучший способ генератора кода на Java?

У меня есть класс с диаграммой внутри. Я повторяю график и создаю строку, которая строит график, а затем просто записываю эту строку в файл Java. Есть ли лучший способ сделать это, я читал о JDT и CodeModel, но мне действительно нужен намек на то, как с ним привыкнуть.

редактировать

Я делаю генератор кода регулярного выражения, до сих пор я преобразовал регулярное выражение в DFA, представленное в directedgraph (используя grail библиотека.) Когда у меня есть DFA, следующий шаг-создать класс, который имеет три метода, 1-й строит тот же граф (DFA), 2-й метод перемещается с одного узла на другой, а третий метод соответствует, если входная строка принята одна. Только первый метод изменяется в зависимости от ввода regularexpression, два других являются статическими и одинаковыми для каждого сгенерированного класса 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 ответов


другое решение было бы придерживаться текущей технологии, но обеспечить небольшой слой с шаблона. Для реализации построителя вам нужно небольшое одноразовое усилие, но получить гораздо лучше читаемый код.

я реализовал первую часть кода. С правильным строителем вы могли бы написать:

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), см. например сделать строителя.


очень простой пример приведен в следующем блоге:

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

возможно, вы захотите взглянуть на него.

проблема с jcodemodel заключается в том, что он внутренне используется популярными генераторами кода, такими как JAX-B, и не хорошо документирован. Также нет никаких учебников. Но если вы хотите использовать эту библиотеку, вы можете посмотреть на разных блогах, где пользователи задокументировали свой опыт / описание проблемы и разрешение .

удачи


все еще немного нечеткой на вопрос, но вот несколько советов:

  • создайте базовый класс, включающий статические функции, и расширьте его сгенерированные классы. Таким образом, вам не нужно переписывать статические функции.
  • вам действительно нужен один класс на график? Обычно у вас будет один класс, который принимает граф в качестве параметра конструктора и просто имеет разные экземпляры объектов одного класса
  • вы можете сериализовать направленный граф? Если это так, то это лучший способ сохранить и оживить его.

я использовал менее известный продукт под названием FreeMarker для нескольких проектов, которые требовали генерации кода (например, классы кодирования/декодирования для сообщений). Это решение на основе Java, в котором вы создаете модель памяти и передаете ее шаблону. С их домашней страницы:

FreeMarker-это "механизм шаблонов"; универсальный инструмент для генерации текста выход (что-нибудь из HTML в автогенерируемые исходный код) на основе шаблоны. Это пакет Java, библиотека классов для Java-программистов. Это не приложение для конечных пользователей, но то, что программисты могут внедрять в свои продукты.

чтобы использовать FreeMarker, создайте модель данных и шаблон для создания кода для класса, который вы пытаетесь построить. Это решение имеет дополнительные затраты на обучение, но должно быть простым в освоении и невероятно полезно для будущих требований к генерации кода и других проектов в будущем.

Update: вот шаблон для класс, указанный в вопросе (Примечание: я его не тестировал):

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

ваша модель данных должна быть довольно простой-карта, содержащая один ключ, значение которого является списком узлов. Если позже вы обнаружите, что ваш шаблон нуждается в дополнительной информации, вы можете изменить модель данных в любое время. Любой объект 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 для отличный пример для моделей данных с использованием карт.

следующим шагом будет использование API FreeMarker для объединения модели template + data для создания класса. Вот это пример из руководства FreeMarker я изменил для вашего случая:

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 очень полезно и содержит много полезных примеров. Вижу Начало Работы руководство, если вы заинтересованы в этом подходе.


лучший способ генератора кода на Java... Как насчет таких инструментов, как ANTLR, который является современным инструментом, созданным специально для реализации лексеров/синтаксических анализаторов с поддержкой генерации кода. Он имеет большую документацию, в том числе две книги:

последний, если он полезен, даже если не использует ANTLR.