IBM®
Перейти к тексту
    в России и странах СНГ [изменить]    Условия использования
 
 
   
    Главная страница    Продукты    Услуги и решения    Поддержка и загрузка    Мой профиль    
Перейти к тексту

developerWorks Россия  >  Open source | Технология Java  >

Моделирование при помощи среды Eclipse Modeling Framework: Часть 3. Настройка сгенерированных моделей и редакторов с применением Eclipse JMerge

Настройка сгенерированных моделей и редакторов с применением Eclipse JMerge

developerWorks
Опции документа

Опции документа, требующие включения JavaScript, не отображаются

Обсудить

Исходные тексты примера


Выскажите мнение об этой странице

Помогите нам улучшить содержание


Уровень сложности: средний

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

07.07.2009

Eclipse Modeling Framework содержит инструмент open source JMerge, который делает процесс генерации кода гибким и настраиваемым. В этой статье приводится пример, иллюстрирующий, как добавить JMerge в приложение и настроить его для разных ситуаций.

Обзор

Из второй статьи этой серии вы узнали, как сэкономить время и обеспечить многократное использование кода при помощи шаблонов и инструментов генерации кода. Но в большинстве случаев этого недостаточно. Нужна возможность вставлять этот сгенерированный код в уже существующий, или позволить будущим разработчикам настраивать генерируемый код без необходимости переписывать все заново при каждой регенерации кода. В идеале создатели генератора кода хотят иметь возможность удовлетворять все потребности будущих разработчиков от изменения реализаций метода и сигнатур метода до изменения структуры наследования генерируемых классов. Это сложная задача, и хороших общих решений пока нет. Но есть хорошее решение для Java™: JMerge.

JMerge — это инструмент open source, который входит в состав Eclipse Modeling Framework. JMerge позволяет настраивать сгенерированные модели и редакторы без нарушения внесенных изменений из-за регенерации кода. JETEmitter поддерживает JMerge, если описать, как объединить свежеесгенерированный код с уже существующим, настроенным кодом. В этой статье приводится пример, на котором иллюстрируются некоторые из имеющихся возможностей.



В начало


Первые шаги

Предположим, что вы присоединились к новому проекту, где нужно создать тестовый класс JUnit для каждого написанного вами класса, который будет тестировать каждый написанный вами метод. Будучи добросовестным, но рациональным (ленивым) разработчиком, вы решили написать подключаемый модуль, на вход которого будут подаваться классы Java, а на выходе генерироваться тесты JUnit. Вы принялись за работу и создали шаблоны JET и подключаемый модуль, а теперь хотите, чтобы пользователи могли настраивать сгенерированные тесты, но с сохранением возможности их регенерации при изменении интерфейса первоначального класса. Для этого воспользуемся JMerge.

Код для вызова JMerge из подключаемого модуля довольно прямолинеен (см. листинг 1). Создаем новый экземпляр JMerger с URI merge.xml, устанавливаем источник и цель и вызываем merger.merge(). После этого можно использовать объединенный код как merger.getTargetCompilationUnit().


Листинг 1. Вызов JMerge
	// ...
	JMerger merger = getJMerger();
	
	// set source
	merger.setSourceCompilationUnit(
		merger.createCompilationUnitForContents(generated));
	
	// set target
	merger.setTargetCompilationUnit(
		merger.createCompilationUnitForInputStream( 
			new FileInputStream(target.getLocation().toFile())));
	
	// merge source and target
	merger.merge();

	// extract merged contents
	InputStream mergedContents = new ByteArrayInputStream(
		merger.getTargetCompilationUnit().getContents().getBytes());
		
	// overwrite the target with the merged contents
	target.setContents(mergedContents, true, false, monitor);
	// ...

// ...
private JMerger getJMerger() {
	// build URI for merge document
	String uri = 
	   Platform.getPlugin(PLUGIN_ID).getDescriptor().getInstallURL().toString();
	uri += "templates/merge.xml";
		
	JMerger jmerger = new JMerger();
	JControlModel controlModel = new JControlModel( uri );
	jmerger.setControlModel( controlModel );
	return jmerger;
}

Для начала воспользуемся минимальным вариантом merge.xml (листинг 2). Он декларирует тег <merge> и назначает декларацию по умолчанию namespace. Основная часть заключена в элементе merge:pull. В данном случае тело каждого метода в источнике будет заменяться на тело соответствующего метода в цели. Если в цели метод не существует, он будет создан. Если в источнике метод присутствует, а в цели — нет, он останется один.


