Ruby/JRuby の上で Java オブジェクトを Ruby String にシリアライズ
Java の Serializable オブジェクトを、簡単に Ruby String にシリアライズできたらどうだろうと思いましたので、実際にやってみました。
目標
こんなことをやってみたいなとおもいました:
pojo = Currency.getInstance('JPY') # 任意の Serializable な Java オブジェクトを作成 p pojo.toString # もとの Java オブジェクトを表示 serialized = PojoSerializer::serialize(pojo) # Java オブジェクトを Ruby String にシリアライズ p serialized.hex # シリアライズした Ruby String を 16進表記でダンプ deserialized_pojo = PojoSerializer::deserialize(serialized) # その Ruby String をデシリアライズ p deserialized_pojo.toString # デシリアライズして得た Java オブジェクトを表示
実装方針
いつもの pagan poetry pattern でいきます*1。Java の複雑な手続きを吸収して Ruby らしくする、グッドラッパーを目指すスクリプトを作ります。
今回は、CRuby でも JRuby でも同じように動かせるように留意しました。
実装後のプログラム実行結果
後述のとおり実装したスクリプトで上のコードを実行すると、以下のような結果が得られました:
$ ruby pojo_serializer.rb "JPY" "ac ed 00 05 73 72 00 12 6a 61 76 61 2e 75 74 69 6c 2e 43 75 72 72 65 6e 63 79 fd cd 93 4a 59 11 a9 1f 02 00 01 4c 00 0c 63 75 72 72 65 6e 63 79 43 6f 64 65 74 00 12 4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72 69 6e 67 3b 78 70 74 00 03 4a 50 59" "JPY" $ jruby pojo_serializer.rb "JPY" "ac ed 00 05 73 72 00 12 6a 61 76 61 2e 75 74 69 6c 2e 43 75 72 72 65 6e 63 79 fd cd 93 4a 59 11 a9 1f 02 00 01 4c 00 0c 63 75 72 72 65 6e 63 79 43 6f 64 65 74 00 12 4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72 69 6e 67 3b 78 70 74 00 03 4a 50 59" "JPY"
JRuby と CRuby の間で、バイトレベルで一致した結果が間違いなく得られていることが確認できます。
実装
PojoSerializer の実装は、下のようになっています。
module PojoSerializer QNS = %w{java.io.ByteArrayOutputStream java.io.ObjectOutputStream java.io.ByteArrayInputStream java.io.ObjectInputStream} begin require 'java' QNS.each do |qn| include_class qn end BRIDGE = :jruby rescue LoadError require 'rjb' QNS.each do |qn| module_eval "#{qn.split('.').last} = Rjb.import('#{qn}')" end BRIDGE = :rjb end def PojoSerializer::serialize(pojo) baos = ByteArrayOutputStream.new oos = ObjectOutputStream.new(baos) oos.writeObject(pojo) oos.flush byte_array = baos.toByteArray if PojoSerializer::BRIDGE == :jruby return byte_array.to_ary.pack('C*') else return byte_array end end def PojoSerializer::deserialize(s) byte_array = '' if PojoSerializer::BRIDGE == :jruby byte_array = s.unpack('C*').to_java(:byte) else byte_array = s.unpack('C*') end bais = ByteArrayInputStream.new(byte_array) ois = ObjectInputStream.new(bais) ois.readObject end end class String def hex s = '' each_byte do |b| s << (b < 16 ? '0' : '') + b.to_s(16) + ' ' end s.chop end end if __FILE__ == $0 begin require 'java' import java.util.Currency rescue LoadError require 'rjb' Currency = Rjb.import('java.util.Currency') end pojo = Currency.getInstance('JPY') p pojo.toString serialized = PojoSerializer::serialize(pojo) p serialized.hex deserialized_pojo = PojoSerializer::deserialize(serialized) p deserialized_pojo.toString end
Java API の細々としたさわり方だとか、Ruby での String の扱い方*2だとかに関する知見をラッパーの中に固定化し、これらの知識を忘れられるようにするためのラッパーです。
JTS の Point をシリアライズするとどうなるか?
GeoTools が使っている com.vividsolutions.jts.geom.Point のインスタンスをシリアライズしてみました:
require 'pojo_serializer' # これが上掲のスクリプト require 'geotools' p PojoSerializer::serialize(Geo::import_array_geometry([0, 0])).size
$ ruby geom_serialize.rb
DEBUG: rjb primitive_conversion mode
1152
地理情報処理の中で、Point はかなり基礎的なデータ単位なのですが、それをシリアライズすると1KB以上と、かなり大きくなってしまうことに驚きました*3。PojoSerializer、Java オブジェクトがどのくらいの大きさなのかを簡単に体感するのにいいかもしれません。
このエントリの品質について
POJO という言葉の使い方が間違っていたらすみません。