JavaからJRubyへの置き換え手順
CalcChargeImplをJavaからJRubyに置き換える手順は次のとおりです。
- JRubyでCalcChargeインターフェースを満たすコードを実装する
- コンポジットファイルを修正する
たったこれだけ? と思われた方も多いでしょう。筆者もこの簡単さを皆さんに紹介したくて記事を書きました。
JRubyでCalcChargeのインターフェースを満たすコードを実装する
冒頭でも言ったとおり、筆者はJRubyを書いたことが一度もありませんでした。図1が最初に書いたCalcChargeImpl.rbの抜粋です。特に複雑なことを行っていないので非常に楽でした。
ただし、実行しても送料と合計が表示されません。retValues
の値も実行ログ(図2)を見るときちんと計算されています。
こういう場合は正攻法が1番です。BasketImplにブレークポイントを置きデバッグです。図3のように104行目のcalcCharge.getCharge
の戻り値をint
型の配列に代入する箇所、105行目の戻り値を返す箇所の2か所に置きました。
104行目までは進むのですが、105行目まで進まず、処理が終わります。図2できちんと値を返していると思っているのが間違いである可能性が大きいです。例外が発生していれば、例外を補足していないため落ちるはずです。ただ、ここはJRubyからの戻り値を扱っているため何か起きていても不思議ではありません。考え付くのは例外以外はありません。図4のようにtry catch
ブロックで処理を囲みました。
やはり図5のようにClassCastExceptionが発生していました。ClassCastExceptionはRuntimeExceptionを継承したクラス、つまりチェック例外で、発生したメソッド内で例外を補足しなくてもエラーで落ちることはありません。しかも、戻り値として期待していた単なるint
型の配列ではなく、org.jruby.RubyArray
でした。この後はJRubyを知っている方には当たり前なのかもしれませんが、筆者は苦労しました。org.jruby.Ruby
はgetArray
というメソッドを持っており、JavaのArrayListを返してくれます。しかし、ここで行き詰ってしまうのです。JRubyにはJavaのプリミティブに相当する概念がありません。仕方なく当初の目的を捨て、CalcChargeImpl#getCharge
の戻り値をArrayListに変え、ArrayListから1つずつ値を取り出し、int
型に変換し、配列に格納するという非常に不格好なコードができてしまいました。
ただし、これでは言語を変えるたびにインターフェースも変えなくてはならないことを意味しています。幸いこの場合、CalcChargeのサービスをコンポジットの外部に公開していないからいいのですが、公開している場合であれば、それを利用している他のコンポジットあるいはドメインにまで影響を与えます。ネットで粘って探した結果、JRubyのreturn
文の中で、戻り値をretValues.to_java(:int)
とするだけなのです。これだけでorg.jruby.RubyArray
がJavaのint
型の配列に変換されます。言語間のインターフェイスはできるだけシンプルなものがいいということが言われますが、配列は要注意です。
これでBasketImplの実装やCalcCharge#getCharge
の戻り値も変えることなく、当初の目標をクリアできました。CalcChargeImpl.rbの詳細は、ページトップにサンプルコードがあるので、ダウンロードして参照していただければと思います。
コンポジットファイルを修正する
JavaからJRubyへの置き換え手順として、「JRubyでCalcChargeインターフェースを満たすコードを実装する」方法を説明しましたが、当然、コンポジットファイルも修正しないと動作しません。同時に説明すると混乱するためあえて別の節として取り上げています。
コンポジットファイルの修正も至って簡単です。図6のように指定するだけです。2点注意すべき点があります。1点目はCalcChargeのt:implementation.script
要素のscript
属性の値は、図7のようなフォルダ構成の場合、「jp/kawakubo/wine/CalcChargeImpl.rb」のようにパス指定になります。JRubyをここに置く必要はまったくありません。srcの下にscriptsというフォルダを作り、その下に置く場合は「scripts/CalcChargeImpl.rb」のように指定します。2点目は、既にお気づきかとは思いますが、component
要素の子要素はimplementation.script
ではなく、頭にt:
が付くことです。JRubyはSCAに準拠しているわけではなく、Tuscanyの実装だからです。composite
要素の属性としてxmlns:t="http://tuscany.apache.org/xmlns/sca/1.0"
を指定していることからお分かりいただけると思います。
忘れ物
チェックの厳しい方はお気づきかもしれません。タイムセールスのメソッドに渡すためのproperty
要素をまだ記述していないのです。いろいろな文献やサイトを調べても欲しい情報が得られず、組み合わせ表を作り調査すること4時間、たどりついたのが以下の結果です。
プロパティの注入の仕方
- コンポジットファイルのルート要素の属性に「
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
」を追加(図8) - 注入したいコンポーネントの
property
要素にtype
属性を追加(図9) - JRubyでプロパティを受け取るために属性名の頭に
$
を付ける(図10)
これで、CalcChargeImplをJavaからJRubyに置き換えることができました。他のプログラムに一切手を加えていないことは非常に驚きです。コンポジットファイルを少し修正するだけで済みました。Groovyが得意な人、Pythonが得意な人はぜひ、CalcChargeImplをそれらの言語で置き換えてみてください。
次回は、Javaで書いたコードをGroovyへも簡単に置き換えられることを説明します。