Как стать автором
Обновить

Генерация кода с jamon

Время на прочтение8 мин
Количество просмотров3.1K
Генерация кода

В нескольких проектах на разных местах работы я использовал генерацию кода. Зачем?
Чаще всего этим достигается сохранение правильности кода при изменениях. Например при добавлении еще одного типа данных в модель предметной области надо добавить класс на Яве, класс на С ++, код преобразования между ними и значение в Enum. Без генерации кода и работы ручками много, и всегда есть шанс что то из этого забыть.
Иногда reflection может быть заменой генерации кода, но даже тогда такой код будет менее читабельный чем шаблон для генерации.
Исходная информация для генерации кода может выглядеть очень по разному — это может быть Ява класс, у которого через reflection считываем свойства и генерим класс на C++, или csv файл, или XML, или еще что нибудь.

Попробую рассказать как я генерирую код в maven проектах с помощью фреймворка jamon.

Постановка задачи

Для примера возьмем задачу из сервиса для карт, который я пишу уже давно, но еще не написал. Есть точка на карте в Иерусалиме и для неё список свойств которые эта точка может иметь. Например относится она к времени второго храма или к Византии, христианская или мусульманская святыня и т.д.
Исходный файл выглядит так:
name,group christian, religion jewish, religion muslim, religion ancient, time firsttemple, time secondtemple, time hasmonean, time hordus, time roma, time byzantee, time arab, time ottoman, time persian, time crusaider, time pray, type museum, type restaurant, type other, type



На основе этого я хочу создать файл такого типа:
package org.citymap;

import java.io.Serializable;

public class PointAttributes implements Serializable {

    
     private boolean muslim;

     public boolean isMuslim() {
        return       muslim;
    }

    public void setMuslim(boolean value) {
         muslim = value;
    }
  ....

