JRuby Bridge Tutorial - How to use the bridge

Accessing global variables

As shown in the chapter on bridge creation, creating the brige causes our JRuby script bridge.rb to be executed.

Here are the essential parts of this script:

# Ruby
require_relative 'vpruby'
$kirin ||= Kirin.new
yebisu = "nigaindayo"
Asahi.new

Let's have a look at these lines in detail:

  1. The first line makes available all the classes needed in our script. Actually, vpruby.rb just does in turn a require_relative for the classes Asahi and Kirin.
  2. A Kirin object is created and assigned to the global variable $kirin, provided this hasn't happened yet - executing the script again in the same Ruby Runtime would not create this object again.
  3. A Ruby string is created and assigned to the top-level variable yebisu. (TODO: global variables)
  4. A Asahi object is created and returned as value from this script. This is the return value from establishing the bridge by executing runScriptlet.

As we have seen, the Asahi object was bound on the Java side to an instance of type RubyObject, which we named asahi - it was the explicit result of the bridge code. To access the global Ruby variable $kirin from Java, you have to explicitly fetch it from the container (actually, it is fetched from the Runtime, but the container knows how to do this):

RubyObject kirin = (RubyObject)container.get("$kirin"); // Java

Invoking methods on the Ruby objects using invokeMethod

This is one of the methods in our Ruby class Asahi:

class Asahi
  ...
  def say_something
    ...
    true
  end
end

This method doesn't need parameters and returns a boolean value (which we don't actually need, but from the Java viewpoint, there is always a value returned). A general mechanism to call a Ruby method is using JavaEmbedUtils.invokeMethod:

// Java
import org.jruby.javasupport.JavaEmbedUtils;
import org.jruby.Ruby;
...
    // Assuming that the ScriptingContainer is stored in variable container
    Ruby runtime = container.getProvider().getRuntime();
    try {
      JavaEmbedUtils.invokeMethod(runtime,asahi,"say_something",new Object[0],Class.forName("java.lang.Boolean"));
    } catch(Exception e) {
      out.println("Exception when using JavaEmbedUtils.invokeMethod");
       e.printStackTrace();
    }

The expression new Object[0], i.e. an array of zero objects, denotes that this method doesn't accept parameters. The last parameter denotes the return type. The Ruby classes TrueClass and FalseClass are mapped to the Java type Boolean.

Now to something more interesting: Asahi also contains the following method:

# Ruby
class Asahi
  ...
  def dupstr(s)
    s+s
  end
end

From the Ruby (i.e. duck typing) point of view, this method expects something, which has the '+' operator defined; this could be a number, a string, or any other suitable user-defined type. We have nearly no duck typing in Java (with the exception of template types, but this doesn't apply here), so from the Java side, we need to be more specific. Let's say we intend to call it with a string as parameter. In Ruby, we would invoke it as

asahi.dupstr('Almrausch') # Ruby

In Java, We would call this method as

  Object[] str_arg = new Object[1];
  str_arg[0] = new String("Almrausch");
  String dup = (String)JavaEmbedUtils.invokeMethod(runtime,asahi,"dupstr",str_arg,java.lang.String.class);

(Exception handling, as in the previous example, would here be needed too, but is left out for brevity). We pass one argument to dupstr, so the Object array - in this case called str_arg - contains one element, which is the input parameter. The return value is a string. Note that I used here, just for illustration, a different way to declare the returning class type: java.lang.String.class instead of Class.forName("java.lang.String").

While dupstr, being a Ruby method, returns a Ruby string, invokeMethod "declares" it as returning a Java string. The mapping between the two string types will be performed by the ScriptingContainer.

Invoking methods on the Ruby objects using RubyObject.callMethod

Remember, that our Ruby objects are actually wrapped in Java in an instance of class RubyObject. Another possibility to call a method is using the callMethod method of the RubyObject. Since the RubyObject doesn't know about the thread context it is invoked for, this context has to be passed explicitly. With this approach, the two Asahi-methods defined above, would be called in the following way:

// Java
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.javasupport.JavaEmbedUtils;
import org.jruby.runtime.ThreadContext;
...
  ThreadContext currentContext = asahi.getRuntime().getCurrentContext();
  asahi.callMethod(currentContext,"say_something");
  String dup0 = asahi.callMethod(currentContext,"dupstr",JavaEmbedUtils.javaToRuby(runtime,"Almrausch")).convertToString().asJavaString();

Note that, just for the sake of illustration, a different way of fetching the runtime is used here.

Since many methods accept only zero, one or two parameters, callMethod is overloaded for these simple cases and makes it easier to use. There is of course also a version of this function accepting an arbitrary number of parameters.

callMethod returns an object which implements the interface IRubyObject. This interface provides several convenience functions for conversion in the presence of standard types. For example, there is a method convertToString, which turns it into an instance of type RubyString. This class in turn has a method asString, which turns a RubyString into a java.lang.String.

Note that we don't have to catch an exception when using callMethod.

Invoking methods on the Ruby objects using ScriptingContainer.callMethod

This works similar to invokeMethod, but the function is overloaded to make it easier to use for a small number of arguments (remember that container is a variable of type ScriptingContainer):

  // Java
  container.callMethod(asahi,"say_something",Boolean.class);
  Object dupstr_args[] = new Object[1];
  dupstr_args[0] = "Almrausch";
  String dup1 = container.callMethod(asahi,"dupstr",dupstr_args,java.lang.String.class);

The callMethod defined in ScriptingContainer is a template method, and we don't need any conversion, if the return type is either a Java class or one of the simple Java classes, where JRuby provides a default conversion.

There also exists a (non-template) variant of callMethod, where the return type is not specified:

  // Java
  String dup1 = container.callMethod(asahi,"dupstr",dupstr_args).toString();

As can be seen, we than need to explicitly convert the result to a String.

Returning Java objects from Ruby

We have written a simple Java class HE holding an int, consisting of a constructor and a method to return its content:

// Java
package hostenv;

public class HE {
  private int i;
  public HE(Integer j) { i = j; }
  public Integer get_i() { return i; }
}

On the Ruby side, we have a method aptly named aHEobjectof the Asahi class, which expects an integral number, constructs a HE object, and returns it. Here is how we can drive this from the Java side. First the Ruby code:

# Ruby
include Java
java_import 'hostenv.HE'
class Asahi
  ....
  def aHEobject(n)
    HE.new(n)
  end
end

One way to use this from Java - remember, that we have access Asahi-instance from Java using the variable asahi of type org.jruby.RubyObject

- would go like this:

// Java
import hostenv.HE;
  ...
  HE rHE = (HE)(container.callMethod(asahi,"aHEobject",11));

Note that the native Java int - not even an object! - is transparently converted into a Ruby Fixnum when calling the method aHEobject, and in then again converted into a Java Integer when the HE-constructor is called.