自動生成したコードに手を加えたい場合、Generation Gapパターンを使うことが多いと思います。
しかし、Eclipse Modeling Framework(EMF)のJmergeを使えば、自動生成したコードを直接修正してもうまいことマージしてくれます。
例えば、以下のようにjavadocコメントの@generatedがあるメソッドは自動生成で上書きされます。逆に@generatedを消したり、noneをつけるとそのメソッドの内容が上書きされず、修正した内容がそのまま残ります。これはメソッドだけでなく、フィールドでも同じ動作をします。
(8年くらい前にこれを見たときは衝撃的でした)
/**
* method1.<br/>
* @generated
*/
public void method1() {
System.out.println("a");
}
/**
* method2.<br/>
* @generated
*/
public void method2() {
System.out.println("a");
}
/**
* method1.<br/>
* @generated
*/
public void method1() {
System.out.println("あ");
}
/**
* method2.<br/>
*/
public void method2() {
System.out.println("あ");
}
/**
* method1.<br/>
* @generated
*/
public void method1() {
System.out.println("a");
}
/**
* method2.<br/>
*/
public void method2() {
System.out.println("あ");
}
この機能を扱うクラスはEMFにあるので、eclipseにEMFを入れておく必要があります。
Eclipseのupdate site で、例えば、Kepler - http://download.eclipse.org/releases/kepler
を選択して、Modeling → 「EMF - Eclipse Modeling Framework SDK」をチェックしてインストールします。
merge ruleを記述
どのようにマージするかのルールはxmlで記述します。
これを始めから作成するのは大変なので、
eclipseディレクトリの./plugins/org.eclipse.emf.codegen.ecore_d.d.d.vyyyyMMdd-dddd.jarの./templates/emf-merge.xmlを使います。以下がデフォルトです。
<?xml version="1.0" encoding="UTF-8"?>
<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">
<!-- Tabbed Standard
indent="	"
braceStyle="standard"
-->
<!-- this accepts both new and old style markup. -->
<merge:dictionaryPattern
name="modelMembers"
select="Member/getComment"
match="@\s*(model)"/>
<merge:dictionaryPattern
name="generatedUnmodifiableMembers"
select="Member/getComment"
match="@\s*(gen)erated\s*(This field/method[^(?:\n\r?|\r\n?)]*)*(?:\n\r?|\r\n?)"/>
<merge:dictionaryPattern
name="generatedModifiableMembers"
select="Member/getComment"
match="@\s*generated\s*(modifiable)\s*(?:\n\r?|\r\n?)"/>
<!-- This is like the above, but for backward compatibility -->
<merge:dictionaryPattern
name="generatedLastGenMembers"
select="Member/getComment"
match="@\s*(lastgen).*(?:\n\r?|\r\n?)"/>
<merge:dictionaryPattern
name="orderedMembers"
select="Member/getComment"
match="@\s*(ordered)\s*(?:\n\r?|\r\n?)"/>
<!-- Only push Annotations for the Members marked by gen-->
<merge:push targetParentMarkup="^gen$" select="Annotation"/>
<merge:pull
sourceMarkup="^modifiable$"
sourceGet="Member/getComment"
sourceTransfer="(\s*<!--\s*begin-user-doc.*?end-user-doc\s*-->\s*)(?:\n\r?|\r\n?)"
targetMarkup="^modifiable$"
targetPut="Member/setComment"/>
<merge:pull
sourceMarkup="^gen$"
sourceGet="Member/getComment"
sourceTransfer="(\s*<!--\s*begin-user-doc.*?end-user-doc\s*-->\s*)(?:\n\r?|\r\n?)"
targetMarkup="^gen$"
targetPut="Member/setComment"/>
<merge:pull
sourceGet="Member/getFlags"
targetMarkup="^gen$"
equals="Member/getName"
targetPut="Member/setFlags"/>
<merge:pull
sourceMarkup="^gen$"
sourceGet="AbstractType/getComment"
sourceTransfer="(\s*<!--\s*begin-user-doc.*?end-user-doc\s*-->\s*)(?:\n\r?|\r\n?)"
targetMarkup="^modifiable$"
targetPut="AbstractType/setComment"/>
<merge:pull
sourceGet="Type/getTypeParameters"
targetMarkup="^lastgen$|^gen$|^modifiable$|^model$"
targetPut="Type/setTypeParameters"/>
<merge:pull
sourceGet="Type/getSuperclass"
targetMarkup="^lastgen$|^gen$|^modifiable$|^model$"
targetPut="Type/setSuperclass"/>
<merge:pull
sourceGet="Type/getSuperInterfaces"
sourceTransfer="(\s*@\s*extends|\s*@\s*implements)(.*?)(?:<!--|(?:\n\r?|\r\n?))"
targetMarkup="^lastgen$|^gen$|^modifiable$|^model$"
targetPut="Type/addSuperInterface"/>
<merge:pull
sourceGet="Enum/getSuperInterfaces"
sourceTransfer="(\s*@\s*extends|\s*@\s*implements)(.*?)(?:<!--|(?:\n\r?|\r\n?))"
targetMarkup="^lastgen$|^gen$|^modifiable$|^model$"
targetPut="Enum/addSuperInterface"/>
<merge:pull
sourceGet="EnumConstant/getArguments"
targetMarkup="^lastgen$|^gen$|^modifiable$|^model$"
targetPut="EnumConstant/setArguments"/>
<merge:pull
sourceGet="EnumConstant/getBody"
targetMarkup="^lastgen$|^gen$|^modifiable$|^model$$"
targetPut="EnumConstant/setBody"/>
<merge:pull
sourceGet="AnnotationTypeMember/getType"
targetMarkup="^gen$"
targetPut="AnnotationTypeMember/setType"/>
<merge:pull
sourceGet="AnnotationTypeMember/getDefaultValue"
targetMarkup="^gen$"
targetPut="AnnotationTypeMember/setDefaultValue"/>
<merge:pull
sourceGet="Initializer/getBody"
targetMarkup="^gen$"
targetPut="Initializer/setBody"/>
<merge:pull
sourceGet="Field/getType"
targetMarkup="^gen$"
targetPut="Field/setType"/>
<merge:pull
sourceGet="Field/getInitializer"
targetMarkup="^gen$"
targetPut="Field/setInitializer"/>
<merge:pull
sourceGet="Method/getTypeParameters"
targetMarkup="^gen$"
targetPut="Method/setTypeParameters"/>
<merge:pull
sourceGet="Method/getReturnType"
targetMarkup="^gen$|^model$"
targetPut="Method/setReturnType"/>
<merge:pull
sourceGet="Method/getParameters"
targetMarkup="^gen$"
targetPut="Method/setParameters"/>
<merge:pull
sourceGet="Method/getBody"
targetMarkup="^gen$"
targetPut="Method/setBody"/>
<merge:pull
sourceGet="Method/getExceptions"
targetMarkup="^gen$"
targetPut="Method/addException"/>
<merge:sweep markup="^gen$" select="Member"/>
<merge:sweep markup="^org.eclipse.emf.ecore.EMetaObject$" select="Import"/>
<merge:sweep markup="^org.eclipse.emf.ecore.impl.EMetaObjectImpl$" select="Import"/>
<merge:sweep markup="^org.eclipse.emf.ecore.util.EObjectCompositeEList$" select="Import"/>
<merge:sweep markup="^org.eclipse.emf.ecore.util.EObjectCompositeWithInverseEList$" select="Import"/>
<merge:sweep markup="^org.eclipse.emf.common.util.AbstractEnumerator$" select="Import"/>
<merge:sort markup="^ordered$" select="Field"/>
<merge:sort markup="^ordered$" select="EnumConstant"/>
<!-- Basic Rules
<merge:pull
sourceGet="CompilationUnit/getHeader"
targetPut="CompilationUnit/setHeader"/>
<merge:pull
sourceGet="Package/getName"
targetPut="Package/setName"/>
<merge:pull
sourceGet="Member/getFlags"
targetPut="Member/setFlags"/>
<merge:pull
sourceGet="Member/getComment"
targetPut="Member/setComment"/>
<merge:pull
sourceGet="Type/getSuperclass"
targetPut="Type/setSuperclass"/>
<merge:pull
sourceGet="Type/getSuperInterfaces"
targetPut="Type/addSuperInterface"/>
<merge:pull
sourceGet="Initializer/getBody"
targetPut="Initializer/setBody"/>
<merge:pull
sourceGet="Field/getType"
targetPut="Field/setType"/>
<merge:pull
sourceGet="Field/getInitializer"
targetPut="Field/setInitializer"/>
<merge:pull
sourceGet="Method/getBody"
targetPut="Method/setBody"/>
<merge:pull
sourceGet="Method/getReturnType"
targetPut="Method/setReturnType"/>
<merge:pull
sourceGet="Method/getExceptions"
targetPut="Method/addException"/>
-->
<!-- Push Enum Constants only for generated Enums -->
<!--
<merge:push targetParentMarkup="^gen$" select="EnumConstant"/>
-->
<!-- Push Annotation Type Members only for generated Annotation Types -->
<!--
<merge:push targetParentMarkup="^gen$" select="AnnotationTypeMember"/>
-->
<!-- Sets the content of annotations if the parent is marked with gen -->
<!--
<merge:pull
sourceGet="Annotation/getContents"
targetParentMarkup="^gen$"
targetPut="Annotation/setContents"/>
-->
<!-- Remove annotations of generated target nodes if the annotation is not in the source -->
<!--
<merge:sweep parentMarkup="^gen$" select="Annotation"/>
-->
<!--
<merge:pull
sourceMarkup="^modifiable$"
sourceGet="Member/getComment"
targetMarkup="^gen$"
targetPut="Member/setComment"/>
-->
</merge:options>
マージするクラスを作成する
String merge(String source, String target);を使えば、マージされた結果を返してくれます。
public class EclipseJavaMerger {
private final JMerger merger = new JMerger(buildMergeRule());
public String merge(String source, String target) {
try {
if (target == null) {
return source;
}
merger.setSourceCompilationUnit(merger.createCompilationUnitForContents(source));
merger.setTargetCompilationUnit(merger.createCompilationUnitForContents(target));
merger.merge();
return merger.getTargetCompilationUnitContents();
} finally {
merger.reset();
}
}
private JControlModel buildMergeRule() {
JControlModel controlModel = new JControlModel();
String configFileUri = getClass().getResource("/emf-merge.xml").toString();
controlModel.initialize(new ASTFacadeHelper(), configFileUri);
return controlModel;
}
}
実際に動作確認をしたい
この動作を確認したい場合は、Eclipse Marketで、Comment Tag Genを検索してください。(試しに作ってみました)
スプレッドシートからenumクラスなどを自動生成するpluginになります。
以下のプロジェクトで動作させてください。ここにあるスプレッドシートを使えば、自動生成されます。
https://github.com/ko2ic/comment-tag-gen-plugin-sample