■ [wo] 噂の Groovy を WebObjects で使ってみる
Ruby 使いの私が WebObjects を使っていて残念に思うのは、 「WebObjects の生産性の高さを Java が足引っ張っている」 ということです。
Groovy がこの Java の面倒臭さを かなり緩和してくれそうだったので、早速試してみました。
WebObjects の優雅さと Java の間の壁
WebObjects の凄さは、EOF/WOF というクラスライブラリの 機能だけではなく、
- EOModeler
- Xcode(Project Builder)
- WebObjects Builder
という 3 つの開発アプリケーションの密接な連携によって生み出されます。
Web アプリケーションで必要とされる典型的な以下のような機能については、 上記のアプリケーションの連携によって、SQL や Java のコードなどを ほとんど書くことなく実装できます。
- フォームなど HTML のレイアウトをする
- フォームから受け取ったデータをデータベースに格納
- データベースに入っているデータをテーブルなどで一覧表示
- データベースの検索
- 結果のソート
- ページめくり
スケッチ(or 落書き)をするぐらいの気軽さで Web アプリができてしまう、 という優雅なプログラミング環境が実現されています。

ところが、こういう典型的な機能からちょっと外れた機能を実現しようと すると、せっかくのシームレスで快適な開発環境に 突然「Java の壁」が立ちはだかります。
Java の面倒臭さ
Java というのはとにかく多くの記述量を強いられる言語です。
例えば、「外部プロセスとして "ls -l /" を実行して結果を 標準出力へ出力する」という非常に簡単なプログラムを考えてみましょう。
Java で書くと以下のようになります。
Runtime r = Runtime.getRuntime();
Process p = r.exec("ls -l /");
InputStream is = p.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
これが、 Ruby であれば、なんとたったの 1 行、
IO.popen("ls -l /").each{|l| puts l}
だけで済みます(というか、これで済んで然るべきだと思います)。
もっと言うと
system("ls -l /")
で済んでしまいます(Java 側のソースとの対比ではこれは少し 対等じゃない解なので、こっちは参考程度に)。
こんな簡単な例でさえ、 Java は Ruby の 8倍ぐらいの記述量を必要とします。
また、登場するクラスの数も Ruby では IO と String ぐらいしかないのに対 し、 Java では Runtime, Process, InputStream, InputStreamReader, BufferedReader, String, System と 7 つものクラスを連携させる必要があり ます。
ここから更に ls -l の結果を正規表現でマッチして一部だけを取り出すとか 加工するなど、複雑な話になればなるほどその差は拡がる一方です。
また、 Java では変数の宣言が必要なために、思い付いたことを コードにする前に「変数宣言」という準備作業に邪魔されて 思い付いたことをすぐにコードにできないもどかしさもあります。
もちろん Java には
- GUI も含め、だいたいどんな場面でも利用できる
- 動作速度が C++ 並に速い
- 各種ライブラリが充実している
- 技術者が多い
などなど良い点がたくさんあるので、現時点では最も 現実的な解だとは思いますが、 Ruby 使いにとってこの記述量は 面倒とかいうレベルを通り越して屈辱的ですらあります。
Groovy とは?
現在、 Groovy という Java VM 上 で動く新しい言語の開発が進んでいます。現時点では、groovy-1.0-beta-5 という、まだ 1.0 の正式版が出ていない状態です。
簡単に特徴を挙げると、以下のようになっています。
- Java VM 上で動く
- 文法が Ruby や Python などに近い
- Java ソースに Groovy スクリプトを埋め込んで実行できる
さて、この Groovy で上記と同じプログラムを書いてみましょう。
"ls -l /".execute().in.eachLine{println it}
どうでしょう?Ruby と比べてしまうと、それほどありがたみは感じない かもしれませんが、 Java から比べれば、ものすごい進歩じゃないでしょうか?
これを Java のソースに埋め込む時は少し決まり文句が増えて 以下のようになります。
import groovy.lang.*;
import org.codehaus.groovy.control.*;
Binding binding = new Binding();
GroovyShell sh = new GroovyShell(binding);
Object result = sh.evaluate("\"ls -l /\".execute().in.eachLine{println it}")
今回は短い例なので決まり文句の比率が多くなり、元の Java のソースと 大差ない記述量になってしまいますが、これがもっと複雑な例になると 決まり文句の部分は増えず、evaluate() の中身が増えて行くだけですので、 素の Java と比較すると、かなり記述量が減ることは間違いありません。
また、記述量が減ることも大事ですが、Ruby で感じられる 「思考を妨げず、思考の流れのままに記述できる」、 つまり「ls -l を実行して結果を一行ずつ出力」という思考の流れと 実際のプログラムの表現が似ている、という特徴を Groovy もかなり実現できている点が非常に重要です。
更に、 groovysh というインタラクティブなシェル環境 (Ruby でいう irb みたいなもの)が用意されているので、 ここで簡単にテストプログラムを書いて動くことを確認してから、 本番のソースに組み込める、というのも魅力です(Java でも BeanShell とか使えばできますけど)。
WebObjects のソースに Groovy ソースを組み込む
さて、ここまでできてしまえば、 WebObjects へ組み込むのはできたも 同然です。 groovy のパッケージに入っている lib 以下の .jar ファイルを全て
/Library/Java/Extensions
以下にコピーします。後は、「埋め込み Groovy」のコードを 書くだけです。環境変数をいじったり、XCode の設定を変えたりなどは 全く必要ありません。
WebObjects Builder で WORepetition を使って ls の結果を 表示するページを作成し、以下のようなコードを書いて実行すると 問題なく ls の結果が表示されます。

