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.
Java Integration in JRuby (yet again)
From Java to Ruby
The above code retrieves
barfrom 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
callto execute the function but with a bunch of Rhino specific arguments). To demonstrate how far JRuby takes it's Java integration we used
alias_methodto 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#callif 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 JavaSo 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 ...
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
Functioninstance (at the time it gets assigned into the context as a property).
Let's walk down the code explaining it step by step :
- than the
RubyFunctionclass is created by extending (a Java class)
BaseFunction, the very same as mentioned previously, note that we used the
JSmodule to access it
- a Java interface
Wrappergets included and to play by its rules we introduce an
initializemethod requires an argument that will be our actual "callable" Ruby object (
Proc) we're calling
superwith 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
callmethod (with it's Java argument semantics as explained previously above), we send a
callmessage to the Ruby
@callableand return the result
__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
RubyFunctionthat gets invoked (Ruby)
callstyle, for such a case the code ain't complete.