分享
 
 
 

使用 Eclipse Modeling Framework 进行建模,第 3 部分

王朝java/jsp·作者佚名  2006-01-09
窄屏简体版  字體: |||超大  

使用 Eclipse Modeling Framework 进行建模,第 3 部分

英文原文

内容:

概述

第一步

区分生成的方法

细粒度的定制

下一步

附录 A:有效的目标选项

附录 B:merge:pull 属性

参考资料

关于作者

对本文的评价

相关内容:

用 Eclipse Modeling Framework 实现模型驱动开发,第 1 部分

用 Eclipse Modeling Framework 实现模型驱动开发,第 2 部分

用 Runtime Spy 调整 Eclipse 的启动性能,第 1 部分

驾驭 Eclipse 功能部件

developerWorks 上所有 Eclipse 文章

使用 Eclipse 的 JMerge 定制生成的代码和编辑器

级别: 中级

Adrian Powell

资深软件开发人员, IBM

2004 年 6 月

Eclipse Modeling Framework(EMF)中包含了一个开放源代码的工具 JMerge,这个工具可以使代码生成更加灵活,可定制性更好。本文使用一个例子来展示如何将 JMerge 添加到一个应用程序中,并为不同的环境定制 JMerge 的行为。

概述

本系列文章的 前一篇 介绍了有关 Eclipse 的 Java Emitter Templates (JET)和代码生成的知识,在那篇文章中,您已经看到如何通过使用模板和代码生成器来节省时间,并实现模式级的代码重用。然而在大部分情况中,这都还不够。您需要能够将所生成的代码插入现有的代码中,或者允许以后的开发人员来定制所生成的代码,而不需要在重新生成代码时重新编写任何内容。理想情况下,代码生成器的创建者希望可以支持今后开发人员所有的需求:从修改方法的实现、修改各种方法签名,到修改所生成类的继承结构。这是一个非常有趣的问题,目前还没有很好的通用解决方案;但是有一个很好的纯 Java 的解决方案,称为 JMerge。

JMerge 是 EMF 中包含的一个开放源代码的工具,可以让您定制所生成的模型和编辑器,而重新生成的代码不会损坏已经修改过的内容。如果描述了如何将新生成的代码合并到现有定制过的代码中,那么 JETEmitter 就可以支持 JMerge。本文通过一个例子来展示其中的一些可用选项。

第一步

假设您已经添加了一个新项目,在这个项目中需要为编写的每个类都创建一个 JUnit 测试类,这样必须要对编写的每个方法都进行测试。作为一个认真且高效的(或者比较懒的)程序员来说,您决定要编写一个插件,它接受一个 Java 类作为输入,并生成 JUnit 测试例子的存根(stub)。您热情高涨地创建了 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;

}

要启动这个过程,可以使用清单 2 这个简单的 merge.xml。其中声明了 <merge> 标签,以及缺省的命名空间声明。这段代码最主要的部分在 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> 元素。 merge:dictionaryPattern 允许您使用正则表达式来区分 Java 元素(参见清单 3)。

清单 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 定义了一个正则表达式,它可以匹配注释中包含 "@generated" 的成员。 select 属性列出了要对这个成员的哪些部分与在 match 属性中给出的正则表达式进行比较。dictionaryPattern 是由字符串 gen 定义的,它就是 match 属性值中圆括号中的内容。

merge:pull 元素多了一个附加属性 targetMarkup。这个属性可以匹配 dictionaryPattern,它必须在应用合并规则之前对目标代码进行匹配。此处,我们正在检查的是目标代码,而不是源代码,因此用户可以定制这些代码。当用户删除注释中的 "@generated" 标签时, dictionaryPattern 就不会与目标代码匹配,因此就不会合并这个方法体。请参见清单 4。

清单 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。

清单 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。

清单 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 还可以处理域、初始化、异常、返回值、import 语句以及其他 Java 元素。它们也采用类似的基本思想,可能只需稍加修改即可。参考 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" 以及它们的 setter 方法,但是还有很多其他可用的选项。JMerge 支持 org.eclipse.jdt.core.jdom.IDOM* 中定义的任何类的匹配和取代。所有可用的选项如表 1 所示。