import com.webobjects.foundation.*;
import com.webobjects.appserver.*;
import com.webobjects.eocontrol.*;
import com.webobjects.eoaccess.*;
import groovy.lang.*;
import org.codehaus.groovy.control.*;
import java.io.*;
import java.util.*;
public class Main extends WOComponent {
/** @TypeInfo java.lang.String */
public NSMutableArray ls;
public String item;
public Main(WOContext context) {
super(context);
try{
Binding binding = new Binding();
GroovyShell sh = new GroovyShell(binding);
Object result = sh.evaluate("r = []; " +
"\"ls /\".execute().in.eachLine{r.add(it)}; " +
"r.each{println it};" + // この println はデバッグ用。必須じゃないです。
"return r");
String[] ary = (String[])((ArrayList)result).toArray(new String[0]);
ls = new NSMutableArray(ary);
}catch(CompilationFailedException e){
System.err.println("CompilationFailedException:" + e.getMessage());
}catch(IOException e){
System.err.println("IOException:" + e.getMessage());
}
}
}

WebObjects から Groovy スクリプトを呼び出す
短い groovy スクリプトであれば、上記のように文字列で指定すれば良いです が、もっと長い本格的な Groovy スクリプトを作る場合は、スクリプトのファ イルは独立させておきたいですね。そういう場合は以下のようにします。
まず、プロジェクトの Resources フォルダ上でコンテクストメニューを出し、 新規ファイルを追加します。

ターゲットに Application Server を選んで、適当にファイル名を付けます。

