Моделирование при помощи среды Eclipse Modeling Framework: Часть 2. Генерация кода с применением шаблонов Eclipse Java Emitter Templates

Генерация кода с применением шаблонов Eclipse Java Emitter Templates

Eclipse Java™Emitter Templates (JET) – это инструмент open source, предназначенный для генерации кода в среде Eclipse Modeling Framework (EMF). Он аналогичен Java Server Pages, но благодаря своей мощи и гибкости позволяет генерировать код на Java, SQL и любых других языках, включая языки JSP. В настоящей статье объясняется, как создать и сконфигурировать JET и как применять его в разных ситуациях.

Адриан Пауэлл, старший разработчик ПО, IBM

Адриан Пауэлл (Adrian Powell) начинал свою деятельность с разработки инструментария Java в отделении VisualAge IBM в качестве члена группы Java Enterprise Tooling. Он потерял два года, программируя генератор кода вручную. С тех пор Адриан разработал инструменты и подключаемые модули почти для каждой версии Eclipse и VisualAge для Java. Сейчас он работает в Центре инноваций электронного бизнеса IBM в Ванкувере, где создает замену самому себе.



29.06.2009

Общие сведения о Java Emitter Templates (JET)

Разработчики часто используют инструменты, генерирующие код для многократного применения. Пользователи Eclipse знакомы со стандартными инструментами для генерации циклов for(;;), методов main() и методов accessor для избранных атрибутов. Автоматизация этих простых механических задач ускоряет разработку и упрощает нам жизнь. В некоторых случаях, таких как генерация программ установки для серверов J2EE, генерация кода может сэкономить время и скрыть трудности, связанные с реализаций решений, позволяя устанавливать ПО на разные серверы J2EE. Генерация кода требуется не только крупным поставщикам инструментов; она может эффективно применяться во многих проектах. Инструмент Eclipse Java Emitter Templates (JET), который входит в состав Eclipse Modeling Framework (EMF), служит простым и действенным способом добавления в проект средств автоматической генерации кода. В настоящей статье мы рассмотрим, как использовать JET в разных ситуациях.


Что такое JET?

Шаблоны Java Emitter Templates очень похожи на компоненты Java Server Pages (JSP). Те и другие используют одинаковый синтаксис и автоматически компилируются в Java. Те и другие применяются для разделения ответственности за рендеринг страниц из модели при помощи контроллера. Те и другие принимают объекты, переданные им в качестве входного аргумента, и позволяют вставлять в код строковые значения («выражения»), непосредственно использовать код Java для выполнения циклов, декларации переменных или для выполнения последовательности логических операций («скриптлетов»). Те и другие служат хорошим способом представления структуры генерируемого объекта (веб-страницы, класса Java или файла), в то же время поддерживая возможность настройки отдельных деталей.

Шаблоны JET отличаются от компонентов JSP двумя особенностями. В JET структуру разметки можно изменить таким образом, чтобы генерировать код на разных языках. На вход JET обычно подается файл конфигурации, а не данные, вводимые пользователем (хотя ничто не мешает это делать). И обработка JET для данной последовательности операций обычно производится лишь однажды. Это не технические ограничения, и можно найти случаи применения шаблонов JET совершенно иными способами.


Начало

Создание шаблона

Создайте новый проект Java JETExample и назначьте в качестве каталога исходного кода src. Чтобы включить JET для этого проекта, нажмите правую кнопку и выберите Add JET Nature. Это приведет к созданию в корне вашего нового проекта каталога templates. В конфигурации JET по умолчанию скомпилированные файлы Java помещаются в корень проекта. Чтобы исправить это, откройте окно свойств проекта, выберите JET Settings и задайте в качестве контейнера исходного кода src. Тогда при работе компилятора JET файлы Java, созданные JET, будут записываться в нужный каталог исходного кода.

Теперь все готово для создания первого шаблона JET. Компилятор JET создает файл исходного кода Java для каждого шаблона JET, поэтому договоримся называть шаблон именем NewClass.javajet, где NewClass будет именем сгенерированного класса. Это не обязательно, но помогает избежать путаницы.

