数ヶ月前、私が初めてGroovyによるAntスクリプトについて書いた時、私はGroovyでのBuilderの概念について触れました。その記事の中では、AntBuilderと呼ばれるGroovyクラスを使って表現力豊かなAntビルド・ファイルを作ることが、いかに容易かを示しました。この記事では、GroovyBuilderの世界をさらに深く探り、こうした強力なツールで他にどんなことができるかを説明します。
GroovyのBuilderを使うと、マークアップ言語、例えばXMLやHTML、Antタスク、さらにはSwingのようにフレームワークを持つGUIまで、を何の苦もなく真似ることができます。ビルダーを使うと、XMLのような高度なマークアップを、XML自体を扱うことなく、素早く作ることができるのです。
Builderの使い方は、実は非常に単純なのです。Builderのインスタンスに付加されたメソッドは、そのマークアップ(例えばHTMLでの<body>タグ)の要素を表します。メソッドに付加されたクロージャー(closure)内で作られたオブジェクトは、子ノード(例えば、<body>タグの内側にあるタグ<p>)を表します。
これが実際にどうなっているかを示すために、リスト1に示す構造を持つXML文書をプログラム的に表現する単純なBuilderを作ります。
リスト1. 単純なXML構造
<person>
<name first="Megan" last="Smith">
<age>32</age>
<gender>female</gender>
</name>
<friends>
<friend>Julie</friend>
<friend>Joe</friend>
<friend>Hannah</friend>
</friends>
</person>
|
この構造を表現するのは、ごく単純です。まず、personメソッドをBuilderインスタンスに付加すると、これがXMLのルート・ノード(<person>)を表すことになります。子ノードを作るためにクロージャーを作り、mapという形式のパラメーターを持つ、nameという名の新しいオブジェクトを宣言します。ついでに言うと、こうしたパラメーターは、要素に付加される属性の基礎となります。
次にnameオブジェクト内で、2つの追加オブジェクトを持つクロージャーを付加します。この追加オブジェクトはageとgenderであり、<name>の、同じような子要素に対応します。だんだん分かってきましたか。ごく簡単なことなのです。
<friends>要素は<person>の兄弟分なので、私はクロージャーから飛び出し、friendsオブジェクトを宣言し、そしてもちろん、friend要素の集合を含むクロージャーを付加します。これをリスト2に示します。
リスト2. Builderはこんなに単純です
import groovy.xml.*
import java.io.*
class XMLBuilder{
static void main(args) {
writer = new StringWriter() builder = new MarkupBuilder(writer)
friendnames = [ "Julie", "Joey", "Hannah"]
builder.person() {
name(first:"Megan", last:"Smith") {
age("33")
gender("female")
}
friends() {
for (e in friendnames) { friend(e) }
}
}
println writer.toString()
}
}
|
ご覧の通り、Groovy表現は非常に優雅であり、対応するマークアップ表現に、容易に割り当てることができます。もちろんGroovyはこの下で、面倒なマークアップ要素(<や >)を処理しています。ですから私は、構造の詳細に対して注意を払うことなく、内容に集中することができるのです。
Builderを使うと、HTMLの構築も容易になります。これはGroovletを開発する時に便利です。極端に単純な場合として、リスト3のような単純なHTMLページを作る場合を考えてみましょう。
リスト3. 基本的なHTML
<html>
<head>
<title>Groov'n with Builders</title>
</head>
<body>
<p>Welcome to Builders 101. As you can see this Groovlet is fairly simple.</p>
</body>
</html>
|
これをGroovyでコード化するのは簡単です。これをリスト4に示します。
リスト4. Groovyでの基本的なHTML
import groovy.xml.*
import java.io.*
class HTMLBuilderExample{
static void main(args) {
writer = new StringWriter() builder = new MarkupBuilder(writer)
builder.html(){
head(){
title("Groov'n with Builders"){}
}
body(){
p("Welcome to Builders 101. As you can see " +
"this Groovlet is fairly simple.") }
}
println writer.toString()
}
|
もう少し面白くするために、Builderを使うと完全機能のGUI が簡単に作れることを示しましょう。先ほど、GroovyのSwingBuilderを使うと非常に簡単にGUIが作れることに触れました。SwingBuilderの実際を、リスト5に示します。
リスト5. GroovyによるGUI Builderは素敵だ
import java.awt.FlowLayout
import javax.swing.*
import groovy.swing.SwingBuilder
class SwingExample{
static void main(args) {
swinger = new SwingBuilder()
langs = ["Groovy", "Ruby", "Python", "Pnuts"]
gui = swinger.frame(title:'Swinging with Groovy!', size:[290,100]) {
panel(layout:new FlowLayout()) {
panel(layout:new FlowLayout()) {
for (lang in langs) {
checkBox(text:lang)
}
}
button(text:'Groovy Button', actionPerformed:{ swinger.optionPane(message:'
Indubitably Groovy!').createDialog(null, 'Zen Message').show()
})
button(text:'Groovy Quit', actionPerformed:{ System.exit(0)})
}
}
gui.show()
}
}
|
この結果を図1に示します。悪くありませんよね。
図1. GroovyでのGUIプログラミングの成果
こうした例は、取るに足らないものですが、面白いものと言えるでしょう。GroovyのBuilderを使うことによって、下にある特定なマークアップ言語、例えばXMLなどを避けられることが、これまでの例でよく分かったと思います。当然のことですが、XMLやHTMLを避けることは、時には望ましいものであり、マークアップ機能は、Java™プラットフォームにとって決して新しいものではありません。例えば、XML機能を持った、私の好きなフレームワークの一つはJiBXです。
JiBXを使うと、XML構造をオブジェクト・モデルに、あるいはその逆に、容易に割り当てることができます。バインディングは強力なものですが、バインディングを行う似たようなツールは、JAXBやCastor、そしてZeusなど、山ほどあります。
バインディング・フレームワークの唯一の問題は、少し時間がかかる可能性がある、という点です。幸いGroovyのBuilderは単純なソリューションとして使うことができ、場合によると、それだけでも非常に効果的です。
ちょっとここで、英語の単語辞書を表現する単純なデータベースがある、と想像してみてください。そうすると、word(単語)に対するテーブルや、そのdefinition(定義)に対する別のテーブル、そして最後にsynonym(同義語)に対するテーブルがあることになります。図2は、このデータベースを簡単に表現したものです。
図2. 辞書のデータベース
見て分かると思いますが、このデータベースは極めて単純です。wordは、definitionとsynonymとの間に、1対多のリレーションシップを持っています。
この辞書データベースには、このデータベース内容で鍵となる面を表現したXML構造を取り入れようと探っている利用者がいます。検索の対象となるXML構造をリスト6に示します。
リスト6. 取り入れ可能な辞書XML
<words>
<word spelling="glib" partofspeech="adjective">
<defintions>
<defintion>Performed with a natural, offhand ease.</defintion>
<defintion>Marked by ease and fluency of speech or writing
that often suggests or stems from insincerity, superficiality, or deceitfulness</defintion>
</defintions>
<synonyms>
<synonym spelling="artful"/> <synonym spelling="urbane"/> </synonyms>
</word>
</words>
|
この問題に対して、JiBXのようなバインディング・フレームワークを使って対応しようとすると、リレーショナル・モデルから目的とする最終的なXMLモデルを作るまでの間に、恐らく何か中間的なオブジェクト・モデルを作る必要が出てきます。その後でデータベースの内容をオブジェクト・モデルの中に読み込み、オブジェクト・モデルの内部構造をXMLフォーマットに整合するように、下にあるフレームワークに要求する必要があります。
このプロセスでは、(求めるフレームワークの手順を使って)オブジェクト構造をXMLフォーマットに割り当てるという、暗黙的なステップも行われます。JAXBのような一部のフレームワークでは、XMLや、例えばJiBXのような他のフレームワークから、実際にJavaオブジェクトを生成してくれるため、自分のJavaオブジェクトをXMLフォーマットにカスタム割り付けすることができます。ただし、いずれも大仕事です。
確かにそれは、高貴な努力でもあります。私は、バインディング・フレームワークを避けるように訴えているわけではありません。私は一応、それを言っておきたかったのです。皆さんは事前警告を受けました。これから皆さんにお見せするのは、もっと早く、そのXMLを生成する方法です。
GroovyのMarkupBuilderと、皆さんお気に入りの新しいデータベース・アクセス・フレームワーク、GroovySqlとを組み合わせると、手軽に使えるXMLを容易に生成することができます。必要なことは、必要なクエリーを見つけ、その結果をBuilderインスタンスに割り当てることだけです。そうすると、辞書データベースの内容を表すXML文書が、一瞬でできあがります。
これをステップ毎に見て行きましょう。まず、Builderのインスタンスを作ります。この場合はXMLを生成しようとしているので、MarkupBuilderです。一番外側のXML要素(つまり、ルート)はwordなので、wordメソッドを作ります。クロージャーの中で最初のクエリーを呼び、イテレーター(iterator)の中にある、そのクエリーの結果を、子ノードであるwordに割り当てます。
次に、wordの2つの子ノードを、2つの新しいクエリーを使って作ります。そのためにdefinitionsオブジェクトを作り、イテレーター内部に割り当て、synonymsに対しても同様のことをします。
リスト7. Builderを使って全てをまとめる
import groovy.sql.Sql
import groovy.xml.MarkupBuilder
import java.io.File
import java.io.StringWriter
class WordsDbReader{
static void main(args) {
sql = Sql.newInstance("jdbc:mysql://localhost/words", "words", "words", "org.gjt.mm.mysql.Driver")
writer = new StringWriter() builder = new MarkupBuilder(writer)
builder.words() {
sql.eachRow("select word_id, spelling, part_of_speech from word"){ row |
builder.word(spelling:row.spelling, partofspeech:row.part_of_speech){
builder.definitions(){
sql.eachRow("select definition from definition where word_id = ${row.word_id}"){ defrow |
builder.definition(defrow.definition)
}
}
builder.synonyms(){
sql.eachRow("select spelling from synonym where word_id = ${row.word_id}"){ synrow |
builder.synonym(synrow.spelling)
} }
}
}
}
new File("dbouuput.xml").withPrintWriter{ pwriter |
pwriter.println writer.toString()
}
}
}
|
ここで示したバインディング・ソリューションは、特に純粋Java主義者の観点から見ると、申し訳ないほど単純です。このソリューションは、JABXやJiBXのようなバインディング・フレームワークでできることに比べて、より良いわけではありませんが、より素早く行うことができ、少し容易であることを私としては強調したいと思います。同じようなことが、単純なJavaコードでできるでしょうか。確かにできますが、途中のどこかで必ずXMLをいじる羽目になります。
GroovyのBuilderを使って開発することによるスピードと簡潔さは、マークアップが必要な状況では便利なはずです。例えば2番目の例で見た通り、データベースを表現するXMLを一瞬で作ることができました。Builderは、プロトタイピングや、最小の時間と努力で実働ソリューションを作らなければならない場合にも、素晴らしい選択肢となります。
次回の実用的なGroovyでは何を取り上げましょう? もちろん、なぜJava言語内部でGroovyを使うのか、についてです。
-
実用的なGroovyシリーズの他の記事もぜひ読んでください。シリーズの進行と共に、それぞれの記事がお互いに関係してきます。今回の記事に特に関係するものとして、下記の記事があります
- GroovyによるAntスクリプト(2004年12月)は、Groovy BuilderとGroovyのマークアップ機能を紹介しています。
- Groovyでサーバー側に対応する(2005年2月)は、GroovyMarkupを紹介しています。
- GroovyによるJDBCプログラミング(2005年1月)は、なぜGroovysqlを新たなデータベース・アクセス・フレームワークとすべきなのかを説明しています。
- Dennis SosnoskiによるXML and Java technologies(developerWorks, 2003年1月)を読んで、(JiBXを含め)データ・バインディング・フレームワークについて、さらに学んでください。
- Groovyは、Groovy open source project pageからダウンロードすることができます。
- developerWorksのJava technologyゾーンには、Javaプログラミングのあらゆる面に関する記事が豊富に用意されています。
- また、developerWorksのJava technologyゾーンのtutorials pageには、Javaに焦点を当てた無料チュートリアルの完全なリストがありますので、ご覧ください。
- developerWorks blogsに参加して、developerWorksコミュニティーに加わってください。
