JRuby: Java and Ruby together

0
371

It may happen that in the middle of a porting of a Java-to-Ruby application one realizes the lack of a library binding. The short solution is to give up, that geek is to start to write the library port, the practice is to continue using the Java library with JRuby.

In this article, we will see how to use Java classes within Ruby applications using the JRuby implementation. The easiest way to try it is to download a recent version of NetBeans. The IDE of the Sun uses by default, to support the Ruby language, just JRuby.

Java classes in Ruby

Let’s start with a simple example that shows how to use Java classes, such as the class Random, directly from a Ruby application. From Netbeans we create a new Ruby project and choose JRuby as “Ruby platform”.

Select the JRuby platform
Select the JRuby platform

Once this is done we can start writing the code including the Java module

<pre class=”brush: php; html-script: true”>
includes Java
</pre>

and instantiating an object of the class java.util.Random

<pre class=”brush: php; html-script: true”>
rnd = java.util.Random.new
</pre>

We can now use Random methods to generate random numbers, for example:

<pre class=”brush: php; html-script: true”>
puts rnd.nextInt
puts rnd.nextLong
puts rnd.nextFloat
puts rnd.nextDouble
puts rnd.nextGaussian
</pre>

Alternatively, the directive can also be used import

<pre class=”brush: php; html-script: true”>
import java.util.Random
rnd = Random.new
</pre>

or use include_classthat also allows you to indicate aliases , for example

<pre class=”brush: php; html-script: true”>
include_class ‘java.util.Random’ do | pkg, name |
“JRandom”
end
</pre>

in the latter case, a type object java.util.Randommust be created using the nameJRandom

<pre class=”brush: php; html-script: true”>
rnd = JRandom.new
</pre>

Aliases are especially useful when there is a conflict between Ruby class names and Java names. An additional mechanism is to create an ad-hoc module and useinclude_package

<pre class=”brush: php; html-script: true”>
module JavaUtil
include_package ‘java.util’
end

rnd = JavaUtil :: Random.new
</pre>

In the example the module JavaUtilcontains the whole package java.utiland not just the class Random. A little curiosity, the Java names are automatically converted in Ruby style, for example you can rewrite the first example in this way:

<pre class=”brush: php; html-script: true”>
includes Java
rnd = java.util.Random.new

puts rnd.next_int
puts rnd.next_long
puts rnd.next_float
puts rnd.next_double
puts rnd.next_gaussian
</pre>

Paths are also converted to Ruby style, for example the following two paths are equivalent:

<pre class=”brush: php; html-script: true”>
java.util.Random # Java style Java
:: JavaUtil :: Random # Ruby style
</pre>

that is, the points are removed, the CamelCase style is used for the names and everything is put in the Java package. A more explanatory example is the one related to the ParserFactory class:

<pre class=”brush: php; html-script: true”>
org.xml.sax.helpers.ParserFactory # Java style Java
:: OrgXmlSaxHelpers :: ParserFactory # Ruby style
</pre>

JRuby: Java and Ruby together

There are also utility methods like java_classand java_kind_of?that respectively return the Java class name of an object and check the type of an object. For example the code

<pre class=”brush: php; html-script: true”>
includes Java

rnd = java.util.Random.new

puts rnd.class
puts rnd.java_class

puts rnd.java_kind_of? (Java :: JavaUtil :: Random)
puts rnd.java_kind_of? (java.util.Random)
</pre>

will output the name of the Ruby class of rnd, the name of the Java class and confirm the type:

<pre class=”brush: php; html-script: true”>
Java :: :: Random JavaUtil
java.util.Random
false
false
</pre>

Extend a Java class into JRuby

You can also add new methods to the imported Java classes. Returning to the example just shown, let’s add a method nextPositiveIntto the class Random.

<pre class=”brush: php; html-script: true”>
includes Java
import java.util.Random

class Random
def nextPositiveInt
self.nextInt.abs
end
end

rnd = Random.new
puts rnd.nextPositiveIn
</pre>

In this way, type objects Randomwill also have the useless method nextPositiveIntwhich, as can be seen from the name, will return a random integer always positive number.

The exceptions

The exceptions generated by Java can be intercepted by JRuby through the exception NativeException. This allows you to manage all exceptions directly from Ruby, a simple example is the following:

<pre class=”brush: php; html-script: true”>
includes Java

begin
Java.Lang.Class.forName ( “ClasseInesistente”)
rescue NativeException => e
puts “Native exception: # {e.cause}”
end
</pre>

The output will be of the type:

<pre class=”brush: php; html-script: true”>
Native exception: java.lang.ClassNotFoundException: Existing class
</pre>

The type of exception is obtained through the method cause, for all the details the method is useful backtrace. By including the individual Java classes that implement the exceptions, you can define the specific exception you want to capture. The example seen above therefore becomes

<pre class=”brush: php; html-script: true”>
includes Java
include_class ‘java.lang.ClassNotFoundException’

begin
Java.Lang.Class.forName ( “ClasseInesistente”)
rescue ClassNotFoundException => e
puts “ClassNotFoundException: # {e.message}”
end
</pre>