       public boolean equals(Object o) {
        if (this == oreturn true;
        if (o == null || getClass() != o.getClass()) return false;

        PointAttributes that = (PointAttributeso;

         
         if (muslim != that.muslimreturn false;
         
         if (christian != that.christianreturn false;
         
        ...
         


        return true;
    }

    public int hashCode() {
        int result = 0;
         
          result = 31 * result + (muslim ? 0);
         
          result = 31 * result + (christian ? 0);
         
         ....
         
        return result;
    }


}



Maven

Проект создаём в maven. Делаем его мульти модульным, иначе красиво генерировать код не получается. В модуле папе перечислены подмодули:
<modules> <module>util</module> <module>codegen</module> <module>common</module> <module>persistency</module> <module>web</module> </modules>



и сам он указан типа «pom»

В модуле codegen будет код генерации, а генерировать будем в модуле common.
Собственно генерировать будет класс org.citymap.CommonTemplatesRunner
Поэтому в модуль common домабвляем следущее:
<build> <plugins> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>build-helper-maven-plugin</artifactId> <version>1.5</version> <executions> <execution> <id>add-source</id> <phase>generate-sources</phase> <goals> <goal>add-source</goal> </goals> <configuration> <sources> <source>target/gencode</source> </sources> </configuration> </execution> </executions> </plugin> <plugin> <artifactId>maven-antrun-plugin</artifactId> <version>1.6</version> <executions> <execution> <phase> generate-sources </phase> <configuration> <target> <java classname="org.citymap.CommonTemplatesRunner"> <classpath refid="maven.dependency.classpath" /> <arg value="${project.build.directory}"/> </java> </target> </configuration> <goals> <goal>run</goal> </goals> </execution> </executions> </plugin> </plugins> </build>


Как видите, мы добавили директорию с соурсами в target. Это нужно для того, чтобы при mvn clean сгенерированный код стирался (как и все продукты компиляции). А помощью anta мы запускаем генератор кода на этапе generate-sources. Как аргумент передаём генератору директории где генерить код (текущая директория зависит от того запускали мавен в этом проекте или в папе)

Jamon


Для генерации я использую фреймворк jamon, потому что у него прекрасный плагин для maven.
Добавляю его в проект codegen так:
<dependencies> <dependency> <groupId>org.jamon</groupId> <artifactId>jamon-runtime</artifactId> <version>2.3.1</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>build-helper-maven-plugin</artifactId> <version>1.5</version> <executions> <execution> <id>add-source</id> <phase>generate-sources</phase> <goals> <goal>add-source</goal> </goals> <configuration> <sources> <source>target/gencode</source> </sources> </configuration> </execution> </executions> </plugin> <plugin> <groupId>org.jamon</groupId> <artifactId>jamon-maven-plugin</artifactId> <version>2.3.2</version> <executions> <execution> <phase>generate-sources</phase> <goals> <goal>translate</goal> </goals> <configuration> <templateSourceDir>src/main/jamon</templateSourceDir> <templateOutputDir>target/gencode</templateOutputDir> </configuration> </execution> </executions> </plugin> </plugins> </build>




Он сам генерирует код — на основе шаблонов в src/main/jamon он сгенерирует файлы умеющие генерировать по шаблонам в target/gencode. Надеюсь никого не запутал с генерацией генераторов :)

Мой шаблон выглядит так:
<%import>
  java.util.*;
  org.citymap.*;
 org.apache.commons.lang.StringUtils;
</%import>
<%args>
  Map<String, PointProperty> pojos;
</%args>
package org.citymap;

import java.io.Serializable;

public class PointAttributes implements Serializable {

    <%for String name:pojos.keySet() %>
      private boolean <% name%>;

     public boolean is<% StringUtils.capitalize(name)%>() {
        return       <% name%>;
    }

      public boolean get<% StringUtils.capitalize(name)%>() {
        return       <% name%>;
    }

      public void set<% StringUtils.capitalize(name)%>(boolean value) {
         <% name%> = value;
    }


    </%for>

    public boolean equals(Object o) {
        if (this == oreturn true;
        if (o == null || getClass() != o.getClass()) return false;

        PointAttributes that = (PointAttributeso;

         <%for String name:pojos.keySet() %>
         if (<% name%> != that.<% name%>return false;
         </%for>


        return true;
    }

    public int hashCode() {
        int result = 0;
         <%for String name:pojos.keySet() %>
          result = 31 * result + (<% name%> ? 0);
         </%for>
        return result;
    }

}



И вызываю я его следующим образом:
public class CommonTemplatesRunner {
    public static void main(String[] argsthrows IOException {
        String targetDir = args[0];
        final Reader csv = new InputStreamReader(CodeGen.class.getClassLoader().getResourceAsStream("properties.csv"));
        final ObjectCSVMapper<PointProperty> csvProcessor = new ObjectCSVMapper<PointProperty>(PointProperty.class, csv);
        final Map<String, PointProperty> pojos = csvProcessor.getEntities();
        File dir = new File(targetDir+"/gencode/org/citymap");
        dir.mkdirs();
        FileWriter fw = new FileWriter(new File(dir,"PointAttributes.java"));
        new PointAttributesTemplate().render(fw, pojos);

    }
}



Осталось создать для него аргумент — этот самый Map<String, PointProperty>.
Это я делаю используя библиотеку supercsv, генерируя из указанного в начале properties.csv объекты типа PointProperty.

Итак, повторяем процесс:

В модуле codegen ничего не запускается, только создается генератор кода.
На основе шаблона PointAttributesTemplate.jamon создается класс PointAttributesTemplate

В модуле common запускается CommonTemplatesRunner:

1. Из properties.csv делаем объекты типа PointProperty.
2. Передаем их как аргумент в PointAttributesTemplate и он генерит PointAttributes.java

Фух. Всё.
Вопросы?
Теги:
Хабы:
Всего голосов 5: ↑4 и ↓1+3
Комментарии0

Публикации