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

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

РЕДАКТИРОВАТЬ

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

и чем Государственный Строитель:

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.

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

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

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

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

Удачи

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

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

Я использовал менее известный продукт под названием 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>
}
}

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

Еще немного размыто по этому вопросу, но вот несколько советов:

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