Pretty image
Ian shows off how well Ruby plays with others by using JRuby to build a plugin that reads a thermometer.

People are using JRuby for amazing things. They’re running massively scalable websites, designing grammars for new programming languages, and even stopping the proliferation of nuclear weapons.

Does that mean you have to be a superhero facing unimaginable obstacles to use JRuby? No! In this series of three articles, we’ll look at three ways JRuby can help programmers like you and me with the kinds of ordinary tasks that crop up in our day-to-day work.

A Heated Situation

Imagine you’re testing a circuit in your lab, and you need to measure the temperature at certain points throughout your test. You’re doing the number-crunching in an engineering software package like MATLAB or GNU Octave. You’d like to poll the temperature directly in your analysis program, but the thermometer came with no programmer’s manual—just a CD-ROM with a creaky GUI program.

Luckily, you know your way around a packet sniffer. You’ve discovered that the thermometer regularly connects to a configurable IP address over port 4444, sends the Fahrenheit temperature as ASCII text, and disconnects. All you need to do is write a TCP server.

Both MATLAB and Octave have some networking support built in. But they’re mostly geared toward acting as network clients. Writing a server directly in these environments would be a painful task. Fortunately, there’s an alternative.

JRuby to the Rescue

It’s possible to extend MATLAB or Octave by writing a plugin in Java. More interestingly, you can write a plugin in any language that targets the Java virtual machine—including Ruby.

The Ruby language supports the kind of experimentation and prototyping that will make server development easy—and the JRuby project makes it possible to use Ruby on the JVM.

Here’s how we might get started in JRuby. For reasons that will become clear later on, we’re going to use the Java networking APIs, even though they’re a little more verbose than the Ruby ones.

First, let’s pull a few Java I/O classes into the Ruby namespace. This step isn’t strictly necessary—we could just spell out the full names in the middle of our code. But this will keep things readable later on. Create a file called temperature_taker.rb, and type the following lines into it:

 require 'java'
 
 import java.io.BufferedReader
 import java.io.InputStreamReader
 import java.lang.Double
 import java.net.ServerSocket

Now, we’ll add the code that does the real work. We open a socket, wait for the temperature to come in, and convert the result to a number:

 server = ServerSocket.new 4444
 client = server.accept
 stream = client.getInputStream
 reader = BufferedReader.new InputStreamReader.new(stream)
 temperature = Double.parseDouble(reader.readLine)
 client.close
 
 puts "Temperature is: #{temperature}"

Time for a quick check before we move on. Run your program like this:

 $ jruby temperature_taker.rb

Once you see the Waiting... prompt, open a new terminal and send a temperature in the same format the thermometer uses:

=> $ telnet localhost 4444
<= Trying 127.0.0.1...
 Connected to localhost.
 Escape character is '^]'.
=> 48.0
<= Connection closed by foreign host.

As soon as you type the temperature and press Enter, the Ruby program should pick up where it left off, print the temperature, and exit.

Classing Things Up

Now that the basics are working, it’s time to start packaging this code up into a class we can call from other programs. With the same import section at the top, change the body of your code to look like this:

 class TemperatureTaker
  def temperature; @temperature end
 
  def initialize
  @temperature = 0.0 / 0.0 # NaN
  end
 
  def run
  @server = ServerSocket.new 4444
  @client = @server.accept
  stream = @client.getInputStream
  reader = BufferedReader.new InputStreamReader.new(stream)
  @temperature = Double.parseDouble(reader.readLine)
  @client.close
  end
 end

The body of the run function is nearly the same code as before. The only difference is that we’ve changed a few local variables into instance variables—these will come in handy when we’re controlling this class from outside. Go ahead and try the class interactively in jirb:

 $ jirb -rtemperature_taker
 irb(main):001:0> tt = TemperatureTaker.new
 => #<TemperatureTaker:0x48cd13 @temperature=NaN>
 irb(main):002:0> tt.run
 => nil
 irb(main):003:0> tt.temperature
 => 48.0

That gets us one temperature. But it’s hardly convenient to call run every time we want to take a reading.

Threads

Let’s wrap the body of the run method in a loop, so that we can keep reading temperatures as long as the server is running:

 def run
  @server = ServerSocket.new 4444
 
  while true do
  @client = @server.accept
  stream = @client.getInputStream
  reader = BufferedReader.new InputStreamReader.new(stream)
  @temperature = Double.parseDouble(reader.readLine)
  @client.close
  end
 end

Now, a call to run will go on forever without returning—at least until the networking APIs throw an exception. If we want to be able to continue with other tasks after we’ve started the server, we can run the server in its own thread. Add this function definition inside the TemperatureTaker class:

 def start
  java.lang.Thread.new(self).start
 end

The start method spins up a new Java thread. The Thread constructor we’re using takes a java.lang.Runnable instance. We haven’t made any mention of this interface in our Ruby class; how is it that this code is allowed to run?

JRuby is doing something nice for us here. Since Runnable requires its implementers to have a run method, and we happen to have a method with that name, JRuby presents our Ruby class as a Runnable to the Thread constructor.

As I mentioned a moment ago, a networking exception will break out of the while loop and allow the run method to finish. This is hardly the most elegant way of shutting down a server, but it’ll do for this minimialistic project. Add a stop method to the class, where you’ll terminate the loop by closing sockets:

 def stop
  @client.close if @client
  @server.close if @server
 end

This class has some data contention in it, by the way. If you call start multiple times, or call stop and start from different threads, you can end up with the server not listening when you thought it was.