表 1. 有效的目标选项

类型

方法

注释

CompilationUnit

getHeader/setHeader

getName/setName

Field

getInitializer/setInitializer

不包含 "="

getName/setName

变量名

getName/setName

类名

Import

getName/setName

要么是一个完全限定的类型名,要么是一个随需应变的包

Initializer

getName/setName

getBody/setBody

Member

getComment/setComment

getFlags/setFlags

例如: abstract, final, native 等。

Method

addException

addParameter

getBody/setBody

getName/setName

getParameterNames/setParameterNames

getParameterTypes/setParameterTypes

getReturnType/setReturnType

Package

getName/setName

Type

addSuperInterface

getName/setName

getSuperclass/setSuperclass

getSuperInterfaces/setSuperInterfaces

附录 B:merge:pull 属性

表2 给出了 merge:pull 元素的属性。

表 2. merge:pull 属性

属性

条件

sourceGet

必需的。该值必须是 附录 A 中列出的一个选项,例如 "Member/getBody"。

targetPut

必需的。该值必须是 附录 A 中列出的一个选项,例如 "Member/setBody"。

sourceMarkup

可选的。用来在触发 merge:pull 规则之前过滤必须匹配源代码的 dictionaryPatterns 。格式如 "^dictionaryName$",也可以使用 "|" 将多个 dictionaryPatterns 合并在一行中。

targetMarkup

可选的。用来在触发 merge:pull 规则之前过滤必须匹配目标代码的 dictionaryPatterns。格式如 "^dictionaryName$",也可以使用 "|" 将多个 dictionaryPatterns 合并在一行中。

sourceTransfer

可选的。一个正则表达式,指定要传递给目标代码的源代码的数量。

参考资料

阅读 developerWorks 上 Eclipse Modeling Framework 系列的其他文章: 第 1 部分 创建和使用 EMF 模型, 和 第 2 部分 使用 Java Emitter Templates(JET)生成代码。

下载本文中使用的 JUnit 插件源代码

下载本文中使用的最终 merge.xml 文件。

EMF 项目主页 上提供了所有的文档、源代码和最新的已编译程序。

IBM 红皮书 Eclipse Development using the Graphical Editing Framework and the Eclipse Modeling Framework 中有更详细的例子,其中介绍了更多定制特性。

如果有问题,可以访问 EMF 新闻组。如果您之前没有使用过 Eclipse 新闻组,请阅读 规则以及如何申请密码

在 developerWorks 的 开放源代码项目专区 中有更多为 为 Eclipse 用户提供的文章。 请参阅 alphaWorks 中最新的 Eclipse 技术下载区

在 Developer Bookstore 的开放源代码区,可以找到数百本 有关开放源代码主题的书籍,其中包括几本 有关 Eclipse 的书籍

订阅 developerWorks,使用其中介绍的最新 IBM 工具和中间件开发并测试应用程序:您可以获得 IBM 的各种软件,从基于 Eclipse 的 WebSphere、 DB2、 Lotus、 Rational 和 Tivoli,以及使用这些软件的一个为期 12 个月的许可证,这些内容只需要极少的钱就可以获得。

订阅 developerWorks,从 developerWorks 的 Speed-start your Linux app 部分下载在 Linux 上运行的产品的免费试用版,包括 WebSphere Studio Site Developer、WebSphere SDK for Web services、WebSphere Application Server、DB2 Universal Database Personal Developers Edition、Tivoli Access Manager 和 Lotus Domino Server。这样可以更快入门,并且有助于搜集每种产品的文档和技术支持。

关于作者

Adrian Powell 从刚开始加入 VisualAge for Java Enterprise Tooling 小组开始使用 IBM 的 Java 开发工具,在这里他花费了两年的时间来手工编写一个代码生成器。从那以后,他一直从事 Eclipse 和 VisualAge for Java 中的工具和插件的开发,他现在几乎为 Eclipse 和 VisualAge for Java 的每一个版本都开发了这种工具和插件。Adrian 目前在 IBM 的 Vancouver Centre 从事于电子商务创新方面的研究,在这里他阅读源代码,为同事提供一些支持。

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有