Недавно я посетил местную встречу rheinJUG в Дюссельдорфе. В то время как тема сессии была Eclipse e4, статья спонсора ночи предоставила некоторые материалы по Xtext, которые меня очень заинтересовали. Причина в том, что в настоящее время на работе мы разрабатываем мобильное Java-приложение (J9, CDC / Foundation 1.1 для Windows CE6), для которого нам нужен простой и надежный способ настройки навигации через приложение.
В предыдущей итерации мы — в основном из-за нехватки времени — жестко запрограммировали большинство навигационных путей, но на этот раз приложение было более сложным, и сделать это снова не было возможным. Сначала мы думали о конфигурации на основе XML, но это казалось трудным для написания (и чтения), а также означало бы, что нам придется платить цену за его анализ при каждом запуске приложения.
Введите Xtext: основанная на Eclipse инфраструктура / библиотека для создания текстовых DSL. Короче говоря, вы просто предоставляете грамматическое описание нового DSL в соответствии с вашими потребностями, и буквально за несколько щелчков мышью вам предоставляется редактор содержимого Eclipse с подсветкой синтаксиса, контурным представлением и, при необходимости, генератор кода на основе этого языка.
Начало работы: образец грамматики
Есть хорошее руководство, предоставляемое как часть документации Xtext, но я считаю, что было бы полезно предоставить еще один пример того, как правильно использовать DSL. Я не буду вдаваться в подробности каждого шага, потому что настройка Xtext — это Eclipse 3.6 Helios — это всего лишь вопрос ввода URL-адреса сайта обновления, а предоставленный мастер создания нового проекта делает начальную настройку простой. Я предполагаю, что вы уже настроили Eclipse и Xtext и создали новый проект Xtext, включающий проект генератора (активируйте соответствующий флажок при прохождении мастера). В этом посте я предполагаю имя проекта com.danielschneller.navi.dsl и расширение файла .navi.
Когда мы закончим, у нас будет готовая инфраструктура для редактирования, анализа и генерации кода на основе таких файлов:
navigation rules for MyApplication
mappings {
map permission AdminPermission to "privAdmin"
map permission DataAccessPermission to "privData"
map coordinate Login to "com.danielschneller.myapp.gui.login.LoginController" in "com.danielschneller.myapp.login"
map coordinate LoginFailed to "com.danielschneller.myapp.gui.login.LoginFailedController" in "com.danielschneller.myapp.login"
map coordinate MainMenu to "com.danielschneller.myapp.gui.menu.MainMenuController" in "com.danielschneller.myapp.menu"
map coordinate UserAdministration to "com.danielschneller.myapp.gui.admin.UserAdminController" in "com.danielschneller.myapp.admin"
map coordinate DataLookup to "com.danielschneller.myapp.gui.lookup.LookupController" in "com.danielschneller.myapp.lookup"
}
navigations {
define navigation USER_LOGON_FAILED
define navigation USER_LOGON_SUCCESS
define navigation OK
define navigation BACK
define navigation ADMIN
define navigation DATA_LOOKUP
}
navrules {
from Login
on navigation USER_LOGON_FAILED
go to LoginFailed
on navigation USER_LOGON_SUCCESS
go to MainMenu
from LoginFailed
on navigation OK
go to Login
from MainMenu
on navigation ADMIN
go to UserAdministration
with AdminPermission
on navigation DATA_LOOKUP
go to DataLookup
with DataAccessPermission
from UserAdministration
on navigation BACK
go to MainMenu
from DataLookup
on navigation BACK
go to MainMenu
}
Как вы можете видеть, это хороший маленький язык для определения координат в приложении, означающий конкретный графический интерфейс пользователя для определенной задачи и возможные пути навигации между ними. При желании путь навигации может быть помечен, чтобы требовать одно или несколько разрешений для работы. Так, например, один из возможных путей навигации, показанный в приведенном выше примере, находится в главном меню приложений, которое идентифицируется идентификатором MainMenu и представлено в коде классом com.danielschneller.myapp.gui.menu.MainMenuController в com.danielschneller.myapp. Пакет OSGi меню с графическим интерфейсом, идентифицированным как DataLookup, реализованный com.danielschneller.myapp.gui.lookup.LookupController в пакете com.danielschneller.myapp.lookup.
Для использования этого пути приложение должно запросить навигационный путь DataLookup, а текущему вошедшему в систему пользователю назначить DataAccessPermission. Что именно это означает, не является целью данного руководства, достаточно сказать, что нам необходимо каким-то образом получить информацию, содержащуюся на этом специализированном языке, в наше Java-приложение в той или иной форме или форме, которую можно оценить во время выполнения. В следующем примере вся информация будет преобразована в структуру данных на основе HashMap. Для нашего небольшого мобильного приложения это имеет несколько преимуществ по сравнению с опцией XML, упомянутой ранее:
- При запуске приложения не требуется синтаксический анализ XML, что снижает производительность
- Проверка правил навигации заранее, предотвращение ошибок разбора во время выполнения
- Для доступа к информации не требуется никаких библиотек — если поместить все в простой HashMap, нам не нужно полагаться ни на какие нестандартные классы
Первое, что я сделал, когда начал работать с Xtext, определил пример входного файла, такой как приведенный выше. Затем, следуя его общей структуре, я начал извлекать для нее формальную грамматику. Конечно, первый черновик примеров данных не был идеальным, в течение нескольких итераций я уточнял некоторые синтаксисы, но в конце концов это определение грамматики, которое я придумал. Это сильно прокомментировано, чтобы позволить вам скопировать его и при этом оставить документацию нетронутой:
grammar com.danielschneller.navi.NavigationRules with org.eclipse.xtext.common.Terminals
generate navigationRules "http://com.danielschneller/fw/funkmde/navi/NavigationRules"
/*
* The top level entry point for the file.
* "Root" is just a name as good as any, but
* makes the meaning quite clear.
*/
Root:
// first thing in the file is a "keyword",
// followed by an attribute that will be
// accessible as "name" later and allow
// definition of an ID type of thing.
'navigation rules for' name=ID
// after the keyword and "name" attribute
// three sections follow, each assigned
// to an attribute for later reference
// (called "mappingdefs", "transitiondefs"
// and "ruledefs").
// Their types are defined later in the file.
mappingsdefs=Mappings
transitiondefs=TransitionDefinitions
ruledefs=NavigationRules
// semicolon ends the definition of "Root"
;
// mappings section >>>>>>>>>>>>>>>>>>>>>>>>>>>>>
/*
* Definition of the "Mappings" type used in
* the "Root" type.
*/
Mappings:
// first the keyword "mappings" is expected,
// then an open curly
'mappings' '{'
// after that a collection of "Mapping"s is
// expected. The "+=" means that they will
// all be collected in a collection type element
// called "mappings" for future reference.
// The "+" at the end means "at least one, but
// more is just fine".
(mappings+=Mapping)+
// finally the "Mappings" type requires a closing
// curly brace.
'}'
// semicolon ends the definition of "Mappings"
;
/*
* Definition of a single "Mapping", those we are
* collecting in the "mappings" attribute of the
* "Mappings" type.
*/
Mapping:
// each mapping starts with the keyword "map"
// and is followed by an element of type "MappingSpec"
'map' MappingSpec
;
/*
* Definition of a "MappingSpec" element. This is
* actually just a "parent type" for two more specific
* kinds of "MappingSpec":
*/
MappingSpec:
// no keywords are defined here, a "MappingSpec"
// can be either a "PermissionMappingSpec" or a
// "CoordinateMappingSpec". Any of these will be
// fine where a "MappingSpec" is asked for.
PermissionMappingSpec | CoordinateMappingSpec
;
/*
* Definition of a "PermissionMappingSpec" element.
*/
PermissionMappingSpec:
// first the keyword "permission" is required.
// then a "name" attribute is expected of type ID.
// Following the name the "to" keyword is expected,
// followed by a string that is stored in the "value"
// attribute
'permission' name=ID 'to' value=STRING
;
/*
* Definition of a "CoordinateMappingSpec" element.
* The definition is very similar to the "PermissionMappingSpec"
* but has more attributes.
*/
CoordinateMappingSpec:
// first the keyword "coordinate", then an ID stored as "name",
// the keyword "to", followed by a string stored as "controllername",
// next the keyword "in" and finally another string, memorized as
// "bundleid"
'coordinate' name=ID 'to' controllername=STRING 'in' bundleid=STRING
;
// <<<<<<<<<<<<<<<<<<<<<<<<<<<<< mappings section
// >>>>>>>>>>>>>>>>>>>>>>>>>>>>> navigations section
/*
* Definition of the "TransitionDefinitions" type used in
* the "Root" type.
*/
TransitionDefinitions:
// first, this element is introduced with the "navigations"
// keyword, followed by an open curly brace.
'navigations' '{'
// after that a collection of "TransitionDefinition"s is
// expected. The "+=" means that they will
// all be collected in a collection type element
// called "transitions" for future reference.
// The "+" at the end means "at least one, but
// more is just fine".
(transitions+=TransitionDefinition)+
// the element ends with a closing curly brace
'}'
;
/*
* Definition of a "TransitionDefinition" element. This
* one is very simple.
*/
TransitionDefinition:
// the keyword "define navigation" is required first,
// then a "name" attribute of type ID is expected.
'define navigation' name=ID
;
// <<<<<<<<<<<<<<<<<<<<<<<<<<<<< navigations section
// >>>>>>>>>>>>>>>>>>>>>>>>>>>>> navrules section
/*
* Definition of the "NavigationRules" element.
*/
NavigationRules:
// Element starts with the keywords "navrules" and
// open curly.
'navrules' '{'
// collection attribute called "rules", consisting
// of one or more occurrences of a "Rule" element.
(rules+=Rule)+
// element finishes with a closing curly keyword
'}'
;
/*
* Definition of a "Rule" element as used in the "NavigationRules"
* element.
*/
Rule:
// first the "from" keyword, then a reference to one of the
// coordinate mappings defined earlier. This time no new
// definition of a coordinate is required, but one of those
// that have been listed before. So the type here is put in
// square brackets
'from' source=[CoordinateMappingSpec]
// following the source specification, one or more "Destination"
// type elements are expected, collected in a collection attribute
// named "destinations"
(destinations+=Destination)+
;
/*
* Definition of a "Destination" type. These are collected
* in a "Rule".
*/
Destination:
// first comes an "on navigation" keyword. After that a
// reference to one of the Transition elements defined
// in the "navigations" section is required and stored
// in the "transition" attribute.
// after that follows a "go to" keyword and a reference
// to a coordinate mapping, stored in the "target" attribute.
// finally - as with the "destinations" collection attribute
// in the "Rule" element - a "permissions" collection is
// defined to store none or more (*) "PermissionReference"
// elements.
'on navigation' transition=[TransitionDefinition]
'go to' target=[CoordinateMappingSpec]
(permissions+=PermissionReference)*
;
/*
* Definition of a "PermissionReference" type. This is used
* in the "permissions" collection of a "Destination".
*/
PermissionReference:
// first, a "with" keyword is expected. After that a
// "permission" attribute stores a reference to one of
// the previously defined permission mappings from the
// "mappings" section.
'with' permission=[PermissionMappingSpec]
;
// <<<<<<<<<<<<<<<<<<<<<<<<<<<<< navrules section
Это то, что XText может переварить и создать плагин редактора и представление схемы. Просто сохраните это как navigationRules.xtext — когда вы создали проект XText в Eclipse с помощью мастера, он должен был быть подготовлен для вас.
Копирование и вставка этого в файл .xtext в Eclipse обеспечит вас подсветкой синтаксиса, дополнением кода и проверкой синтаксиса, упрощая просмотр файлов грамматики.
После этого щелкните правой кнопкой мыши файл .mwe2, лежащий рядом с файлом грамматики в представлении Package Explorer, и выберите Run As MWE2 Workflow из контекстного меню. Это займет некоторое время и сгенерирует несколько классов, как в текущем (XText) проекте, так и в сопутствующем … ui проекте.
Затем щелкните правой кнопкой мыши проект Xtext и выберите « Запуск от имени приложения Eclipse» из контекстного меню. Это вызовет другой экземпляр Eclipse с недавно созданной поддержкой файлов правил навигации (с суффиксом .navi).
Чтобы попробовать это, просто создайте новый проект и в этом новый файл. Убедитесь, что его имя оканчивается на .navi. При появлении запроса обязательно примите добавление природы Xtext в проект. Вам будет представлен новый пустой редактор, в котором уже есть маркер ошибки. Это связано с тем, что согласно нашему определению грамматики, пустой файл не соответствует всем указанным нами правилам. Попробуйте дважды нажать комбинацию клавиш завершения кода (Ctrl-Space) и посмотрите, что произойдет:
Первое завершение кода заполняет правила навигации для детали. Согласно грамматике, это единственный допустимый текст в начале файла, поэтому он вставляется автоматически. Повторное нажатие Ctrl-Space скажет вам, что теперь вам нужно Имя типа ID. Просто попробуйте и попробуйте завершение. Это поможет вам создать синтаксически обоснованный файл правил навигации. Обратите внимание, что представление «Проблемы» говорит вам, что в данный момент не так. Также обратите внимание, что когда вы достигнете части, где ссылки ожидаются по грамматике (например, при определении координат источника и назначения в правиле навигации), вы получите предложения, основанные на том, что вы ввели ранее.
Вот как весь пример сверху выглядит в редакторе:
Пока вы все еще уточняете и настраиваете свои грамматические определения, вы, вероятно, закроете этот экземпляр Eclipse и снова откроете его, как только вы повторили шаги рабочего процесса MWE2 Run As в основном экземпляре. В долгосрочной перспективе я предлагаю вам создать проект Feature и Update Site, чтобы упростить распространение и обновление промежуточных итераций.
Генерация кода
Теперь, когда у нас есть полный Xtext DSL, определенный и готовый к работе, давайте посмотрим на стороны создания кода. Эта часть совершенно необязательна: вы можете включать необходимые библиотеки Xtext в среду выполнения ваших приложений (хотя они кажутся многочисленными) и просто использовать их для динамической загрузки и анализа файлов .navi на лету. Вероятно, это было бы хорошей идеей, если бы вы все равно писали приложение на основе Eclipse. Однако при нацеливании на очень ограниченную платформу, такую как JavaME, эта опция не подходит. Вместо этого мы теперь создадим генератор кода, который обеспечивает преобразование из синтаксиса DSL в более классические термины Java — в частности, мы создадим структуру данных на основе HashMap, которая несет всю ту же информацию, но в терминах Java.
Это пример того, как будет выглядеть сгенерированный вывод:
public class NaviRules {
private Map navigationRules = new Hashtable();
// ...
public NaviRules() {
NaviDestination naviDest;
// ========== From Login (com.danielschneller.myapp.gui.login.LoginController)
// ========== On USER_LOGON_FAILED
// ========== To LoginFailed (com.danielschneller.myapp.gui.login.LoginFailedController in com.danielschneller.myapp.login)
naviDest = new NaviDestination();
naviDest.action = "USER_LOGON_FAILED";
naviDest.targetClassname = "com.danielschneller.myapp.gui.login.LoginFailedController";
naviDest.targetBundleId = "com.danielschneller.myapp.login";
store("com.danielschneller.myapp.gui.login.LoginController", naviDest);
// ========== On USER_LOGON_SUCCESS
// ========== To MainMenu (com.danielschneller.myapp.gui.menu.MainMenuController in com.danielschneller.myapp.menu)
naviDest = new NaviDestination();
naviDest.action = "USER_LOGON_SUCCESS";
naviDest.targetClassname = "com.danielschneller.myapp.gui.menu.MainMenuController";
naviDest.targetBundleId = "com.danielschneller.myapp.menu";
store("com.danielschneller.myapp.gui.login.LoginController", naviDest);
// =============================================================================
// ========== From LoginFailed (com.danielschneller.myapp.gui.login.LoginFailedController)
// ========== On OK
// ========== To Login (com.danielschneller.myapp.gui.login.LoginController in com.danielschneller.myapp.login)
naviDest = new NaviDestination();
naviDest.action = "OK";
naviDest.targetClassname = "com.danielschneller.myapp.gui.login.LoginController";
naviDest.targetBundleId = "com.danielschneller.myapp.login";
store("com.danielschneller.myapp.gui.login.LoginFailedController", naviDest);
// .... and so on ...
}
}
Вспомогательный класс NaviDestination опущен, но, как правило, является просто классом структурного типа держателя значения.
При создании проекта Xtext с помощью мастера ранее мы создали третий проект Eclipse, заканчивающийся … генератором. Его папка src содержит три подкаталога: модель, шаблоны и рабочий процесс. Поместите образец файла .navi в каталог модели. Он будет служить входом для генератора.
Создайте первый шаблон
Генерация кода основана на шаблонах. Xtext использует механизм шаблонов Xpand. В каталоге шаблонов создайте новый шаблон Xpand с помощью контекстного меню. Назовите его NaviRules.xpt, откройте его и вставьте следующее:
«REM»
import the namespace defined in our DSL model
«ENDREM»
«IMPORT navigationRules»
«REM»
Define a template called "main" for elements of
type "Root". The minus sign at the end takes care
of not adding a newline at the end of it.
«ENDREM»
«DEFINE main FOR Root-»
«ENDDEFINE»
Поскольку в файле правил навигации есть только один экземпляр корневого элемента, это будет главная точка входа — отсюда и название. Нет необходимости называть это главным, но это кажется уместным.
Теперь между DEFINE и ENDDEFINE вставьте то, что должно быть сгенерировано: Как показано выше, нам нужен новый исходный файл Java с именем NaviRules.java:
...
«DEFINE main FOR Root-»
«FILE "NaviRules.java"-»
«ENDFILE-»
«ENDDEFINE»
...
Опять же, генерируемое содержимое помещается в скобки FILE и ENDFILE. Все, что не заключено в «», будет дословно использовано в выходном файле. Итак, прежде всего, поместите в статические части файла Java. Сначала я написал исходный текст для одного правила навигации, удостоверился, что он скомпилирован, а затем скопировал соответствующие части в шаблон по частям:
...
«FILE "NaviRules.java"-»
import java.util.*;
public class NaviRules {
public static class NaviDestination {
String action;
List requiredPermissions = new ArrayList();
String targetClassname;
String targetBundleId;
NaviDestination() {};
public final List getRequiredPermissions() {
return new ArrayList(requiredPermissions);
}
// let Eclipse generate getters, setters,
// equals and hashCode methods for this
}
private Map navigationRules = new Hashtable();
«ENDFILE-»
...
Теперь, в этом нет ничего особенного. Для заполнения элементов из правила навигации в DSL-файле необходимо указать следующее:
...
private Map navigationRules = new Hashtable();
public NaviRules() {
NaviDestination naviDest;
«REM»
Iterate all elements in the "rules" collection attribute
of the "ruledefs" attribute of the "Root" element. Call
each iterated element (which is of type "Rule") "rule" and
expand the "ruletmpl" template for it here.
«ENDREM»
«FOREACH ruledefs.rules AS r»«EXPAND ruletmpl FOR r»«ENDFOREACH»
}
...
В конструкторе класса мы сначала определяем локальную переменную naviDest ранее объявленного типа. Затем, как говорится в комментарии, инструкция FOREACH будет проходить итерацию по всем элементам типа правила. Поначалу это может показаться не совсем очевидным. Помните, что в данный момент в шаблоне текущая область является элементом «Root» из файла правил навигации. Он имеет атрибут под названием ruledefs согласно определению грамматики. Этот атрибут имеет тип NavigationRules, который, в свою очередь, имеет атрибут коллекции с именем rules, содержащий объекты типа Rule. Внутри цикла текущий элемент может быть адресован именем переменной шаблона r. Тело цикла (между FOREACH и ENDFOREACH) содержит еще одну инструкцию Xpand для расширения шаблона, называемого ruletmpl, который будет объявлен следующим.
Не беспокойтесь, хотя это поначалу немного сложно: переключение контекстов между Java и областями шаблонов в Eclipse значительно упрощается, поскольку редактор шаблонов Xpand будет синтаксически менять цвет (статические части выделены синим цветом) и также поможет вам с завершение кода внутри частей шаблона Xpand. Если вы пройдете через Ctrl-Spacing, все станет более очевидно, чем при чтении примера.
Теперь для шаблона ruletmpl. Поместите его под оператором ENDDEFINE, принадлежащим основному шаблону:
...
«ENDFILE-»
«ENDDEFINE»
«DEFINE ruletmpl FOR Rule-»
// ========== From «source.name» («source.controllername»)
«FOREACH destinations AS d»«EXPAND destTmpl(source) FOR d»«ENDFOREACH»
// =============================================================================
«ENDDEFINE»
Вы видите ту же идею, использованную снова: статические части, которые передаются в выходной файл 1: 1, и операторы Xpand, которые заполняют данные из файла определения правил навигации. В этом случае вы видите ссылки на атрибуты элемента Rule. Согласно инструкции FOREACH в предыдущем шаблоне, под рукой будет повторяться для каждого экземпляра правила в нашем исходном файле. Внутри этого определения текущей областью является область действия Rule, поэтому для «source.name» сначала берется атрибут name объекта CoordinateMappingSpec, на который ссылается источник в правиле, а затем атрибут controllername.
Затем следующий цикл FOREACH повторяет одно или несколько возможных назначений каждого правила. Вместо того, чтобы просто применять шаблон (destTmpl) для каждого пункта назначения, мы также передаем соответствующий CoordinateMappingSpec, сохраненный в атрибуте источника правила. Это затем используется в следующем шаблоне:
...
«DEFINE destTmpl(CoordinateMappingSpec source) FOR Destination-»
// ========== On «transition.name»
// ========== To «target.name» («target.controllername» in «target.bundleid»)
naviDest = new NaviDestination();
naviDest.action = "«transition.name»";
naviDest.targetClassname = "«target.controllername»";
naviDest.targetBundleId = "«target.bundleid»";
«FOREACH permissions AS p»«EXPAND permTmpl FOR p»«ENDFOREACH»
store("«source.controllername»", naviDest);
«ENDDEFINE»
«DEFINE permTmpl FOR PermissionReference-»
naviDest.requiredPermissions.add("«permission.value»");
«ENDDEFINE»
В этих самых внутренних шаблонах доступ к атрибутам источника и цели объектов CoordinateMappingSpec осуществляется и устанавливается для назначения членам экземпляра объекта Java NaviDestination для каждого пункта назначения. Существует только еще один (очень простой) шаблон для элементов PermissionReference. На этом файл Xpand готов.
Настройте рабочий процесс генератора
Первоначально мастер создал файл NavigationRulesGenerator.mwe2 в папке рабочего процесса. Откройте его и замените его содержимое следующим:
module workflow.NavigationRulesGenerator
import org.eclipse.emf.mwe.utils.*
var targetDir = "src-gen"
var fileEncoding = "Cp1252"
var modelPath = "src/model"
Workflow {
component = org.eclipse.xtext.mwe.Reader {
path = modelPath
// this class has been generated by the xtext generator
register = com.danielschneller.navi.NavigationRulesStandaloneSetup {}
load = {
slot = "root"
type = "Root"
}
}
component = org.eclipse.xpand2.Generator {
metaModel = org.eclipse.xtend.typesystem.emf.EmfRegistryMetaModel {}
expand = "templates::NaviRules::main FOREACH root"
outlet = {
path = targetDir
}
fileEncoding = fileEncoding
}
}
Наиболее интересными частями этого файла рабочего процесса являются раздел загрузки в компоненте Reader и разделы расширения и выпуска в компоненте Generator:
Первый соединит так называемый слот с элементом Root из наших правил навигации.
Второй вызовет оценку основного шаблона в файле NaviRules.xpt в папке шаблонов и загрузит в него все найденные экземпляры Root в файлах * .navi из src / model (modelPath).
Теперь настало время для настоящего поколения.
Запустить рабочий процесс генератора
Щелкните правой кнопкой мыши файл MWE2, который вы только что отредактировали, и выберите команду Run As MWE2 Workflow из контекстного меню. Консоль Eclipse покажет этот вывод:
0 [main] DEBUG org.eclipse.xtext.mwe.Reader - Resource Pathes : [src/model]
431 [main] DEBUG xt.validation.ResourceValidatorImpl - Syntax check OK! Resource: file:/Users/ds/ws/ws36_xtext/com.danielschneller.navi.dsl.generator/src/model/MyApp.navi
1013 [main] INFO org.eclipse.xpand2.Generator - Written 1 files to outlet [default](src-gen)
1014 [main] INFO .emf.mwe2.runtime.workflow.Workflow - Done.
Затем посмотрите на сгенерированное содержимое исходной папки src-gen. Если все прошло нормально, вы должны найти свежий файл NaviRules.java, который там находится, на основе содержимого вашего файла правил навигации и шаблонов Xpand. Попробуйте внести некоторые изменения в шаблон, а затем снова запустите рабочий процесс. Вы увидите изменения, отраженные в сгенерированном исходном файле.
Создать второй исходный файл
В каталоге шаблонов добавьте еще один файл шаблона Xpand Navigation.xpt со следующим содержимым:
«IMPORT navigationRules»;
«DEFINE main FOR Root-»
«FILE "Navigation.java"-»
public final class Navigation {
«FOREACH ruledefs.rules.destinations.transition.collect(e|e.name).toSet().sortBy(e|e) AS t»«EXPAND actionTmpl FOR t»«ENDFOREACH»
private final String name;
private Navigation(String aName) {
name = aName;
}
public String getName() {
return name;
}
}
«ENDFILE-»
«ENDDEFINE»
«DEFINE actionTmpl FOR String-»
/** Constant for Navigation «this» */
public static final Navigation «this» = new Navigation("«this»");
«ENDDEFINE»
This is a template for a type-safe enumeration that can be used in Java 1.4 — remember I had to do this for JavaME.
Notice the FOREACH loop in this case. It demonstrates that not only simple iterations are possible, but that Xpand allows more complex operations as well. In this case it will collect the names of all the navigation transitions from all the Destinations in the navigation rules. These are of type String. They are made unique by converting them to a Set datastructure and then finally sorted in their natural order. The resulting list of sorted strings is then iterated, each one — called t — is passed to the actionTmpl template. It is very simple, just placing the string itself («this») into a single line of Java source code.
Of course, strictly speaking this is a rather complicated procedure to get the same information we could also have taken from the TransitionDefinitions element in the rules definition. However I think it serves as a nice example for additional Xpand capabilities. For a full description of its possibilities, have a look at the Xpand Reference in the Eclipse documentation.
To use the new template, add another section to the MWE2 workflow definition:
component = org.eclipse.xpand2.Generator {
metaModel = org.eclipse.xtend.typesystem.emf.EmfRegistryMetaModel {}
expand = "templates::Navigation::main FOREACH root"
outlet = {
path = targetDir
}
fileEncoding = fileEncoding
}
Повторный запуск приведет к несколько иному выводу, давая понять, что два файла были сгенерированы. Вот что выходит из папки src-gen как Navigation.java:
public final class Navigation {
/** Constant for Navigation ADMIN */
public static final Navigation ADMIN = new Navigation("ADMIN");
/** Constant for Navigation BACK */
public static final Navigation BACK = new Navigation("BACK");
/** Constant for Navigation DATA_LOOKUP */
public static final Navigation DATA_LOOKUP = new Navigation("DATA_LOOKUP");
/** Constant for Navigation OK */
public static final Navigation OK = new Navigation("OK");
...
Больше…
Это было как раз о моих первых экспериментах с Xtext. Я уверен, что с этим еще многое можно сделать. Для получения дополнительной информации, пожалуйста, взгляните на это очень хорошее
руководство по началу работы с Xtext Питера Фриза из Itemis.
С http://www.danielschneller.com/2010/08/code-generation-with-xtext.html