Статьи

Начало работы с Xtext, часть 2

На прошлой неделе я показал вам, как легко создать DSL с Xtext . В этой статье мы рассмотрим, как использовать модели, созданные с помощью DSL.

Цель

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

Поскольку мы ожидаем, что будем писать более одного приложения этого типа, и поскольку мы также хотели бы иметь возможность выразить структуру приложения на бизнес-уровне (один из основных драйверов для DSL и MDSD в этом отношении), мы пришли с идеей использования DSL для описания того, что делает приложение. Определение DSL — это то, что мы сделали на прошлой неделе. На этой неделе нам нужно сопоставить концепции DSL с некоторым кодом и некоторыми API, с которыми мы будем программировать.

Итак, мы собираемся создать набор шаблонов кода для генератора кода, который затем сможет читать наши модели DSL и создавать код постоянства для нас.

Подготовить

Чтобы извлечь какую-либо выгоду из этого поста, вам нужна рабочая версия DSL, которую мы создали в прошлый раз. Если вы новичок в этой серии, вернитесь на одну неделю назад и выполните действия, описанные в статье на прошлой неделе. Это не должно занять у вас больше 30 минут, и я обещаю, что мы будем ждать вас здесь.

Создать основной шаблон

Вероятно, мы собираемся создать несколько классов (у нас будут бины Entity, DAO и, возможно, некоторые интерфейсы), поэтому разумно разделить все эти шаблоны на отдельные файлы. Генератор кода должен вызвать все эти файлы шаблонов, чтобы сгенерировать наше приложение. Чтобы сделать обслуживание более удобным, у каждого генератора кода должна быть главная точка входа, поэтому давайте создадим основной файл шаблона, который служит этой главной точкой входа.

Во-первых, пожалуйста, удалите все в org.xtext.example.entity.generator / templates . Затем создайте новый шаблон Xpand в этом каталоге, выбрав File -> New -> Other … -> Xpand Template . Это будет наш основной шаблон, поэтому давайте назовем его Main.xpt . Откроется пустой редактор.

Вставьте следующую строку:

«IMPORT entity»

Если вам интересно, как вставить специальные кавычки, используйте завершение кода (нажмите CTRL + Пробел или CMD + Пробел на Mac), чтобы вставить их. После активации завершения кода появится диалоговое окно с вопросом, нужно ли добавить природу Xpand в проект. Пожалуйста, подтвердите этот диалог.

Оператор IMPORT импортирует метамодель вашего DSL, что делает его известным Xpand, что позволит нам использовать метамодель в наших шаблонах.

Затем добавьте следующие строки:

«DEFINE main FOR Model»
«EXPAND Entity::entity FOREACH this.types.typeSelect(Entity)»
«ENDDEFINE»

Это добавит новый шаблон с именем main в ваш файл шаблона. Указывая FOR Model , мы указываем, что этот шаблон будет позже привязан к переменной, представляющей вашу модель. Оператор EXPAND … FOREACH вызовет шаблон с именем entity в файле шаблона с именем Entity для всех элементов типов коллекций в вашей модели. Помните, что ваша модель содержит ряд сущностей и определений типов. Они хранятся в типах коллекции . Оператор typeSelect (Entity) гарантирует, что генератор рассматривает только элементы, имеющие тип Entity (мы не хотим создавать Java-бины для простых типов данных).

Создать шаблон для бобов Java

Каждое ориентированное на данные приложение нуждается в группе классов для хранения данных. Обычно называемые сущностями , в нашем случае эти классы являются POJO. Итак, давайте теперь создадим шаблон, который поможет нам создавать POJO из нашей модели.

Пожалуйста, добавьте еще один шаблон Xpand в ваш проект, выбрав File -> New -> Other … -> Xpand Template . Назовите новый файл Entity.xpt , сохранив его в той же папке, что и Main.xpt .

Снова запустите файл, импортировав метамодель:

«IMPORT entity»

Затем добавьте следующие строки:

«DEFINE entity FOR entity::Entity»
«FILE this.name + ".java"»
public class «this.name» {
«EXPAND attribute FOREACH this.attributes»
}
«ENDFILE»
«ENDDEFINE»

Этот шаблон создаст новый файл Java для каждого экземпляра объекта, для которого он вызывается. Это достигается с помощью оператора FILE , который принимает имя выходного файла в качестве первого параметра.

Как вы можете догадаться, нам также нужен шаблон для создания атрибутов, поэтому, пожалуйста, добавьте следующие строки:

«DEFINE attribute FOR Attribute»
private «this.type.name» «this.name»;

public void set«this.name.toFirstUpper()»(«this.type.name» «this.name») {
this.«this.name» = «this.name»;
}

public «this.type.name» get«this.name.toFirstUpper()»() {
return this.«this.name»;
}
«ENDDEFINE»

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

Создать шаблон для DAO

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

Создайте шаблон DAO.xpt и вставьте этот текст:

«IMPORT entity»

«DEFINE dao FOR entity::Entity»
«FILE this.name + "DAO.java"»
import java.util.Collection;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
public class «this.name»DAO
extends HibernateDaoSupport {
«EXPAND crud FOR this»
}
«ENDFILE»
«ENDDEFINE»

«DEFINE crud FOR Entity»
public «this.name» load(Long id) {
return («this.name»)getHibernateTemplate().get(«this.name».class, id);
}

@SuppressWarnings("unchecked")
public Collection<«this.name»> loadAll() {
return getHibernateTemplate().loadAll(«this.name».class);
}

public «this.name» create(«this.name» entity) {
return («this.name») getHibernateTemplate().save(entity);
}

public void update(«this.name» entity) {
getHibernateTemplate().update(entity);
}

public void remove(«this.name» entity) {
getHibernateTemplate().delete(entity);
}
«ENDDEFINE»

Не забудьте вызвать этот шаблон из основного файла шаблона Main.xpt :

«IMPORT entity»

«DEFINE main FOR Model»
«EXPAND Entity::entity FOREACH this.types.typeSelect(Entity)»
«EXPAND DAO::dao FOREACH this.types.typeSelect(Entity)»
«ENDDEFINE»

Создать модель

В своем рабочем пространстве откройте org.xtext.example.entity.generator / src / model / MyModel.entity , убедившись, что в нем содержится следующая модель:

typedef String
typedef Integer
typedef Double
typedef Date mapsto java.util.Date

entity Customer {
String firstName
String lastName
String email
Address shippingAddress
Order* orders
}

entity Address {
String line1
String line2
String city
String postcode
String state
String country
}

entity Product {
String description
Double price
}

entity Book extends Product {
String ISBN
String author
}

entity Order {
Date orderDate
OrderLine* orderLines
}

entity OrderLine {
Product item
Integer amount
}

Настройка генератора

Прежде чем мы сможем запустить генератор и трансформировать нашу модель, нам нужно настроить некоторые аспекты проекта генератора.

Сначала откройте рабочий процесс генератора в /org.xtext.example.entity.generator/src/workflow/EntityGenerator.mwe . Нам нужно изменить имя корневого шаблона, чтобы оно совпадало с именем корневого шаблона, который мы выбрали. Мы также хотели бы, чтобы генератор правильно форматировал вывод, поэтому мы собираемся добавить beautifier вывода.

Измените компонент генератора в файле рабочего процесса, чтобы он соответствовал следующему:

  <component class="org.eclipse.xpand2.Generator">
<metaModel class="org.eclipse.xtend.typesystem.emf.EmfRegistryMetaModel"/>
<fileEncoding value="MacRoman"/>
<expand value="templates::Main::main FOR model"/>
<genPath value="src-gen"/>
<beautifier class="org.eclipse.xpand2.output.JavaBeautifier"/>
</component>

Примечание: вам нужно оставить атрибут fileEncoding как есть. Вышеуказанный фрагмент действителен только на Mac.

