以前、libxml-rubyで [BUG] object allocation during garbage collection phase で、元々libxml-rubyを使って書いてたコードをjavaのDOM/XPath APIに置き換えるために、ラッパを書いた。

のだけど、

「JRuby徹底入門」という本を読んだら、javaのクラス(のproxy)にメソッドを後付けするにはJavaUtilities.extend_proxyを使う、という例が載ってた。

なるほど便利。

なんでも実行時に決定できるってすごいことだな。

jruby:1.1.3
Sun Java: 1.6.0_05
OS:CentOS 5.2

$KCODE='u'

require 'java'

# org.w3c.dom.Documentにlibxml-ruby風I/F追加
JavaUtilities.extend_proxy('org.w3c.dom.Document') do
def root
self.documentElement
end
end

# org.w3c.dom.Nodeにlibxml-ruby風I/F追加
JavaUtilities.extend_proxy('org.w3c.dom.Node') do
@@xpath = nil

def to_s
self.nodeValue.to_s
end

def next
self.nextSibling
end

def text?
self.nodeType == org.w3c.dom.Node::TEXT_NODE
end

def [](name)
self.getAttribute(name)
end

def parent
self.parentNode
end

def child
self.firstChild
end

# XPathオブジェクトを返す
#
# TODO: XPathオブジェクト1つで使いまわしてみたが・・・
# evaluateの都度生成よりはいいような気がするがどうだろう
def get_xpath
if !@@xpath
factory = javax.xml.xpath.XPathFactory.newInstance()
@@xpath = factory.newXPath()
end

@@xpath
end

# 指定されたノードに対するxpath式のクエリ結果を返す
#
# expr::
# XPath式
def find(expr)
nodelist = self.get_xpath.evaluate(expr, self, javax.xml.xpath.XPathConstants::NODESET)

ret = []
for i in 0..(nodelist.length - 1)
ret.push(nodelist.item(i))
end
ret
end
end


# 指定されたファイルをopenしてXML文書を返す
#
# fn_in::
# ファイル名。nilの場合、stdinを適用
def build_doc(fn_in)
st = java.lang.System.in
if fn_in
st = java.io.FileInputStream.new(fn_in)
end

factory = javax.xml.parsers.DocumentBuilderFactory.newInstance()
builder = factory.newDocumentBuilder()
builder.parse(st)
end


doc = build_doc("test.xml")
doc.find("//entity").each { |e|
puts e["name"]
}