Листинг 2. Простейший merge.xml
<?xml version="1.0" encoding="UTF-8"?>
<merge:options xmlns:merge=
   "http://www.eclipse.org/org/eclipse/emf/codegen/jmerge/Options">

    <merge:pull 
      sourceGet="Method/getBody"
      targetPut="Method/setBody"/>

</merge:options>



В начало


Различение сгенерированных методов

Проблема этого простого подхода выясняется быстро: при каждом изменении источника и регенерации все ваши изменения теряются. Нужно добавить какой-то механизм, чтобы сообщить JMerge, что некоторые методы были отредактированы и не должны переписываться. Для этого воспользуемся элементом <merge:dictionaryPattern>. Он позволяет использовать регулярные выражения, чтобы различать элементы Java.


Листинг 3. Простой dictionaryPattern
<merge:dictionaryPattern
   name="generatedMember" 
   select="Member/getComment" 
   match="\s*@\s*(gen)erated\s*\n"/>

<merge:pull 
   targetMarkup="^gen$"
   sourceGet="Method/getBody"
   targetPut="Method/setBody"/>

dictionaryPattern определяет регулярное выражение, которое сопоставляет Member с "@generated" где-нибудь в элементе. Атрибут select определяет, какой аспект Member будет сравниваться с регулярным выражением, указанным в атрибуте match. dictionaryPattern идентифицируется по строке gen, которая помечается круглыми скобками в значении атрибута match.

Элемент merge:pull содержит дополнительный атрибут targetMarkup. Он совпадает с dictionaryPattern, который должен совмещаться с целевым кодом до применения правила слияния. Здесь мы проверяем целевой код, а не исходный, так как пользователи могут редактировать код. Когда пользователь удаляет тег "@generated" в комментарии, dictionaryPattern не будет совпадать с целевым кодом, и тело метода не будет объединяться.


Листинг 4. Редактирование кода
/**
 * test case for getName
 * @generated
 */
public void testSimpleGetName() {
	// because of the @generated tag,
	// any code in this method will be overridden
}

/**
 * test case for getName
 */
public void testSimpleSetName() {
	// code in this method will not be regenerated
}

Можно заметить, что некоторые элементы не должны редактироваться, и нужно исключить любую попытку их изменения. Для этого определим еще один dictionaryPattern, который будет следить за каким-нибудь другим тегом в исходном коде (а не в целевом), например, @unmodifiable. Затем определим правило pull, которое проверяет sourceMarkup, вместо targetMarkup, так что пользователь не сможет удалить тег и заблокировать слияние.


Листинг 5. merge.xml для немодифицируемого кода
<merge:dictionaryPattern
   name="generatedUnmodifiableMembers" 
   select="Member/getComment" 
   match="\s*@\s*(unmod)ifiable\s*\n"/>

<merge:pull 
   sourceMarkup="^unmod$"
   sourceGet="Member/getBody"
   targetPut="Member/setBody"/>



В начало


Детальная настройка

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

Для этого замените прежнюю цель pull на код из листинга 6.


Листинг 6. Детальная настройка кода
<!-- if target is generated, transfer -->
<!-- change to sourceMarkup if the source is the standard -->
<merge:pull 
   targetMarkup="^gen$"
   sourceGet="Method/getBody"
   sourceTransfer="(\s*//\s*begin-user-code.*?//\s*end-user-code\s*)\n"
   targetPut="Method/setBody"/>

В этом случае переписывается все до строки "// begin-user-code" и после "// end user-code", а весь настраиваемый промежуточный код сохраняется. В приведенном выше регулярном выражении знаками "?" помечен код, который останется в целевом коде, хотя все остальное будет заменено. Нечто аналогичное можно сделать с комментариями JavaDoc, чтобы сами комментарии копировались, но оставалось место для настройки пользователем.


Листинг 7. Детальная настройка JavaDoc
<!-- copy comments except between the begin-user-doc
     and end-user-doc tags -->
<merge:pull 
  sourceMarkup="^gen$"
  sourceGet="Member/getComment"
  sourceTransfer="(\s*<!--\s*begin-user-doc.*?end-user-doc\s*-->\s*)\n"
  targetMarkup="^gen$"
  targetPut="Member/setComment"/>