Начнем с создания нового файла в каталоге шаблонов с именем GenDAO.javajet. Появится диалоговое окно с предупреждением об ошибке компиляции в строке 1 колонки 1 нового файла. Конкретно там будет сказано: "The jet directive is missing" (отсутствует директива jet). Не пугайтесь, технически все правильно, так как мы только что создали пустой файл. Нажмите ОК, чтобы закрыть окно, и Cancel, чтобы очистить диалог New File (файл уже создан). Во избежание повторения этой проблемы первым делом надо создать директиву jet.

Каждый шаблон JET должен начинаться с директивы jet. Это указывает компилятору JET на то, что скомпилированный шаблон Java будет выглядеть так (это не то, что генерирует шаблон, так выглядит только скомпилированный класс шаблона; здесь путаница в терминологии, так что следите за мной). Здесь же содержится некоторая информация о стандартном классе Java. Например, будем использовать следующее:

Листинг 1. Пример декларации jet
<%@ jet
    package="com.ibm.pdc.example.jet.gen"
    class="GenDAO"
    imports="java.util.* com.ibm.pdc.example.jet.model.*"
    %>

В листинге 1 все очень наглядно. Когда шаблон JET скомпилирован, в com.ibm.pdc.example.jet.gen создается файл Java GenDAO, который будет импортировать заданные пакеты. Опять же так будет выглядеть только шаблон, а не код, генерируемый шаблоном — об этом ниже. Обратите внимание на то, что имя файла кода Java, выводимого JET, определяется в декларации jet и не связано с filename. Если два шаблона декларируют одно и то же имя класса, при внесении изменений они будут конфликтовать друг с другом без предупреждения. Это может случиться при копировании и вставке файлов шаблонов без надлежащей модификации декларации jet. Вы предупреждены, будьте начеку: копирование и вставка очень применяются для создания новых файлов в каталоге шаблонов.

Как и компоненты JSP, которые получают информацию через предварительно продекларированные переменные типа session, error, context и request, JET использует для передачи информации в шаблон предварительно продекларированные переменные. Шаблоны JET используют только две неявные переменные: stringBuffer типа StringBuffer (удивительно!), которая применяется для создания выходной последовательности при вызове generate(); и аргумент типа Object с говорящим именем argument. Первая строка типичного шаблона JET оформляет это в более подходящий класс, как показано в листинге 2.

Листинг 2. Инициализация аргумента JET
<% GenDBModel genDBModel = (GenDBModel)argument; %>

package <%= genDBModel.getPackageName() %>;

Видно, что синтаксис по умолчанию для шаблонов JET идентичен синтаксису JSP, где для пропуска кода или «скриптлетов» используется <%...%>, а для распечатки значения выражения — <%= ... %>. Как и в JSP, аккуратное использование тегов <% ... %> позволяет добавлять любые логические циклы или конструкции, как это делается в любом методе Java. Например:

Листинг 3. Скриптлеты и выражения
Welcome <%= user.getName() %>!
<% if ( user.getDaysSinceLastVisit() > 5 ) { %>
О, спасибо, что вернулись.  А мы думали, мы вас потеряли!
<% } else { %>
Так быстро?  Вам что, больше нечем заняться?
<% } %>

Когда шаблон JET определен, сохраните его и щелкните на нем правой кнопкой в Package Explorer. Выберите Compile Template. Если все правильно, в пакете com.ibm.pdc.example.jet.gen будет создан новый класс GenDAO. Он содержит единственный метод public String generate(Object argument) (см. листинг 4), результатом чего будет определенный вами в javajet файл шаблона.

Листинг 4. Простейший код Java, скомпилированный шаблоном JET, который выводит "Hello <%=argument%>"
package com.ibm.pdc.example.jet.gen;

import java.util.*;

public class GenDAO
{
  protected final String NL = System.getProperties().getProperty("line.separator");
  protected final String TEXT_1 = NL + "Hello, ";
  protected final String TEXT_2 = NL + "\t ";

  public String generate(Object argument)
  {
    StringBuffer stringBuffer = new StringBuffer();
    stringBuffer.append(TEXT_1);
    stringBuffer.append( argument );
    stringBuffer.append(TEXT_2);
    return stringBuffer.toString();
  }
}

Разбор типичного кода

Написав несколько шаблонов, можно заметить некоторые общие, повторяющиеся элементы, например, такую простую вещь, как то, что к любому сгенерированному коду добавляется декларация об авторских правах. Как и в JSP, этим управляет декларация include. Поместите в свой шаблон javajet любые элементы, которые нужно включить в файл, скажем, 'copyright.inc', а затем добавьте оператор <%@ include file="copyright.inc" %>. Включенный файл будет целиком вставлен в результирующий код, так что он может указывать на любые продекларированные ранее переменные. Расширение .inc может быть любым, только не выбирайте окончание jet, иначе JET попытается скомпилировать включенный файл с очевидно неудачным результатом.

