关于Eclipse中的开源框架EMF(Eclipse Modeling Framework),第三部分
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
1 // ... 2 JMerger merger = getJMerger(); 3 4 // set source 5 merger.setSourceCompilationUnit( 6 merger.createCompilationUnitForContents(generated)); 7 8 // set target 9 merger.setTargetCompilationUnit( 10 merger.createCompilationUnitForInputStream( 11 new FileInputStream(target.getLocation().toFile()))); 12 13 // merge source and target 14 merger.merge(); 15 // extract merged contents 16 InputStream mergedContents = new ByteArrayInputStream( 17 merger.getTargetCompilationUnit().getContents().getBytes()); 18 19 // overwrite the target with the merged contents 20 target.setContents(mergedContents, true, false, monitor); 21 // ... 22 // ... 23 private JMerger getJMerger() { 24 // build URI for merge document 25 String uri = 26 Platform.getPlugin(PLUGIN_ID).getDescriptor().getInstallURL().toString(); 27 uri += "templates/merge.xml"; 28 29 JMerger jmerger = new JMerger(); 30 JControlModel controlModel = new JControlModel( uri ); 31 jmerger.setControlModel( controlModel ); 32 return jmerger; 33 }
要启动这个过程,可以使用清单 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:dictionaryPatternname="generatedMember" select="Member/getComment" match="s*@s*(gen)erateds* "/> <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:dictionaryPatternname="generatedUnmodifiableMembers" select="Member/getComment" match="s*@s*(unmod)ifiables* "/> <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-codes*) "targetPut="Method/setBody"/>
这样会只重写字符串 " // begin-user-code " 之前和 " // end user-code " 之后的内容,因此就可以在定制代码中保留二者之间的内容。在上面的正则表达式中, "?" 表示在目标代码中,除了要替换的内容之外,其他内容全部保留。您可以实现与 JavaDoc 注释类似的功能,这样就可以拷贝注释,同时为用户定制预留了空间。请参见清单 7。
清单 7. 细粒度的 JavaDoc 定制
<!-- copy comments except between the begin-user-docand end-user-doc tags --> <merge:pull sourceMarkup="^gen$"sourceGet="Member/getComment"sourceTransfer="(s*<!--s*begin-user-doc.*?end-user-docs*-->s*) "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 | 可选的。一个正则表达式,指定要传递给目标代码的源代码的数量。 |
关于Eclipse中的开源框架EMF(Eclipse Modeling Framework),第三部分相关推荐
- eclipse中SSH三大框架环境搭建二
通过上一篇博客我们可以轻松搭建strtus2的环境,接下来由我来继续介绍spring的环境搭建以及spring注入的简单使用 相关链接:eclipse中SSH三大k框架环境搭建<一> ec ...
- eclipse中配置c++开发环境 Eclipse + CDT + MinGW
转自eclipse中配置c++开发环境 Eclipse + CDT + MinGW 基本框架:Eclipse + CDT + MinGW 背景知识: CDT:CDT 是完全用 Java 实现的开放源码 ...
- java 详解 搭建 框架_在Eclipse中搭建Struts框架过程详解
虽然用MyEclipse搭建Struts框架是更为便捷的方式,但是用Eclipse可以增强自己对Struts的理解.本文演示了使用Eclipse搭建Struts 1.2框架的过程.此项目实现了简单的功 ...
- 边缘计算开源框架EdgeXFoundry的部署应用开发(三)设备服务开发
边缘计算开源框架EdgeXFoundry的部署应用开发(三)设备服务开发 使用SDK开发真实设备接入服务 着手编写一个温湿度设备接入 准备相关文件及目录 脚本可选,用于单文件编译测试 编写温湿度设备接 ...
- eclipse中搭建ssm框架
工具:jdk1.7+eclipse+tomcat+mysql. 这里用的版本是spring3,框架中用到的实体类和xml映射文件都可以用工具生成的.接下来会将源码贴出,方便初学者快速搭建. 一.新建一 ...
- 如何将Eclipse中的开源项目使用到Android Studio中
近几日,笔者用到了一些开源项目,比如著名的PTR项目.但是在使用的过程中,遇到了一些问题. 这个开源库是在Eclipse上面写的,我们现在开发用的是Android stuido. 两种软件的项目结构是 ...
- 在eclipse中搭建struts2框架的详细过程
今天想学习struts2框架,所以搭建了一个struts框架,记录一下搭建struts2的详细过程. 一.搭建环境: Eclipse:Mars.1 Release(4.5.1) JDK: jdk1 ...
- eclipse中查看android源代码,Android Eclipse中查看 Android框架源码
有时候用Eclipse想按住ctrl键查看源码怎么办? 下面具体步骤让你轻松看源码: project->properties->java build path->libraries ...
- 在Eclipse中引用Jquery框架路径问题
如果引入Jquery的js文件后,还是不能使用Jquery框架的功能的话.出现这个问题原因有:①版本不支持(解决方法就是安装更高的版本或者安装插件).②下载的jS文件是错误的或者不完整(所以建议到官网 ...
最新文章
- uniapp设置模板路径页面样式混乱解决办法
- 折腾了一晚上Windows网络却未果
- python学习框架图-Python学习---Python的框架基础学习
- 《R语言编程艺术》——2.5 使用all()和any()
- 手机内存解读以及android刷机原理
- 百度AI原生云实践: 基于容器云打造 AI 开发基础设施
- C#中的泛型和泛型集合
- vb mysql 表格显示,在VB中编辑数据库和电子表格
- 将excel转为python的字典_python读取excel数据转换成字典
- Spring实战(六)自动装配的歧义性
- Android 系统(125)---Android通过Dialer实现暗码启动
- src2pkg:slackware下超强打包程式 [转]
- jenkins linux虚拟机,Jenkins实战演练之Linux节点任务配置
- 菜鸟和老鸟都无法避免的问题——未将对象引用设置到对象的实例
- 菜鸟学JAVA之——多线程
- css实现小气泡样式和模拟京东快递单号放大效果
- NLP - Gensim
- python之无限浏览网页
- PCB布线技巧(全)
- 宏观经济调控政策笔记+
热门文章
- linux 日志按大小切割_日志切割工具logrotate,帮你管理你的日志文件
- mysql 主备及时_MySQL高可用(二)主备延时如何解决?
- Eclipse SQLExplorer插件的安装和使用
- Codeforces 1028C(面积并/思维)
- HlpViewer.exe 单独打开
- 【备忘录】创建数字数组
- C语言逗号运算符和逗号表达式
- 在Silverlight+WCF中应用以角色为基础的安全模式(一)基础篇之角色为基础的安全模式简介...
- 常量与格式化输出练习
- 一个没有收到任何Offer的应届生回想