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 &lt;&lt; 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!