Настройка процесса компиляции JET

Если включенного файла недостаточно, и нужно добавить еще методы или настроить процесс генерации, простейший способ заключается в создании нового «скелета» JET. Файл скелета – это шаблон, в котором описывается, как должен выглядеть скомпилированный шаблон JET. По умолчанию скелет выглядит как в листинге 5.

Листинг 5. Скелет JET по умолчанию
public class CLASS
{
    public String generate(Object argument)
    {
        return "";
    }
}

Все операторы импорта располагаются сверху, CLASS заменяется на имя класса, которое мы указали в атрибуте class декларации, а тело метода generate() заменяется на код, выполняющий всю генерацию. Поэтому чтобы изменить вид кода шаблона после компиляции, надо просто создать новый файл скелета и выполнить все необходимые настройки, сохранив лишь эти основные элементы.

Чтобы создать специальный скелет, создайте в каталоге шаблонов новый файл с именем custom.skeleton, как показано в листинге 6.

Листинг 6. Специальный скелет JET
public class CLASS
{
    private java.util.Date getDate() {
        return new java.util.Date();
    }
		 
    public String generate(Object argument) {
        return "";
    }
}

Затем в любой шаблон JET, с которым вы хотите использовать это специальный скелет, добавьте атрибут skeleton="custom.skeleton" в декларации jet файла javajet.

Можно также создать расширенный базовый класс public class CLASS extends MyGenerator и добавить все необходимые вспомогательные методы в этот базовый класс. Это несколько удобнее, так как общий код остается общим, а процесс разработки облегчается благодаря тому, что компилятор JET не выдает всякий раз свои любезные сообщения об ошибке.

Специальный скелет позволяет также менять имя метода и список аргументов для метода generate(), так что достаточно нестандартно мыслящий разработчик может создавать очень специфические шаблоны. Я немного неточно выразился, когда сказал, что JET заменяет тело generate() генерируемым кодом. На самом деле он заменяет тело только последним методом, продекларированным в скелете, так что небрежные изменения кода скелета могут серьезно навредить вам и запутать ваших коллег.


Работа с CodeGen

Итак, когда шаблон скомпилирован, получается стандартный класс Java. Чтобы использовать его в приложении, нужно распространить только класс скомпилированного шаблона, а не шаблон javajet. Можно также предоставить пользователям возможность вносить в шаблон изменения и автоматически рекомпилировать шаблон при запуске приложения. Это делает Eclipse Modeling Framework (EMF), так что все, кому это интересно, могут зайти в plugins/org.eclipse.emf.codegen.ecore/templates и изменить способ, которым EMF генерирует их модель или редактор.

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

Компиляция шаблона в среде исполнения

Чтобы предоставить конечным пользователям возможность настраивать свои шаблоны (и возиться с их отладкой), можно выбрать компиляцию шаблонов в среде исполнения. Есть несколько способов сделать это, и для первого раза мы воспользуемся вспомогательным классом org.eclipse.emf.codegen.jet.JETEmitter, который скроет от нас некоторые детали. Очевидный (но, как правило, неправильный) код довольно прост и показан в листинге 7.

Листинг 7. Упрощенное использование JETEmitter (часто неправильное)
String uri = "platform:/templates/MyClass.javajet";
JETEmitter jetEmitter = new JETEmitter( uri );
String generated = jetEmitter.generate( new NullProgressMonitor(), 
new Object[]{argument} );

Первая проблема всплывет при попытке выполнить это в стандартном методе main(). Метод generate() выдаст NullPointerException, так как JETEmitter предполагает, что его вызывает модуль. При инициализации он обращается к CodeGenPlugin.getPlugin().getString(), что приведет к неудаче, так как CodeGenPlugin.getPlugin() будет пустым.

