使用 Eclipse Modeling Framework 进行建模,第 3 部分
2008-08-28 16:50
579 查看
Eclipse Modeling Framework(EMF)中包含了一个开放源代码的工具 JMerge,这个工具可以使代码生成更加灵活,可定制性更好。本文使用一个例子来展示如何将 JMerge 添加到一个应用程序中,并为不同的环境定制 JMerge 的行为。
概述
本系列文章的 前一篇介绍了有关 Eclipse 的 Java Emitter Templates (JET)和代码生成的知识,在那篇文章中,您已经看到如何通过使用模板和代码生成器来节省时间,并实现模式级的代码重用。然而在大部分情况中,这都还不够。您需要能够将所生成的代码插入现有的代码中,或者允许以后的开发人员来定制所生成的代码,而不需要在重新生成代码时重新编写任何内容。理想情况下,代码生成器的创建者希望可以支持今后开发人员所有的需求:从修改方法的实现、修改各种方法签名,到修改所生成类的继承结构。这是一个非常有趣的问题,目前还没有很好的通用解决方案;但是有一个很好的纯 Java 的解决方案,称为 JMerge。
JMerge 是 EMF 中包含的一个开放源代码的工具,可以让您定制所生成的模型和编辑器,而重新生成的代码不会损坏已经修改过的内容。如果描述了如何将新生成的代码合并到现有定制过的代码中,那么
第一步
假设您已经添加了一个新项目,在这个项目中需要为编写的每个类都创建一个 JUnit 测试类,这样必须要对编写的每个方法都进行测试。作为一个认真且高效的(或者比较懒的)程序员来说,您决定要编写一个插件,它接受一个 Java 类作为输入,并生成 JUnit 测试例子的存根(stub)。您热情高涨地创建了 JET 和插件, 现在想允许用户定制所生成的测试类;然而在原有类的接口发生变化时,仍然需要重新生成代码。要实现这种功能,可以使用 JMerge。
从插件中调用 JMerge 的代码非常简单(参见清单 1)。这会创建一个新的
清单 1. 调用 JMerge
要启动这个过程,可以使用清单 2 这个简单的 merge.xml。其中声明了
清单 2. 一个非常简单的 merge.xml
区分生成的方法
这种简单的方法有一个非常明显的问题:每次修改源类并重新生成代码时,此前所做的修改就全部丢失了。我们需要增加某种机制来告诉 JMerge 有些方法已经被修改过了,因此这些方法不应该被重写。要实现这种功能,可以使用
清单 3. 一个简单的 dictionaryPattern
清单 4. 定制代码
您或许会注意到有些元素是不能定制的,任何试图定制这些代码的企图都应该被制止。为了支持这种功能,要定义另外一个
清单 5. 不可修改代码的 merge.xml
细粒度的定制
在使用这种解决方案一段时间之后,您将注意到有些方法在定制的代码中具有一些通用的不可修改的代码(例如跟踪和日志记录代码)。此时我们既不希望禁止生成代码,也不希望全部生成整个方法的代码,而是希望能够让用户定制一部分代码。
要实现这种功能,可以将前面的
清单 6. 细粒度的定制代码
这样会只重写字符串 "
清单 7. 细粒度的 JavaDoc 定制
要支持这种注释,首先要修改开始标签和结束标签,使其遵循 HTML 注释语法,这样它们就不会出现在所生成的 JavaDoc 中;然后修改
下一步
到现在为止,我们已经介绍了如何转换方法体,但是 JMerge 还可以处理域、初始化、异常、返回值、import 语句以及其他 Java 元素。它们也采用类似的基本思想,可能只需稍加修改即可。参考
更复杂的例子请参见 EMF 使用 JMerge 的方法:
附录 A:有效的目标选项
在
表 1. 有效的目标选项
附录 B:merge:pull 属性
表2 给出了
表 2. merge:pull 属性
下载
参考资料
您可以参阅本文在 developerWorks 全球站点上的 英文原文.
阅读 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,从 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。这样可以更快入门,并且有助于搜集每种产品的文档和技术支持。
关于作者
概述
本系列文章的 前一篇介绍了有关 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; } |
<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"/> |
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 |
|
表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 | 可选的。一个正则表达式,指定要传递给目标代码的源代码的数量。 |
|
名字 | 大小 | 下载方法 |
---|---|---|
os-ecemf3/com.ibm.pdc.example.jet_1.0.0.zip | HTTP |
关于下载方法的信息 |
您可以参阅本文在 developerWorks 全球站点上的 英文原文.
阅读 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,从 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 从事于电子商务创新方面的研究,在这里他阅读源代码,为同事提供一些支持。 |
相关文章推荐
- 使用 Eclipse Modeling Framework 进行建模,第 3 部分
- 使用 Eclipse Modeling Framework 进行建模,第 3 部分
- 使用 Eclipse Modeling Framework 进行建模,第 2 部分
- 使用 Eclipse Modeling Framework 进行建模,第 2 部分
- 使用 Eclipse Modeling Framework 进行建模,第 2 部分
- 关于Eclipse Modeling Framework进行建模,第二部分
- [轉]使用 Eclipse Modeling Framework 進行建模,第 2 部分
- 使用 OpenSSL API 进行安全编程,第 3 部分: 提供安全服务 OpenSSL 提供必要的能力
- 在 Eclipse Workbench 之外使用 Eclipse GUI,第 3 部分: 添加操作、菜单和工具栏
- Eclipse 中的 JFace 数据绑定,第 3 部分: 使用高级功能(2/19)
- Eclipse 中的 JFace 数据绑定,第 3 部分: 使用高级功能(9/19)
- Eclipse 中的 JFace 数据绑定,第 3 部分: 使用高级功能(3/19)
- Eclipse 中的 JFace 数据绑定,第 3 部分: 使用高级功能(14/19)
- 使用 Google Web Toolkit、Apache Derby 和 Eclipse 构建 Ajax 应用程序,第 3 部分: 通信
- 使用 OpenSSL API 进行安全编程,第 3 部分: 提供安全服务
- 使用 OpenSSL API 进行安全编程,第 3 部分: 提供安全服务
- Eclipse 中的 JFace 数据绑定,第 3 部分: 使用高级功能(4/19)
- Eclipse 中的 JFace 数据绑定,第 3 部分: 使用高级功能(15/19)
- 关于Eclipse中的开源框架EMF(Eclipse Modeling Framework),第三部分
- Spark 实战,第 3 部分: 使用 Spark SQL 对结构化数据进行统计分析