Fork me on GitHub

September 10, 2012

Spicing up Java with Ruby (a Rhino Story)

Post moved to

This post is certainly not a new comer, there's plenty of posts and a wiki covering the topic of JRuby's Java integration. But since, repetition is the mother of all wisdom, why not yet another, besides this one will be inspired by pieces of (simplified) use-cases out there, I promise.
These days more than ever, understanding the metal underneath JRuby might come very handy and there's two good reasons for that. First of all as more Java shops adopt JRuby, they might need to integrate with their existing libraries. Second, as Ruby-ist are trying out JRuby as a deployment option, they might save a wheel being re-invented by using third-party code beyond gems - by borrowing jars. Although it still seems not likely to happen a lot, cause somehow Java scares the shit out of a "traditional" Ruby monkey. I'm not going to argue here why it's needlessly counter productive to ignore Java, esp. since there's still a lot to learn from programming concepts such as POJOs.

Rhino's Story

As promised, this is going to be a Rhino story, which is a fairly old JavaScript implementation written in Java (a the very first successful polyglot JVM pioneer). It's childhood goes back to the days when JavaScript was considered nothing but pure evil. I remember a CS graduate complaining, during a lunch we shared, about Mozilla being so lame not to be able to make JavaScript work the same as Microsoft's "original" in the latest IE6. These were the dark days Rhino was growing up. Luckily we moved forward since than, to the new-age of JavaScript renaissance.
Rhino is gemified for quite some time and is known as therubyrhino gem. He's actually therubyracer's older brother and allows us to integrate Ruby objects into the JavaScript world and the other way around: call JavaScript objects like they were Ruby instances, let's take a closer look what that means.

Java Integration in JRuby (yet again)

To make a JavaScript interpreter really useful from Ruby, we'll need to be able to call a (native) function seamlessly just like a Method. And, we'd also like to use Ruby objects from JavaScript and invoke their methods like vanilla functions. These two challenges will serve really well in demonstrating the power of Ruby meeting Java. For simplicity we assume that we've gone through a few quirks e.g. we're able to evaluate strings of JavaScript already.

From Java to Ruby

The above code retrieves bar from a context and if it's a Function calls it just like a Proc. To make sure this works, we should wrap all (Java) objects we get from Rhino, and pass to the Ruby world, into a class with Proc#call semantics. Turns out JRuby itself decorates all Java instances already to provide them with a taste of Ruby, thus additional wrapping seems redundant, we rather attach Ruby-sh behaviour to Rhino's Java classes. Just like in Ruby, we might open a Java class and all instances of the given class (no matter if their were instantiated inside Java or Ruby code) will act upon those methods.

We've seen the Function interface already, all functions in Rhino implement it, although concrete classes might vary. Opening all possible implementations is certainly an option, but since there's an abstract BaseFunction they all inherit from, it's sufficient to define behaviour in one place. JRuby when dispatching methods of a concrete instance (e.g. a NativeFunction) will look for Ruby methods on it's (Ruby "decorated") Java class and all it's super classes just like you would expect for a Ruby object to resolve methods from it's class chain.
Now the tricky part is that we're name clashing, there's the (Java) call() interface method prescribed already (which is actually the method we want to delegate our call to execute the function but with a bunch of Rhino specific arguments). To demonstrate how far JRuby takes it's Java integration we used alias_method to alias a Java method, now if you're paying attention you should immediately object that this won't work as I've aliased a dummy method from a "base" class as __call__ that will eventually be overriden in an extending class. This is very true for Ruby but Java has slightly different method semantics - there's no simple way to call an arbitrary super method that has been redefined (and JRuby respects that), even a casted Java invocation ((BaseFunction) bar).call(...) will not dispatch to BaseFunction#call if it has been overiden in bar's class. And since (so far) all our functions are coming from Java it will work as expected.

From Ruby to Java

So far - so Ruby, now it's time to make our hands dirty (and I'm talking Java dirty :)) as we pass a Ruby lambda into Rhino we need to write some Java code, or maybe not ...

Here, we're "exporting" a Ruby lambda into the context and later calling it as a JavaScript function, yet again, this will require some wrapping for callables.
Fortunately, JRuby allows us to create Java classes (inherit from them and implement interfaces) with Ruby code, let the beauty of this "Java" class speak for itself:

Although 100% Ruby it might as well be considered a Java class since it will leave JRuby-land as it gets passed to Rhino's typed Java API as a Function instance (at the time it gets assigned into the context as a property).

Let's walk down the code explaining it step by step :
  • first we create a module as a namespace for all the classes from the "org.mozilla.javascript" Java package
  • than the RubyFunction class is created by extending (a Java class) BaseFunction, the very same as mentioned previously, note that we used the JS module to access it
  • a Java interface Wrapper gets included and to play by its rules we introduce an unwrap method (this is a Rhino convention used when decorating non JavaScript objects)
  • our initialize method requires an argument that will be our actual "callable" Ruby object (Method or Proc) we're calling super with no args since one of BaseFunction's constructors does not take any and we'd like to make sure we call the correct one
  • we override some getters that we inherited, notice that we do not need to specify return types, JRuby will take care of things and make sure their correct (it does now that since it detects that we're overriding a method already present)
  • the most interesting part is defining the call method (with it's Java argument semantics as explained previously above), we send a call message to the Ruby @callable and return the result
There's one last trick, we've declared a __call__ alias. Recall that we've done a similar alias previously to deal with the call (Java - Ruby) method name collision - we've expected methods with the same name to do different things based on whether they're being called from Ruby or Java. Thus to satisfy the convention we need to make sure __call__ points to the updated method in case it's called from Ruby. After all, we might end up calling a piece of JavaScript that in turn returns a RubyFunction that gets invoked (Ruby) call style, for such a case the code ain't complete.

As mentioned in the beginning we did a few simplifications, our BaseFunction#call needs exception handling and RubyFunction#call should perform JavaScript style argument slicing or filling. Feel free to tune into the actual sources (and specs) if you'd interested in how the wheels turn. Also do not forget to checkout JRuby's wiki page on the topic.