Using Java from Ruby with JRuby IRB
Last month I visited a joint meeting for the Richmond Java User Group and Central Virginia Ruby Enthusiasts’ Group. The audience was evenly split between Java and Ruby developers, which made for an ideal setting for my presentation about Java-Ruby interoperability that JRuby facilitates. On the tails of another great JRubyConf last week, I was inspired to share a summary of my presentation and my slides with interested folks who weren’t able to attend the talk.
###Using a Java library from Ruby
In this article, I’ll focus on using a Java library from Ruby. You see, if you were to tinker with a single Java method, you would need to write a complete program with “public static void main(String[] args)”
. As we will demonstrate here, with JRuby IRB, it becomes almost trivial to play with Java methods.
We will use the unique Java library Akka here. According to the Akka website, “using the Actor Model together with Software Transactional Memory [Akka raises] the abstraction level and provides a better platform to build correct concurrent and scalable applications.”
###The Akka Getting Started Tutorial
To begin, let us look at the Akka Getting Started Tutorial written in Java, and translate it into Ruby.
The example computes π using the Madhava-Leibniz series.
Note that in the series, the neighboring terms can be gathered into a smaller work unit without disturbing the identity, or other work units. We created a worker and a few additional workers that communicate by passing a message–Plain Old Java/Ruby Object with a little extra information–along with the message router acting as a broker.
For comparison, here is the Ruby version, and the Java version. The Ruby version is shorter, and requires less ceremony in setting up various classes involved in the program.
###Exploring the code
Now let us walk through the key points in the Ruby version.
Preliminary stuff
require "java"
Line 1: Enable Java integration support in JRuby.
$: << File.join(File.dirname(__FILE__), 'lib')
Line 3: Add the lib directory (that resides in the same directory as the current file) to JRuby’s library search paths.
java_import 'akka.actor.Actors'
java_import 'akka.actor.ActorRef'
java_import 'akka.actor.UntypedActor'
java_import 'akka.actor.UntypedActorFactory'
java_import 'akka.routing.CyclicIterator'
java_import 'akka.routing.InfiniteIterator'
java_import 'akka.routing.Routing'
java_import 'akka.routing.UntypedLoadBalancer'
java_import java.util.concurrent.CountDownLatch
Lines 8 through 16: Import the Java library’s classes into the current namespace so we don’t have to prefix them with Java::…. This is similar to Java’s import statements.
def actorOf(&code)
Actors.actorOf(Class.new do
include UntypedActorFactory
define_method(:create) do |*args|
code[*args]
end
end.new)
end
Lines 18 through 25: This is a convenient method to create an Akka actor instance through the use of Ruby’s Class.new, which is analogous to anonymous class declaration in Java. This greatly reduces the clutter when used on lines 81 and 125.
class Calculate; end
class Work < Struct.new(:start, :nrOfElements); end
class Result < Struct.new(:value); end
Lines 27 through 29: Define Plain Old Ruby Objects acting as messages between Akka actors. Note there is a lot less noise for these classes than there is for the Java counterparts.
Worker class
class Worker < UntypedActor
# needed by actorOf
def self.create(*args)
new *args
end
# define the work
def calculatePiFor(start, nrOfElements)
((start * nrOfElements)...((start + 1) * nrOfElements)).inject(0) do |acc, i|
acc + 4.0 * (1 - (i.modulo 2) * 2) / (2 * i + 1)
end
end
# message handler
def onReceive(message)
if message.kind_of? Work
work = message
# perform the work
result = calculatePiFor(work.start, work.nrOfElements)
# reply with the result
context.replyUnsafe(Result.new(result))
else
raise IllegalArgumentException.new "Unknown message [#{message + b}]"
end
end
end
Lines 31 through 59 define the Worker class. The part that actually performs the calculation calculatePiFor
is much more compact with the use of Enumerable#inject. When this actor is sent a message, it executes the methodonReceive.
Following the Java example, we are examining the class of the message passed; this is admittedly not characteristic of Ruby.
PiRouter class
class PiRouter < UntypedLoadBalancer
attr_reader :seq
def initialize(workers)
super()
@seq = CyclicIterator.new(workers)
end
end
Lines 61 through 68 define the PiRouter class. Besides having an instance variable :seq
, there isn’t much code, much of it is done in the Akka library itself.
Master class
class Master < UntypedActor
def initialize(nrOfWorkers, nrOfMessages, nrOfElements, latch)
super()
@nrOfMessages, @nrOfElements, @latch = nrOfMessages, nrOfElements, latch
@nrOfResults, @pi = 0, 0.0
# create the workers
workers = java.util.ArrayList.new
nrOfWorkers.times { workers << Actors.actorOf(Worker).start }
# wrap them with a load-balancing router
@router = actorOf { PiRouter.new(workers) }.start
end
# message handler
def onReceive(message)
if message.kind_of? Calculate
# schedule work
@nrOfMessages.times do |start|
@router.sendOneWay(Work.new(start, @nrOfElements), context)
end
# send a PoisonPill to all workers telling them to shut down themselves
@router.sendOneWay(Routing::Broadcast.new(Actors.poisonPill))
# send a PoisonPill to the router, telling him to shut himself down
@router.sendOneWay Actors.poisonPill
elsif message.kind_of? Result # handle result from the worker
@pi += message.value
@nrOfResults += 1
context.stop if @nrOfResults == @nrOfMessages
else
raise IllegalArgumentException.new "Unknown message [#{message}]"
end
end
def preStart
@start = java.lang.System.currentTimeMillis
end
def postStop
# tell the world that the calculation is complete
puts format("\n\tPi estimate: \t\t%s\n\tCalculation time: \t%s millis",
@pi, (java.lang.System.currentTimeMillis - @start))
@latch.countDown
end
end
Lines 70 through 116 define the Master class. Master responds to two kinds of messages. Calculate sets everything in motion. Result messages are sent from the PiRouter to the Master, which adds up the values of the Result messages until the set number of them were sent its way.
Pi class
class Pi
def self.calculate(nrOfWorkers, nrOfElements, nrOfMessages)
# this latch is only plumbing to know when the calculation is completed
latch = CountDownLatch.new(1)
# create the master
master = Actors.actorOf do
Master.new(nrOfWorkers, nrOfMessages, nrOfElements, latch)
end.start
master.sendOneWay(Calculate.new) # start the calculation
latch.await # wait for master to shut down
end
end
Pi.calculate(4, 1000, 1000)
Lines 119 through 133 include the top-level class with one class method that sets up the concurrency latch and sends the Calculate message to the Master. ###Conclusion This example shows the basics of Java-Ruby interaction in a complete program. These fundamentals are applicable to your first explorations. If you come up with your own harmony of Java and Ruby, be sure to share it with us in the comments section. ###Thank you, Richmond! (And beyond?) I enjoyed sharing JRuby with the user group members I met in Virginia. Thanks again to the Richmond Java User Group and Central Virginia Ruby Enthusiasts’ Group for hosting me. We are always excited to share JRuby with a larger audience and to hear your thoughts. If you are interested in arranging a JRuby presentation, drop us a line, and we’ll chat!
Share your thoughts with @engineyard on Twitter