More “Monkey Patch”-ed Java Classes

28 02 2007

Last time, I demonstrated how to re-open Java objects from JRuby and I used a completely worthless example. This time, the example is only mostly worthless. By that I mean, we’re going to add some Ruby-like aesthetics to a very useful Java library called XOM. The perceived value of this code is directly proportional to how much you believe you should be able to use each, any?, all?, include?, or grep on all collection-like objects from JRuby code.

XOM is a well designed and very opinionated Java library. It is also my preferred library for general purpose XML wrangling. However, because of the principles under which XOM was developed (not implementing collections API, I’m looking at you), certain Ruby conveniences don’t get applied to XOM objects in JRuby. In particular, XOM’s collections don’t implement the Java collections framework. Because of this, XOM collections don’t get decorated with Ruby-like iterators and such. We’re going to change that.

Before we get started, make sure you can reach XOM from JRuby:

1) Download XOM, of course.
2) Set or export your CLASSPATH so that it includes the XOM jar.
3) Fire up jirb.

The classes we’re looking at are Nodes and Elements. I would like for both of these classes to behave more like Ruby array objects. For the Nodes class, which is a modifiable collection, I want [] to be an alias for get, I want << to be an alias for append, and I want to mix in the Ruby Enumerable module. For Elements, which is a read-only collection, I want the [] alias and Enumerable.

So first things first:

require 'java'

XomNodes = Java::nu.xom.Nodes

XomElement = Java::nu.xom.Element

XomElements = Java::nu.xom.Elements

This should look familiar, but you’ll notice that we used the Java module to reference the XOM class objects. This is because the magic that makes JRuby understand that java.lang.String is a Java class, doesn’t work for nu.xom.Nodes, because nu is not a common Java package starter. So we use Java::nu.xom.Nodes, and everything works great.

Now, if we want our collections to behave more like Ruby arrays, then we need to mix Enumerable into our Java objects. Enumerable will not work unless the object has an each method that iterates over the collection, passing each iterated item to yield, in turn.

module XomCollectionExtensions
  include Enumerable

  # Implements each method as described in the Enumerable contract
  def each
    (0...size).each { |i| yield get(i) }

What I’ve done here is created my own mixin module. I’ve included Enumerable in my module, and I’ve created the each method. My each implementation just uses an end exclusive Range to iterate over the Nodes in the collection.

Now I can either re-open the Java class and include my module (as well as aliasing the append and get method)…

# modifications to the nu.xom.Nodes to make it behave more
# like a Ruby Array
class XomNodes
  include XomCollectionExtensions

  alias_method :[], :get
  alias_method :<<, :append

… or I can send the include and alias_method messages to the class.

XomElements.send :include, XomCollectionExtensions
XomElements.send :alias_method, :[], :get

If you’ve been following along, you should now be able to check out your handiwork:

nodes =
nodes <<'first')
nodes <<'second')

nodes.each { |n| p n.to_xml }

Also, if you’re interested in what else can be done with this sort of black magic (besides hacking up your Java objects at runtime), errtheblog has a some nice examples of using alias_method_chain. A welcome bit of warmth that was on this cold and snowy evening, I can tell you.




2 responses

31 01 2008
Brian Sam-Bodden

Thanks for this. Incidentally I was playing with JRuby and XOM. Created a little library that pretty much is a blatant copy of Jim Weirich’s Builder and implemented it on top on XOM.

28 03 2008
Jay Lawrence

Nice stuff! We’re using JRuby a lot too … I will definitely be using these ideas to make our JRuby experience even more rewarding.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )


Connecting to %s

%d bloggers like this: