コード生成をサポートするEclipseプラグイン
概要
Jalcedo RegExpBuilder Pluginを使えばソースコード生成の仕組みを作るのがちょっとは楽になるカモよ。
ソースコード生成の仕組みを作るための基本戦略
RailsのScaffoldは言うに及ばずSeasar2のEclipseプラグインであるDoltengのように、最近はソースコードの自動生成が流行っている。使う側からすれば今まで自分で書かなければいけなかったソースコードを機械が自動で生成してくれるので大助かりだけれども、その自動生成の仕組みを作る部分ってのはやっぱり手作業なんじゃないだろか。ようしらんけど。
実際にDoltengの実装を見た訳じゃないけれども、Javaのソースコード生成はJETというテンプレートエンジンを使ったりしているのだろう。Railsの場合はERBを使っているだろう。
一回、私もコード生成を行うような仕組みを作ったことがある。
そこで気づいたことはソースコード生成を使う方は良いけど、作る方はかなりめんどくさいってことだ。
例えばCodeZineのEclipse JETを使用した簡単なコード生成の実例という記事の中でJavaBeansを生成するための"JavaBeans生成テンプレート"なるものを見ることができるがこんな感じ。
<%@ jet package="jet.excel.sample.template"
class="BeansBindingTemplate"
imports="java.util.* jet.excel.sample.*" %>
<% BindingData data = (BindingData) argument; %>
package <%=data.getPackageName()%>;
public class <%=data.getClassName()%> {
<% for (ColumnBindingData column : data.getColumnBindings()) { %>
private String <%=column.getName()%>;
public String get<%=column.getName().
substring(0,1).toUpperCase()
+ column.getName().substring(1)%>() {
return <%=column.getName()%>;
}
public void set<%=column.getName().
substring(0,1).toUpperCase()
+ column.getName().
substring(1)%>(String <%=column.getName()%>) {
this.<%=column.getName()%> = <%=column.getName()%>;
}
<% }%>
}
ソースコード生成の仕組みを作る際の問題点 その1
わあ、簡単!
って、ちょっと考えてみればそんなこと無いことにすぐ気づく。私はすぐ気づかなかったけれども。
はたして一体、このテンプレートで生成されたJavaBeansは本当にちゃんと動くの?って事。その答えはこのテンプレートから生成されたJavaBeansをコンパイルするか、生成されたソースコードの文法をチェックするまでわからない。
もっと複雑なソースコードを生成する場合、いきなりテンプレートを書き始めるということはまず無いはず。たぶんこんな手順になるはずだ。
- 生成する予定のソースコードに似たプロトタイプのソースコードを書く。このソースコードはちゃんとコンパイルもできるし文法も正しい。
- プロトタイプのソースコードからプロトタイプに特有の削除し、テンプレートを作成する。
- テンプレートを利用してソースコードを生成する。
この手順に従えば、少なくともテンプレートにする前のプロトタイプのソースコードの文法が正しく、コンパイルもできるため、いきなりテンプレートを書くよりも生成されたソースコードが正しい可能性は高い。
プロトタイプのソースコードからテンプレートを作る作業がちょっとめんどくさいし、ミスが入る余地が多いけど、一回作ってしまえば良いんだしこれでいいよね。
ソースコード生成の仕組みを作る際の問題点 その2
ただし、これでうまくいくのは生成するソースコードに機能拡張を思いつくまでの話だ。
生成するソースコードに機能を拡張したくなった場合、どこにその拡張する機能のソースコードを書けば良いんだろう??
ちょっとした機能ならばテンプレートをそのまま修正すれば良いけれども、ちょっと大きい機能追加の場合、問題点その1がすぐに現れてくる。追加したコードが正しいかどうかはコードを生成してみるまでわからないってこと。
ではプロトタイプに機能を追加して、またテンプレートを作り直せばいいじゃん?
はい、はっきりいってかなりメンドクサイです。
プロトタイプからテンプレートを生成する作業は一度やってみればわかるけどミスが入る頻度が多く、それでいて同じ事の繰り返しが多くて、それを機能拡張のたびに行うのはかなりしんどいです。
要はプロトタイプのソースコードからテンプレートを自動で生成できれば良いんですけどね。
プロトタイプのソースコードからテンプレートを自動で生成する
そこで出てくるのが、今回ご紹介するJalcedo RegExpBuilder Pluginなんですね。
このプラグインのできることは、
- あるファイル名にマッチするファイルを、任意のファイルにコピーする。
- ファイルをコピーする際、ファイル内容を正規表現に従って変換する。
ってだけなんですけれども、今回のような、メタコード生成をちょっと楽にすることができます。
例えばさっきの"JavaBeans生成テンプレート"を思い出してもらうと、このテンプレートを生成するためのプロトタイプはこんな感じになるでしょう。
/*=<%@ jet package="jet.excel.sample.template"
class="BeansBindingTemplate"
imports="java.util.* jet.excel.sample.*" %>
<% BindingData data = (BindingData) argument; %>*//*-*/
package /*=<%=data.getPackageName()%>*/jp.fraction.test/*-*/;
public class /*=<%=data.getClassName()%>*/Item/*-*/ {
/*=<% for (ColumnBindingData column : data.getColumnBindings()) { %>*//*-*/
private String /*=<%=column.getName()%>*/name/*-*/;
public String /*=get<%=column.getName().
substring(0,1).toUpperCase()
+ column.getName().substring(1)%>*/getName/*-*/() {
return /*=<%=column.getName()%>*/name/*-*/;
}
public void /*=set<%=column.getName().
substring(0,1).toUpperCase()
+ column.getName().substring(1)%>*/setName/*-*/(String /*=<%=column.getName()%>*/name/*-*/) {
this./*=<%=column.getName()%>*/name/*-*/ = /*=<%=column.getName()%>*/name/*-*/;
}
/*=<% }%>*//*-*/
}
見ての通り、プロトタイプにテンプレートのソースコードをコメントで挿入した形になります。ちょっと見た目はゾッとしませんが少なくともコンパイルすることはできますし、Javaの文法的にも正しいです。
そしてJalcedo RegExp Builderの設定は以下のような形にします。
- 適用するファイル名
- src/jp/fraction/test/Item\.java
- 変換後のファイル名
- template/Item.template
- 変換対象内容
- /\*=(.*?)\*/(.*?)/\*-\*/
- 変換後の内容
- $1
これでsrcというソースコードフォルダーにある、jp.fraction.test.Item.javaというソースコードが変換パターンに従って変換された後、templateフォルダの中にItem.templateという名前で保存されます。
少なくとも一度書いてしまえば、それ以降の機能追加のたびにテンプレートを新たに書き下ろすと言うことがなくなるのでちょっとは楽になるでしょう。