JRuby Bridge Tutorial - Making method invocation less cumbersome

We have shown here how to use a Ruby class from the Java world. The method calls look a bit cumbersome. Reason is that the Java compiler needs to know the signatures of a method, and Ruby is much more relaxed in this respect. A Ruby method can return any object, and due to duck typing can accept any object. This means in Java: Casting, casting, casting,....

This is fine, if you call your Ruby function in only a very few places, or if you really need this flexibility. More often than not, you have very precise ideas of what types go into a Ruby method, and which one comes out. With other words, you want to write a class, which to your application looks like a Java class, but is implemented in Ruby. In this case, the code can be written in a way that using the Ruby class from Java is done in a much more natural way. This pages explains two ways of achieving this: Using abstract classes, and using interfaces.

Using an abstract class to let Java know about the Ruby class

For this example, assume that we want to use on the Java side a class named Kantan with a default constructor and a single method with name shirou. This method should accept two parameters: A String and, for the fun of it, something more complicated: A hash, mapping strings to list of strings. Further, it should return a String.

We start by defining in Java an abstract class with the desired properties:

package kantan;
import java.util.*; // For HashMap and ArrayList
public abstract class Kantan {
    public abstract String shirou(String ichi, HashMap<String,ArrayList<String>> aMap);
}

In Ruby, Java classes are known under the name Java::PACKAGENAME::CLASSNAME, which in our case would be Java::kantan::Kantan. Being an abstract class, we need to provide an implementation. This is done by inheritance:

class KantanImp < Java::kantan::Kantan
    def initialize
      puts "Executing initializer of #{self.class}"
    end
    # Implementation of abstract method
    def shirou(s, m)
        ms = "\n"
        m.each_key do |k|
          ms += k + ' ' + m[k].join(', ') + '. '
        end
        'totemo '+s + ms
    end
end

BTW: Note how easy it is to access the HashMap and their values - lists of strings - from Ruby! Unless you try to be overly fancy, they can be used like the native Ruby types.

For using this class in Java, assume that you have successfully created the bridge, as described here, that your bridge has created (and placed into the ScriptingContainer) a global variable - how to do this has been described earlier -, for instance by $sapporo ||= KantanImp.new, and that you have imported in Java your class definition by import kantan.Kantan;. Also assume that you have already built a nice HashMap in Java, which you want to be handled by your class, for example like this:

// Create HashMap and populate it. This is a bit cumbersome (it's Java, after all)
HashMap> fukuzatsu = new HashMap>();
ArrayList a1 = new ArrayList();
a1.add("helps"); a1.add("sometimes");
fukuzatsu.put("Surrender",a1);
ArrayList a2 = new ArrayList();
a2.add("is futil");
fukuzatsu.put("Resistance",a2);

You would then make available the Ruby object to your Java application by

Object sapporo = (Object)container.get("$sapporo");
Kantan kansapp = (Kantan)sapporo;

Once this is done, your Kantan object can be used in the following, simple way:

String kansapp_res = kansapp.shirou("kantan desu", fukuzatsu);

Using an interface definition to let Java know about the Ruby class

Another possibility is to use a Java interface instead of an abstract class. For this example, let us create an interface IfKantan, containing just one method named yarou, which takes a String and returns another String:

package ifkantan;
import java.util.*;
public interface IfKantan {
    public abstract String yarou(String s);
}

Here to, we provide an implementing class on the Ruby side, but this is now not done by inheritance, but by a mixin, for instance like this:

class IfKantanImp
  include Java::ifkantan::IfKantan
  def initialize
  end
  def yarou(s)
    "jitsu ha #{s}"
  end
end

Aside from this, creating the object in your bridge and using it from the Java side, works exactly the same as described in the previous section:

# Inside bridge
$suntory = IfKantanImp.new

// Java
Object suntory = (Object)container.get("$suntory");
IfKantan ifkansunt = (IfKantan)suntory;
// .....
String ifkansunt_res = ifkansunt.yarou("motto kantan desu");