後は、このファイルに Groovy スクリプトを書きます。
r = []
"ls /".execute().in.eachLine{r.add(it)}
r.each{println it}
return r
この Groovy スクリプトが正しく動作するかどうかは、以下のように コマンドラインから groovy を実行することで確認できます。 Groovy のページの説明 にあるように、環境変数 GROOVY_HOME と PATH の設定をしておくのを忘れずに。
# パスを通す $ export GROOVY_HOME=/Users/tmaeda/groovy-1.0-beta-5 $ export PATH=$GROOVY_HOME/bin:$PATH # 確認 $ echo $GROOVY_HOME $ echo $PATH $ cd /Users/tmaeda/...(略).../GroovyTest/ $ groovy ./ls.groovy
単体で正しく動くことが確認できたら、このスクリプトを WebObjects 側のソースから読み込んで実行しましょう。
public class Main extends WOComponent {
/** @TypeInfo java.lang.String */
public NSMutableArray ls;
public String item;
public Main(WOContext context) {
super(context);
try{
Binding binding = new Binding();
GroovyShell sh = new GroovyShell(binding);
WOResourceManager rm = application().resourceManager();
// ResourceManager 取得
InputStream script =
rm.inputStreamForResourceNamed("ls.groovy", null, null);
// Resources フォルダから ls.groovy を読み込む
Object result = sh.evaluate(script);
String[] ary = (String[])((ArrayList)result).toArray(new String[0]);
ls = new NSMutableArray(ary);
}catch(CompilationFailedException e){
System.err.println("CompilationFailedException:" + e.getMessage());
}catch(IOException e){
System.err.println("IOException:" + e.getMessage());
}
}
}
Groovy スクリプトの呼び出しをもっと簡単に
WebObjects で Groovy を利用する場合、だいたい用途は以下のパターンに なることが多いと思います。
- 何か実行して、結果を文字列で受け取る
- 何か実行して、結果を文字列の NSArray で受け取る
ですから、もし頻繁にスクリプトを呼び出すのであれば、以下のような static メソッドを Application.java に定義しておけば、スクリプトの 呼び出しがより簡単になるでしょう。
public static NSMutableArray executeGroovy(String filename)
{
String[] ary = {};
try{
GroovyShell sh = new GroovyShell();
WOResourceManager rm = application().resourceManager();
InputStream script = rm.inputStreamForResourceNamed(filename, null, null);
Object result = sh.evaluate(script);
ary = (String[])((ArrayList)result).toArray(new String[0]);
}catch(CompilationFailedException e){
System.err.println("CompilationFailedException:" + e.getMessage());
}catch(IOException e){
System.err.println("IOException:" + e.getMessage());
}
return new NSMutableArray(ary);
}
こうすれば、 Main.java では以下のように 1 行書くだけで、 スクリプトの呼び出しが可能です。
ls = Application.executeGroovy("ls.groovy");
Groovy スクリプトへ WebObjects 側から引数を渡す必要が ある場合なども考慮して、Groovy の Binding オブジェクト と絡めるとか、 GroovyShell.run(InputStream in, String fileName, String[] args) と絡めるなど、もう少し練る必要がありますが、基本は同じでしょう。
その他、全体の感想
Groovy はまだ開発途中のものなので、 Ruby から比べると 若干機能不足な点が目につきますし、 上記埋め込み Groovy スクリプトの実行時は、 ネイティブ Java のコードを実行するのに比べて若干重たい 感じもします。
また、開発環境も Eclipse の Groovy Plugin が開発途中のようですが、まだ安定していないようです。 emacs にもまだ groovy-mode というのは ないようです(現時点では jdee の流用で妥協?)。 しかし、これらは今後、開発が進むにつれて解決していくことでしょう。
Java に埋め込んで利用できる記述能力の高い言語の登場に 大変ワクワクします。今後の動向にも注目していこうと思います。
2004/07/21 追記
SPICE OF LIFE さんがこれを更におしすすめて、 WebObjects ソースを完全に Groovy で書く手順を作成していらっしゃいます。 素晴しい。
あと、 Groovy-beta-6 が出ました。 かなりのバグフィックスや機能追加が行われたようです。
参考文献
- JAOO Cannes 2004 2004/05/24-26 に開かれた JAOO というカンファレンスで Groovy が紹介さ れたときのプレゼン資料(PDF)。手っ取り早く Groovy の特徴を掴むにはこ れが一番かもしれません。
- Groovy - Language Guide Groovy のサイトの言語ガイド。ここでも Groovy の概要が掴めます。
- Groovy Wiki Groovy Wiki。Groovy のサンプルコードなどが公開されていますが、 Groovy のアップデートに追い付いてなくて現在の Groovy では動かないコー ドもあるようです。
Groovyなる言語知りませんでした。すばらしい。
この記事Blogで紹介されていただきました。
koshida さん、御紹介ありがとうございます。
まだまだ開発途中なので、実用には使えないかもしれませんが、
今後が非常に楽しみな言語だと思います。
すばらし。これを .groovy ファイルを直接利用出来るように発展させることは可能でしょうか。
その状態でさらに、[]がNSMutableArrayになって、[:]がNSMutableDictionaryになってくれればもう言うことなし。
ogino. さん、いらっしゃいませ。
.groovy ファイルを直接利用する方法と、
スクリプトの呼び出しをもっと簡単にする方法を追記してみました。
[] などを NSMutableArray などに変換するスマートな方法は
いまのところなさそうですが、上記のように Application.java に
定型処理を書いておけば、問題はだいぶ解消されると思います。
すばらしい。これは参考になります。ありがとうございました。
kuramochi さん、いらっしゃいませ。
いつも Webページ/掲示板/ML 等でのご発言で勉強させて頂いております。
こちらこそ、ありがとうございます。
サイトの紹介ありがとうございます。NSArrayとNSDictionaryのリテラル化もサポートしたかったのですが、どうもGroovyを拡張する手段が用意されてないのが残念です。
suzuki さん、こんにちわ。
こちらこそ、いつも面白いもの公開して頂きありがとうございます。
今後のご活躍も期待しています。
tickets_3.txt;2;2