Поскольку средство форматирования основано на модуле форматирования Java Eclipse (см. Также этот пост, посвященный устройству форматирования кода Eclipse, которое я написал несколько лет назад и которое все еще привлекает 200 читателей в день к моему блогу), мы должны добавить некоторые зависимости пакета в проект генератора ,

Для этого откройте /org.xtext.example.entity.generator/META-INF/MANIFEST.MF , перейдите в Зависимости и добавьте следующие пакеты:

  • org.eclipse.jface.text
  • org.eclipse.core.runtime
  • org.eclipse.jdt.core
  • org.eclipse.xtext.log4j
  • com.ibm.icu

Запуск генератора

Чтобы запустить генератор, просто выберите файл рабочего процесса /org.xtext.example.entity.generator/src/workflow/EntityGenerator.mwe и выберите Run as … -> MWE Workflow из контекстного меню. Вы увидите кучу сообщений журнала в представлении консоли, и через несколько секунд вы найдете группу только что сгенерированных файлов в /org.xtext.example.entity.generator/src-gen .

Использование DSL для управления генератором

В приведенном выше примере мы написали модель DSL вручную (ну, вы, вероятно, только что скопировали ее из этого поста), но это, конечно, не обычный шаблон использования. Итак, как вы можете использовать редактор DSL для создания моделей, которые затем читаются генератором?

На самом деле, это очень легко, если вы выполните следующие действия:

  1. Запустите DSL в рабочей среде времени выполнения (обратитесь к последней части, чтобы узнать как)
  2. В рабочей среде времени выполнения создайте новый проект подключаемого модуля Eclipse . Выберите любое имя, которое вам нравится, например org.xtext.example.entity.mymodel . Убедитесь, что вы сняли флажок « Создать активатор» на второй странице мастера — он нам не нужен.
  3. Добавьте все следующие зависимости в файл манифеста вновь созданного проекта:

    • org.xtext.example.entity
    • org.eclipse.xpand
    • org.eclipse.xtend
    • org.eclipse.xtext
    • org.eclipse.emf.mwe.core
    • org.eclipse.emf.mwe.utils
    • org.eclipse.xtend.typesystem.emf
    • org.eclipse.jface.text
    • org.eclipse.core.runtime
    • org.eclipse.jdt.core
    • org.eclipse.xtext.log4j
    • com.ibm.icu
    • org.xtext.example.entity.generator
  4. Создайте новый файл рабочего процесса в /org.xtext.example.entity.mymodel/src/workflow/EntityGenerator.mwe и вставьте следующий текст:
    <workflow>
    <cartridge file="workflow/EntityGenerator.mwe" model="classpath:/model/MyModel.entity"/>
    </workflow>
  5. Патч файла рабочего процесса в генераторе: вернитесь в исходное рабочее пространство и измените строку, содержащую <component class = «org.eclipse.xtext.MweReader» uri = «… на <component class =» org.eclipse.xtext. MweReader «uri =» $ {model} «>
  6. Вернитесь в рабочую область времени выполнения и создайте новый файл MyModel.entity в /org.xtext.example.entity.mymodel/src/model/MyModel.entity .

Теперь вы можете использовать редактор DSL для создания вашей модели. Генератор можно запустить, запустив файл рабочего процесса генератора EntityGenerator.mwe в недавно созданном проекте модели /org.xtext.example.entity.mymodel в рабочей области времени выполнения.

Вывод

Я надеюсь, что последний раздел не слишком беспокоил вас. Хорошей новостью является то, что мы собираемся предоставить мастера, который поможет вам настроить проекты модели DSL — см. Ошибку 281214 .

Из-за ограниченного пространства этот пример не является полным в том смысле, что из него можно создать полнофункциональное приложение CRUD. Однако вы должны увидеть, что возможно с текстовыми DSL и генерацией кода.

Если вам интересно увидеть весь генератор, напишите мне строку (peter dot friese [at] itemis dot de) или DM мне в Twitter (мой идентификатор в Twitter — @peterfriese).