博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
[转载]使用 Eclipse Modeling Framework 进行建模,第 3 部分
阅读量:2447 次
发布时间:2019-05-10

本文共 6048 字,大约阅读时间需要 20 分钟。

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

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

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

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

blue_rule.gif
c.gif
c.gif
u_bold.gif

假 设您已经添加了一个新项目,在这个项目中需要为编写的每个类都创建一个 JUnit 测试类,这样必须要对编写的每个方法都进行测试。作为一个认真且高效的(或者比较懒的)程序员来说,您决定要编写一个插件,它接受一个 Java 类作为输入,并生成 JUnit 测试例子的存根(stub)。您热情高涨地创建了 JET 和插件, 现在想允许用户定制所生成的测试类;然而在原有类的接口发生变化时,仍然需要重新生成代码。要实现这种功能,可以使用 JMerge。

从插件中调用 JMerge 的代码非常简单(参见清单 1)。这会创建一个新的 JMerger 实例,以及一个 URI merge.xml,设置要合并的来源和目标,并调用 merger.merge() 。然后合并的内容就可以展开为 merger.getTargetCompilationUnit() 。

// ... 	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:pull 元素中。此处,源类中每个方法的代码都会被替换为目标类的对应方法的代码。如果一个方法在目标类不存在,就会被创建。如果一个方法只在源类中存在,而在目标类不存在,就会被保留。

"http://www.eclipse.org/org/eclipse/emf/codegen/jmerge/Options"> sourceGet="Method/getBody" targetPut="Method/setBody"/>
blue_rule.gif
c.gif
c.gif
u_bold.gif

这种简单的方法有一个非常明显的问题:每次修改源类并重新生成代码时,此前所做的修改就全部丢失了。我们需要增加某种机制来告诉 JMerge 有些方法已经被修改过了,因此这些方法不应该被重写。要实现这种功能,可以使用 元素。 merge:dictionaryPattern 允许您使用正则表达式来区分 Java 元素(参见清单 3)。

name="generatedMember"    select="Member/getComment"    match= "s*@s*(gen)erateds*"/>    targetMarkup= "^gen$"    sourceGet="Method/getBody"    targetPut="Method/setBody"/>

dictionaryPattern 定义了一个正则表达式,它可以匹配注释中包含 " @generated " 的成员。 select 属性列出了要对这个成员的哪些部分与在 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。

name="generatedUnmodifiableMembers"    select="Member/getComment"    match= "s*@s*(unmod)ifiables*"/> sourceMarkup="^unmod$"    sourceGet="Member/getBody"    targetPut="Member/setBody"/>
blue_rule.gif
c.gif
c.gif
u_bold.gif

在使用这种解决方案一段时间之后,您将注意到有些方法在定制的代码中具有一些通用的不可修改的代码(例如跟踪和日志记录代码)。此时我们既不希望禁止生成代码,也不希望全部生成整个方法的代码,而是希望能够让用户定制一部分代码。

要实现这种功能,可以将前面的 pull 目标替用清单 6 来代替。

这样会只重写字符串 " // begin-user-code " 之前和 " // end user-code " 之后的内容,因此就可以在定制代码中保留二者之间的内容。在上面的正则表达式中, "?" 表示在目标代码中,除了要替换的内容之外,其他内容全部保留。您可以实现与 JavaDoc 注释类似的功能,这样就可以拷贝注释,同时为用户定制预留了空间。请参见清单 7。

要支持这种注释,首先要修改开始标签和结束标签,使其遵循 HTML 注释语法,这样它们就不会出现在所生成的 JavaDoc 中;然后修改 sourceGet 和 targetPut 属性,以便使用 "Member/ getComment" 和 "Member/ setComment" 。 JMerge 允许您在细粒度级别上存取 Java 代码的不同部分。(更多内容请参见 )。

blue_rule.gif
c.gif
c.gif
u_bold.gif

到现在为止,我们已经介绍了如何转换方法体,但是 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 的功能)。

blue_rule.gif
c.gif
c.gif
u_bold.gif

在 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
blue_rule.gif
c.gif
c.gif
u_bold.gif

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

表 2. merge:pull 属性

属性 条件
sourceGet 必需的。该值必须是 中列出的一个选项,例如 "Member/getBody"。
targetPut 必需的。该值必须是 中列出的一个选项,例如 "Member/setBody"。
sourceMarkup 可选的。用来在触发 merge:pull 规则之前过滤必须匹配源代码的 dictionaryPatterns 。格式如 "^dictionaryName$",也可以使用 "|" 将多个 dictionaryPatterns 合并在一行中。
targetMarkup 可选的。用来在触发 merge:pull 规则之前过滤必须匹配目标代码的 dictionaryPatterns 。格式如 "^dictionaryName$",也可以使用 "|" 将多个 dictionaryPatterns 合并在一行中。
sourceTransfer 可选的。一个正则表达式,指定要传递给目标代码的源代码的数量。

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/374079/viewspace-130683/,如需转载,请注明出处,否则将追究法律责任。

转载于:http://blog.itpub.net/374079/viewspace-130683/

你可能感兴趣的文章
pvs-stdio ue4_华为云:如今PVS-Studio多云
查看>>
vc编程查找计算机运行记录_如何查找计算机的正常运行时间和安装日期
查看>>
steam无法显示成人内容_如何在Steam上查看仅限成人游戏
查看>>
轻松将图像上传到Photobucket
查看>>
如何在iPhone或iPad上启用USB受限模式(适用于iOS 11.4.1)
查看>>
注意:浏览器崩溃的技术支持弹出窗口又回来了
查看>>
如何在Ubuntu 11.10中安装Classic Gnome桌面
查看>>
亚马逊echo中国使用_如何阻止您的Amazon Echo收听
查看>>
linkedin 分享_如何永远阻止LinkedIn的烦人电子邮件
查看>>
ipad和iphone适配_如何在iPhone和iPad上将链接,照片和媒体快速添加到Apple Notes
查看>>
开源星空照片_如何拍摄星空的好照片
查看>>
usb延长线线序_我应该使用哪种延长线?
查看>>
如何使用Google Assistant控制Xbox One
查看>>
图标缓存 vista_在Windows Vista中将Internet Explorer 7图标添加到桌面
查看>>
iexplore.exe_如何一次杀死所有iexplore.exe进程?
查看>>
discord linux_如何启用和自定义Discord的游戏内叠加
查看>>
ipad 悬浮按钮 自定_如何在iPad上自定义鼠标按钮
查看>>
css 按钮固定在右上交_如何在iPad上交换左右鼠标按钮
查看>>
如何加入“动物杂交:新视野”寻蛋活动
查看>>
cloudlet_使用Search Cloudlet为您的搜索添加种类
查看>>