在生成java代码的中经常会遇到Java文件的合并问题,EMF的org.eclipse.emf.codegen插件提供了Java文件合并的工具类,用户只需要配置一定的规则就可以解决java文件的合并问题。
EMF提供了简介的API调用:
public String mergeContent(String sourceContent,String targetContent) { //用户配置的合并规则 String jmergeRuleURI= URI.createPlatformPluginURI( "org.eclipse.emf.codegen.ecore/templates/emf-merge.xml", false).toString(); //$NON-NLS-1$ JControlModel model = new JControlModel(); model.initialize(new ASTFacadeHelper(), jmergeRuleURI); JMerger jMerger = new JMerger(model); jMerger.setSourceCompilationUnit(jMerger.createCompilationUnitForContents(sourceContent)); jMerger.setTargetCompilationUnit(jMerger.createCompilationUnitForContents(targetContent); //执行合并规则 jMerger.merge(); return jMerger.getTargetCompilationUnit().getContents(); }
从上面代码可以看出合并规则是重点,我们以"org.eclipse.emf.codegen.ecore"插件中本身提供的emf-merge.xml来简要说明规则。
首先需要说明的是,我们对Java中的元素用节点(Node)表示,例如Field、Method都是java可编译单元的节点。对于java文件合并,无非三种情况:
1、如果Java Source中存在该节点,而且Java Target中也同样存在该节点,如何合并
2、如果Java Source中存在该节点,但是Java Target中不存在该节点,如何处理
3、如果Java Source中不存在该节点,但是Java Target中存在该节点,如何处理
在合并规则的xml文件中对这三种情况都有相应的配置。
其次在合并规则中有个重要的标记概念(markup),个人理解就是对节点的划分,比如一部分节点是自动生成的,这部分就属于“gen”的标记范围。这样在运用合并规则的时候,就可以对某一个范围进行操作。
下面首先看一下如何定义标记,
<merge:dictionaryPattern
name="generatedUnmodifiableMembers"
select="Member/getComment"
match="@\s*(gen)erated\s*(This field/method[^(?:\n\r?|\r\n?)]*)*(?:\n\r?|\r\n?)"/>
name属性仅仅在没有捕获的内容的时候作为标记名,例如 match="@\s*model" 可以匹配这个正则表达式,但是正则表达式中没有捕获的内容,那么将会用该名字作为标记,select属性中的内容分为两部分,并由‘/’分割。前面的部分表示节点的具体类型,示例中为Member,则表示节点的实际类型为org.eclipse.emf.codegen.merge.java.facade.JMember(可以查看该包下的JNode及子类,把Java可编译单元解析成具体的语法树模型),后面的getComment表示该实际类型的方法名。match属性为正则表达式。这个标记就表示如果JMember类型的comment可以匹配match所表示的正则表达式,那么这个JMember就属性'gen'标记。其中‘gen’就是正则表达式中捕获的字符串。
如果我的java代码如下:
/**
* Returns the value of the '<em><b>Title</b></em>' attribute.
* @generated
*/
String getTitle();
那么该节点会放在‘gen’标记中。
其他定义标签示例:
<merge:dictionaryPattern
name="modelMembers"
select="Member/getComment"
match="@\s*(model)"/>
EMF生成的Java代码的模型标记
如何定义合并规则:
1、如果Java Source中存在该节点,而且Java Target中也同样存在该节点,如何合并
使用pull元素来定义规则:
<merge:pull
sourceMarkup="^gen$"
sourceGet="Member/getComment"
targetMarkup="^gen$"
targetPut="Member/setComment"/>
sourceMarkup表示节点所属的标记,sourceGet也是有两部分组成,一部分表示节点的类型,另一部分表示获得的方法。该合并规则表示,如果target中的JMember类型属于'gen'标签,而且对应的source JMember类型也属于'gen'标签,那么会把source的comment设置到target的comment中。
如果想保留target中的部分java注释的话需要使用sourceTransfer属性,例如sourceTransfer="(\s*<!--\s*begin-user-doc.*?end-user-doc\s*-->\s*)(?:\n\r?|\r\n?)",表示将会保留target中“// begin-user-code”
和“
// end-user-code”
包围住的所有的字符。
2、如果Java Source中存在该节点,但是Java Target中不存在该节点,如何处理
使用push元素定义规则:
<merge:push targetParentMarkup="^gen$" select="Annotation"/>
表示如果Annotation节点的父属于'gen'标记,则会在target中添加该注解。
事实上在默认情况下如果没有声明所属的标记,默认markup为ture的,个人理解这个节点可以实现那些节点可以不用合并到target中,即仅仅在target父类型标记不为'gen'标记,则不会合并到target中。
3、如果Java Source中不存在该节点,但是Java Target中存在该节点,如何处理
使用sweep元素来定义规则:
<merge:sweep markup="^gen$" select="Member"/>
表示如果该节点类型为JMember,而且属于'gen'标记,则会删除该节点。
实际上对于这种情况,如果JMerger提供了三种策略,删除节点,注释节点、重命名节点。默认为删除策略,通过action属性设置
如何根据source排序
如果想根据source中的顺序对target进行排序,需要定义排序规则:
<merge:sort markup="^ordered$" select="Field"/>
如果source中field为f2/f1/f3.而且target中存在相应的Field,那么在合并后的输出Field为source的顺序,即f2/f1/f3。
在合并规则的xml根节点上可以定义不执行合并的Pattern
<merge:options
indent=" "
braceStyle="matching"
redirect="Gen"
block="\s*@\s*generated\s*NOT\s*(?:\n\r?|\r\n?)"
noImport="\s*//\s*import\s+([\w.*]*)\s*;\s*(?:\n\r?|\r\n?)"
xmlns:merge="http://www.eclipse.org/org/eclipse/emf/codegen/jmerge/Options">
block属性就是不执行合并的模式,当用户的comment能匹配这种模式的时候,pull规则不会应用(即失效)。如果Type的comment匹配这种模式,这这个类型就不会执行合并操作。
参考:http://www.blogjava.net/JetGeng/archive/2006/05/01/44261.html
http://www.blogjava.net/JetGeng/archive/2006/05/02/44342.html