This is definitely the most natural way to handle Java exceptions.

JRuby: Java and Ruby together

A real example

Let’s now see a real example to better understand the potential of JRuby. Suppose we need to view data collected in real time from a Ruby application and want to use the open source library LiveGraph, which only provides Java libraries.

The only viable solution at this point is to bring our application into JRuby and then directly use the Java libraries.

After having set JRuby as a platform and included in the JRuby Classpath the necessary libraries that can be downloaded from the LiveGraph site, we can include the necessary classes through include_class

<pre class=”brush: php; html-script: true”>
require ‘java’

include_class ‘org.LiveGraph.dataFile.write.DataStreamWriter’
include_class ‘org.LiveGraph.dataFile.common.PipeClosedByReaderException’
include_class ‘org.LiveGraph.LiveGraph’
include_class ‘com.softnetConsult.utils.sys.SystemTools’
include_class ‘java.lang.System’
</pre>

At this point we just have to write the code necessary to view the data collected

<pre class=”brush: php; html-script: true”>
class LiveGraphDemo
def initialize
lg = LiveGraph.application
lg.execStandalone

out = lg.updateInvoker.startMemoryStreamMode
if (out.nil?)
puts “Could not switch LiveGraph into memory stream mode.”
lg.disposeGUIAndExit
end

out.setSeparator ( “;”)
out.addDataSeries (“Sensor 1”)
out.addDataSeries (“Sensor 2”)

CollectData
writeData

out.close
lg.disposeGUIAndExit
end
end
</pre>

The example methods collectDataand writeDatawill take care of collecting the data directly from sensors and passing them to LiveGraph for viewing using out.setDataValueand out.writeDataSet.

LiveGraph
LiveGraph

In addition to including them in the JRuby classpath you can use the classes contained in a JAR file directly from the application using require, for example assuming that our libraries are contained in the directory java_libjust write

<pre class=”brush: php; html-script: true”>
require ‘java_lib / LiveGraph.2.0.beta01.Complete.jar’
require ‘java_lib / SoftNetConsultUtils.2.01.slim.jar’
</pre>

and then include the individual classes as seen before.

In this case we used a relative path, but absolute paths can also be used, for example<pre class=”brush: php; html-script: true”>
require ‘/usr/lib/ooo3/basis3.0/program/classes/agenda.jar’
</pre>

By including JAR files in this way, JRuby will add them to the classpath dynamically.

JRuby: Java and Ruby together

Ruby from Java

We conclude by taking a look at the solution to the inverse problem: running Ruby code from a Java application. There are several mechanisms that allow this behavior, we see the most immediate: directly insert JRuby in the Java application.

To do this, just use JavaEmbedUtils which provides the methods to create an instance of the JRuby runtime.

<pre class=”brush: php; html-script: true”>
import java.util.ArrayList;
import org.jruby.Ruby;
import org.jruby.javasupport.JavaEmbedUtils;

public class RubyInJava
{
public static void main (String [] ARGV)
{
Ruby runtime = JavaEmbedUtils.initialize (new ArrayList ());
runtime.evalScriptlet (“puts ‘Hello World'”);
}
}
</pre>

JavaEmbedUtils.initialize(), which deals with initializing and creating an instance of JRuby, takes as a subject a list of paths to be added to the Ruby load path, in our case we do not add anything by passing it an empty list. Ruby code is executed through the method evalScriptlet().

Another example. We resume the extension made to the Random class and bring it to Java:

<pre class=”brush: php; html-script: true”>
import java.util.ArrayList;
import org.jruby.Ruby;
import org.jruby.javasupport.JavaEmbedUtils;

public class RubyInJava
{
public static void main (String [] ARGV)
{
Ruby runtime = JavaEmbedUtils.initialize (new ArrayList ());

String script = “require ‘java’ \ n” +
“import java.util.Random \ n” +
“class Random \ n” +
“def nextPositiveInt \ n” +
“self.nextInt.abs \ n” +
“end \ n” +
“end \ n” +
“rnd = Random.new \ n” +
“puts rnd.nextPositiveInt \ n”;

runtime.evalScriptlet (script);
JavaEmbedUtils.terminate (runtime);
}
}
</pre>

All we did was change a Java class using Ruby code in a Java application. Even if the description is from seasickness, it is a mechanism that can be useful in some cases. Note that the added method will only be visible in the JRuby context.

We close with a note. The problem: every call to JavaEmbedUtils.initialize () creates a new instance of the runtime. The solution: set the property jruby.runtime.threadlocaltotrue

<pre class=”brush: php; html-script: true”>
System.setProperty (“jruby.runtime.threadlocal”, “true”);
</pre>

in this way it is possible to reuse the same runtime within a single thread. To access the runtime instance it should be used Ruby.newInstance()that returns a type object org.jruby.Ruby.

Conclusions

Although the solution of mixing two languages is not very elegant, in many cases it is the only viable solution. From the point of view of developers Ruby is also a way to get around a historical limit of language: the lack of stable and mature libraries.

LEAVE A REPLY

Please enter your comment!
Please enter your name here