Простое решение в виде включения этого кода в модуль работать будет, но не полностью. Текущая реализация JETEmitter создает скрытый проект .JETEmitters, в который помещается сгенерированный код. Однако JETEmitter не добавляет в этот новый проект classpath модуля, так что если сгенерированный код ссылается на любые объекты вне стандартной библиотеки Java, он не будет компилироваться. В ранних сборках версии 2.0.0 эта проблема, похоже, решена, но на начало апреля они еще не были готовы. Чтобы обойти эту проблему, нужно расширить класс JETEmitter, переписав метод initialize() и добавив свои собственные операторы classpath. Ремко Попма (Remko Popma) написал хороший пример jp.azzurri.jet.article2.codegen.MyJETEmitter (см. Ресурсы), который справится с этим, пока JET не станет работать правильно. Модифицированный код выглядит как в листинге 8.

Листинг 8. Правильный вызов JETEmitter
String base = Platform.getPlugin(PLUGIN_ID).getDescriptor().getInstallURL().toString();
String uri = base + "templates/GenTestCase.javajet";
MyJETEmitter jetEmitter = new MyJETEmitter( uri );
jetEmitter.addClasspathVariable( "JET_EXAMPLE", PLUGIN_ID);
String generated = jetEmitter.generate( new NullProgressMonitor(), 
   new Object[]{genClass} );

Командная строка

При компиляции JET из командной строки проблемы с classpath, столь затрудняющие компиляцию из метода main(), отсутствуют. В приведенном выше случае трудность состояла не в компиляции javajet в код Java, а в компиляции этого кода Java в .class. Из командной строки намного удобнее управлять параметром classpath, так что пошаговое выполнение делает процесс гораздо более гладким и простым. Единственная трудность состоит в том, что Eclipse нужно выполнять в режиме "headless" (без интерфейса пользователя), но и это учтено. Чтобы скомпилировать JET, войдите в plugins/org.eclipse.emf.codegen_1.1.0/test. Этот каталог содержит примеры сценариев для Windows и Unix, а также эталонный шаблон JET для проверки.

ANT-задача

Существует ANT-задача jetc, которая может брать либо единственный атрибут template, либо fileset для нескольких шаблонов. Если сконфигурировать classpath для задачи jetc, компиляция шаблона пройдет так же гладко, как со стандартными классами Java. Подробности о том, как приобрести и использовать эту задачу, см. в разделе Ресурсы.


Настройка JET для генерации JSP

По умолчанию для разметки своего шаблона JET использует "<%" и "%>", но эту же разметку используют компоненты JSP. Чтобы сгенерировать JSP, нужно заменить разделители. Для этого в декларации jet в заголовке шаблона используйте атрибуты startTag и endTag, как в листинге 9. В данном случае в качестве начального и конечного разделителей я использовал "[%" и "%]", и "[%= expression %]", как можно видеть, интерпретируется правильно, точно так же, как прежде "<%= expression %>".

Листинг 9. Шаблон JET с модифицированными тегами
<%@ jet 
    package="com.ibm.pdc.example.jet.gen" 
    class="JspGen"
    imports="java.util.* " 
    startTag = "[%"
    endTag = "%]"
    %>

[% String argValue = (String)argument; %]
package [%= argValue %];

Связываем всё вместе

К сожалению, слишком часто для многократного использования кода применяются операции копирования и вставки, как в больших, так и в малых масштабах. Решение этой проблемы часто не очевидно, и даже объектно-ориентированные языки не помогают. В тех случаях, когда повторяется один и тот же базовый образец кода, но с мелкими изменениями в реализации, помещение общего кода в шаблон с последующим использованием JET для генерации вариантов служит прекрасным способом сберечь время и усилия, потраченные на механическую работу. JSP-компоненты уже пошли по этому пути, но JET идет еще дальше. Шаблоны JET используют ту же базовую разметку и семантику, что и JSP, но предоставляют больше возможностей по настройке. Шаблоны можно скомпилировать заранее, чтобы получить больше контроля над ними, или распространять и компилировать в среде исполнения, чтобы получить повышенную гибкость.

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

Ресурсы

Научиться

Получить продукты и технологии

Обсудить

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы в первый раз заходите в developerWorks. Выберите данные в своем профиле (имя, страна/регион, компания) которые будут общедоступными и будут отображаться, когда вы публикуете какую-либо информацию. Вы можете изменить данные вашего ИБМ аккаунта в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Open source, Технология Java
ArticleID=404828
ArticleTitle=Моделирование при помощи среды Eclipse Modeling Framework: Часть 2. Генерация кода с применением шаблонов Eclipse Java Emitter Templates
publish-date=06292009