Remember the situation in which we’re using this code, though: starting a server once at the beginning of a long-running analysis program, reading a bunch of temperatures, and only shutting it down at the end of the run. Under these circumstances, this particular sin will go unpunished.

Go ahead and try out the new version of your class:

 $ jirb -rtemperature_taker
 irb(main):001:0> tt = TemperatureTaker.new
 => #<TemperatureTaker:0x48cd13 @temperature=NaN>
 irb(main):002:0> tt.start
 => nil
 irb(main):003:0> # Before typing the next line of code,
 irb(main):004:0* # use telnet to send a temperature:
 irb(main):005:0* tt.temperature
 => 48.0
 irb(main):006a:0> tt.stop
 Exception in thread "Thread-1" java/net/PlainSocketImpl.java:-2:...
 => nil

Notice that we’re not calling run directly anymore; we just call start, which returns immediately and lets our main thread continue on to other tasks.

We should expect to see an exception when we call stop, since we’re closing a socket in the middle of an I/O routine. If you like, you can modify the program to rescue the java.net.SocketException and print a message such as Server shutting down.

Running in MATLAB

All we have to do to compile this code into a class file callable from the outside world is to run jrubyc --javac on the source file:

 $ jrubyc --javac temperature_taker.rb

You’re almost ready to use this class from your analysis routine.

To use a Ruby class in MATLAB, you need to give MATLAB the location of the JRuby support libraries. Add the following line to MATLAB’s classpath.txt, and modify the path to point to your JRuby installation’s lib/jruby-complete.jar file:

 /path/to/jruby/lib/jruby-complete.jar

Now, you can launch MATLAB and run your analysis program. Here’s a simple routine that takes ten readings at one-second intervals, then plots them:

 javaaddpath('/path/to/your/project');
 
 tt = TemperatureTaker();
 tt.start();
 
 fprintf('Listening for temperature for 10 seconds\n');
 
 v = [];
 for i = 1:10,
  pause(1);
  v(i) = tt.temperature();
 end
 tt.stop();
 
 plot(v);

If you type quickly, you can enter two or three readings into telnet during that ten-second window. Afterward, MATLAB will show something like the figure.

matlab.jpg

Now, on to Octave. As of this writing, Octave’s Java integration has trouble loading the JRuby support libraries. Is there a technology that will let us compile our TemperatureTaker class in such a way that it doesn’t require any support libraries? As it turns out, there is: Mirah.

On Self-Reliance

Mirah is a programming language that has Ruby’s syntax but none of its dependencies (other than the JVM, of course). Our Ruby example can be turned into a valid Mirah file with just three minor changes:

  1. Remove the line require 'java' from the beginning of the file.

  2. Add the line implements Runnable just inside the TemperatureTaker class definition.

  3. Change the text java.lang.Thread to just Thread inside the start method.

Here’s the completed Mirah file:

 import java.io.BufferedReader
 import java.io.InputStreamReader
 import java.lang.Double
 import java.net.ServerSocket
 
 class TemperatureTaker
  implements Runnable
 
  def temperature; @temperature end
 
  def initialize
  @temperature = 0.0 / 0.0 # NaN
  end
 
  def run
  @server = ServerSocket.new 4444
 
  while true do
  @client = @server.accept
  stream = @client.getInputStream
  reader = BufferedReader.new InputStreamReader.new(stream)
  @temperature = Double.parseDouble(reader.readLine)
  @client.close
  end
  end
 
  def start
  Thread.new(self).start
  end
 
  def stop
  @client.close if @client
  @server.close if @server
  end
 end

Once you have Mirah installed, you can compile the example to a class file using the mirahc command:

 $ mirahc temperature_taker.mirah

Now you can take out the entry you added to MATLAB’s classpath.txt earlier, and run the analysis program as before.

Even better, you can now measure temperatures from Octave as well:

 javaaddpath('/path/to/your/project');
 
 tt = java_new("TemperatureTaker");
 tt.start();
 
 fprintf('Listening for temperature for 10 seconds\n');
 
 v = [];
 for i = 1:10,
  pause(1);
  v(i) = tt.temperature();
 end
 tt.stop();
 
 plot(v);

As you can see, the Octave version is nearly identical; the only difference is the syntax for creating Java objects. To run this example, you’ll have to enable Octave’s Java support on your machine.

Wrapping Up

With just a few lines of code, we’ve written a MATLAB/Octave plugin for our hypothetical networked thermometer. By starting in the JRuby interpreter, we were able to code interactively: type in some Ruby, send a few temperature readings, modify our program while it’s running, and so on.

Building the final plugin in Mirah let us keep the lightweight Ruby syntax without having to bundle the JRuby runtime with our plugin.

Are there times when you’d want to just stay in JRuby, rather than moving to Mirah? Sure—if you’re using third-party Ruby libraries like Rails, it makes sense to just code in Ruby and deploy your program along with the JRuby runtime and libraries.

In the next installment of this series, we’ll do just that.

The source code shown here is available at http://github.com/undees/pragpub. Special thanks to Charles Nutter and Nick Sieger for reviewing this article.

By day, Ian Dees slings code, tests, and puns at a Portland-area test equipment manufacturer. By night, he dons a cape and keeps watch as Sidekick Man, protecting the city from closet monsters. Ian is the author of Scripted GUI Testing With Ruby and co-author of the upcoming Using JRuby.

Send the author your feedback or discuss the article in the magazine forum.