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:
require_relative
for the classes Asahi and Kirin.
$kirin
, provided this hasn't happened yet - executing the
script again in the same Ruby Runtime
would not create this object again.
yebisu
.
(TODO: global variables)
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
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
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.
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.
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.
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
// 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.