Want to receive a weekly email containing
the scoop on our new titles along with the
occasional special offer? Just click the button.
(You can always unsubscribe later by editing your
account information).

Give us an email and a password (you can use the password later to log in and
change your preferences). We'll send you a newsletter roughly once a week.

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

whiletruedo

@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.

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:

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

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

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

whiletruedo

@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.

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.