Чтобы поддерживать комментарии, сначала заменим теги начала и конца в соответствии с синтаксисом комментариев HTML, так чтобы они не появлялись в генерируемом JavaDoc, и заменим атрибуты sourceGet и targetPut на "Member/getComment" и "Member/setComment". JMerge позволяет удалять и добавлять многие разные аспекты кода Java на детальном уровне (см. Приложение A).



В начало


Следующие шаги

До сих пор мы рассматривали преобразование тела методов, но JMerge может управлять и полями, инициализаторами, исключениями, возвращаемыми значениями, операторами импорта и другими элементами. К ним применимы те же основные идеи, с небольшими изменениями. Из plugins/org.eclipse.emf.codegen_1.1.0/test/merge.xml видно, как их можно применять (я использую Eclipse 2.1, и для другой версии Eclipse версия подключаемого модуля ecore может быть другой). Это довольно простой пример без использования тега sourceTransfer, но он показывает один из способов работы с исключениями, флагами и другими элементами Java.

В качестве более сложного примера можно привести способ, которым EMF использует JMerge: plugins/org.eclipse.emf.codegen.ecore_1.1.0/templates/emf-merge.xml. Из него видно, что EMF позволяет лишь частично настраивать JavaDoc, но пользуясь приведенными выше советами, вы можете добавить собственную поддержку для тела методов (путем модификации шаблонов JET с добавлением новой разметки).



В начало


Приложение A: Допустимые цели

Внутри правил dictionaryPattern и pull мы использовали "Member/getComment" и "Member/getBody" и их установщики, но существует много других возможностей. JMerge поддерживает совмещение и замену для любого класса, определенного в org.eclipse.jdt.core.jdom.IDOM*. Возможные варианты приведены в табл. 1.

Таблица 1. Допустимые цели

Тип Метод Комментарий
CompilationUnitgetHeader/setHeader
getName/setName
FieldgetInitializer/setInitializerНе содержит "="
getName/setNameИмя переменной
getName/setNameИмя класса
ImportgetName/setNameПолностью подходящее имя типа или пакет по требованию
InitializergetName/setName
getBody/setBody
MembergetComment/setComment
getFlags/setFlagsПримеры: abstract, final, native, и т.д.
MethodaddException
addParameter
getBody/setBody
getName/setName
getParameterNames/setParameterNames
getParameterTypes/setParameterTypes
getReturnType/setReturnType
PackagegetName/setName
TypeaddSuperInterface
getName/setName
getSuperclass/setSuperclass
getSuperInterfaces/setSuperInterfaces



В начало


Приложение B: Атрибуты merge:pull

В табл. 2 приведены атрибуты элемента merge:pull.

Таблица 2. Атрибуты merge:pull

Атрибут Условия
sourceGet Обязательный. Значение должно быть одним из вариантов, перечисленных в Приложении A, например, "Member/getBody".
targetPut Обязательный. Значение должно быть одним из вариантов, перечисленных в Приложении A, например, "Member/setBody".
sourceMarkup Необязятельный. Используется для выбора того, какие dictionaryPatterns должны совпадать с источником перед запуском этого правила merge:pull. Используйте форму "^dictionaryName$" и соедините несколько dictionaryPatterns в одну строку с применением символа "|".
targetMarkup Необязятельный. Используется для выбора того, какие dictionaryPatterns должны совпадать с целью перед запуском этого правила merge:pull. Используйте форму "^dictionaryName$" и соедините несколько dictionaryPatterns в одну строку с применением символа "|".
sourceTransfer Необязятельный. Регулярное выражение, которое определяет количество исходного кода, преобразуемого в целевой.




В начало


Загрузка

ИмяРазмерМетод загрузки
os-ecemf3/com.ibm.pdc.example.jet_1.0.0.zipHTTP
Информация о методах загрузки


Ресурсы

Научиться

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

Обсудить


Об авторе

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




Выскажите мнение об этой странице


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



 


 


 


Поделиться этой статьей:

забобрить забобрить memori сохранить в memori




В начало


IBM обладает всеми авторскими правами касательно информации, расположенной на developerWorks. Использование информации приведенной на этом ресурсе без явного письменного разрешения от IBM или первоначального автора запрещены. Если Вы желаете использовать информацию с developerWorks, пожалуйста воспользуйтесь регистрационной формой для того, чтобы связаться с нами запрос на использование материалов developerWorks Россия.
    IBM в России Конфиденциальность Контакты