Forum: JRuby Instantiating and using a JRuby object from Java

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
0fa73332c8e4a3b06ea439fd3f034322?d=identicon&s=25 Ronald Fischer (rovf)
on 2015-11-17 15:47
JRuby will be used inside an existing Java framework. I have a JRuby
class like this:

class JRController
  def initialize(x)
    ...
  end
  def execute
    ...
  end
end

From my Java framework, I need to create a JRController object (passing
a native Java object to it), and then invoke its execute method.

Can this be done, and if yes, how would the Java code look like? I
searched on the Net for examples, where the main program is written in
Java, and JRuby is used within it, but found only solutions where a
JRuby script is executed from the Java side, which does not apply to my
case.

Any help appreciated...
3b8ad006781b1a3ab36dc76e2a05ce9c?d=identicon&s=25 Cris Shupp (cshupp)
on 2015-11-17 18:55
Ronald,

I am assuming that your main application is a Ruby app and you are
integrating it with Java.  I assume this since it appears you are not
interested in having a ScriptingContainer execute a ruby script.

Is this what you want?

Suppose I have a java class:

public class Silly {

  public void go(Runnable r) {
    r.run();

}

I want my java class to call into the land of Ruby:

 class Foo
   include java.lang.Runnable
   def run
   puts "Hi From Ruby"
  end
 end

the Ruby code:

s.go(Foo.new) yields "Hi From Ruby" class as expected


OK now suppose you have the following java interface:

public interface Build {
  Runnable build();
}

And, to the Silly class, I add:

  public void buildAndGo(Build b)
  {
    Runnable r = (Runnable)b.build();
    go(r);
  }


in the land of Ruby we add:

class Faa
 def build
 Foo.new
 end
end


If we have:

s = Silly.new

then we can:

irb(main):018:0> s.buildAndGo(Faa.new)
Hi From Ruby


So java built up a Foo, via Faa, and used Foo's run method.

Cris

Ronald Fischer wrote in post #1179303:
> JRuby will be used inside an existing Java framework. I have a JRuby
> class like this:
>
> class JRController
>   def initialize(x)
>     ...
>   end
>   def execute
>     ...
>   end
> end
>
> From my Java framework, I need to create a JRController object (passing
> a native Java object to it), and then invoke its execute method.
>
> Can this be done, and if yes, how would the Java code look like? I
> searched on the Net for examples, where the main program is written in
> Java, and JRuby is used within it, but found only solutions where a
> JRuby script is executed from the Java side, which does not apply to my
> case.
>
> Any help appreciated...
3b8ad006781b1a3ab36dc76e2a05ce9c?d=identicon&s=25 Cris Shupp (cshupp)
on 2015-11-17 19:05
Also...

I could do this:

irb(main):020:0* s.buildAndGo(->{Foo.new})
Hi From Ruby
0fa73332c8e4a3b06ea439fd3f034322?d=identicon&s=25 Ronald Fischer (rovf)
on 2015-11-18 14:58
Cris Shupp wrote in post #1179316:
> Ronald,
>
> I am assuming that your main application is a Ruby app and you are
> integrating it with Java.  I assume this since it appears you are not
> interested in having a ScriptingContainer execute a ruby script.

Actually no. The 'main' is in Java (it is an already existing Java
application), and new features should be added. Since the interface
between the main application and the new features is pretty small -
basically, a few Java objects need to be created and passed to the new
code, and another Java object is being returned - I was thinking to use
JRuby to implement the new features, instead of writing everything in
Java. The main program can't be made into Java, since we can't touch
this part.

In order to invoke my Ruby code, my Java application needs to somehow
execute a Ruby method and pass the objects. This means that the Java
code also needs to know the class, where this method belongs to.

Let me sketch it in detail. I'm using in this example class methods
instead of instance methods, because in hindsight, the seem to be better
suited for my problem. I have a Ruby class like this:

   class MyRubyClass
     def self.doit(x)
       ....
     end
   end

On the Java Side, this I need to invoke this method:

   MyJavaDataClass input_data = MyJavaDataClass.new;
   my_java_data.loadData;
   MyJavaDataResponse output_data MyRubyClass.doit(my_java_data)

Of course, this doesn't work: Java doesn't know anything about the class
MyRubyClass or the method 'doit'. How would I write the "glue" between
the Java and the Ruby world?

Ronald
3b8ad006781b1a3ab36dc76e2a05ce9c?d=identicon&s=25 Cris Shupp (cshupp)
on 2015-11-18 16:39
OK...

How is this:

We have the following ruby script (foo.rb):

puts "Hi world from the land of Ruby!"

java_import 'foo.DoIt' do |p, c|
  'JDoIt'
end

class MyRubyClass
  include JDoIt

  def doIt(array_list)
    r_val = []
    array_list.each do |s|
      r_val << s + ": Rubified!!"
    end
    r_val
  end
end

#The last line is returned by the scripting container
MyRubyClass.new

-----------END _RUBY

The following java interface:

package foo;

import java.util.List;

public interface DoIt {
  List<String> doIt(List<String> javaData);
}


--------END_JAVA_INTERFACE

The following java class:

package foo;

import org.jruby.embed.ScriptingContainer;
import java.util.*;

public class Toy {

  public void runScript() {
    ScriptingContainer container = new ScriptingContainer();
    DoIt it =
(DoIt)container.runScriptlet(org.jruby.embed.PathType.ABSOLUTE,"C:/Users/Cris/workspace_s/Silly/src/foo.rb");
    List<String> javaData = new ArrayList<String>();
    javaData.add("Hello World!");
    javaData.add("Hi World!");
    javaData.add("Howdy World!");
    List<String> rubifiedData = (List<String>)it.doIt(javaData);
    for (String s : rubifiedData) {
      System.out.println(s);
    }
  }


  public static void main(String[] args){
    Toy t = new Toy();
    t.runScript();
  }

}
-----END_JAVA

The output from this program is:

Hi world from the land of Ruby!
Hello World!: Rubified!!
Hi World!: Rubified!!
Howdy World!: Rubified!!


Make sure to have jruby's complete jar on your build and classpath.


Cris
0fa73332c8e4a3b06ea439fd3f034322?d=identicon&s=25 Ronald Fischer (rovf)
on 2015-11-19 08:47
Cris Shupp wrote in post #1179389:
> OK...
>
> How is this:
[...]
> public interface DoIt {
>   List<String> doIt(List<String> javaData);
> }
[...]
>   public void runScript() {
>     ScriptingContainer container = new ScriptingContainer();
>     DoIt it =
>
(DoIt)container.runScriptlet(org.jruby.embed.PathType.ABSOLUTE,"C:/Users/Cris/workspace_s/Silly/src/foo.rb");
>     List<String> javaData = new ArrayList<String>();
>     javaData.add("Hello World!");
>     javaData.add("Hi World!");
>     javaData.add("Howdy World!");
>     List<String> rubifiedData = (List<String>)it.doIt(javaData);

Well, two questions here:

Your example passes only strings to the Ruby class. In my case, I need
to pass arbitrary Java objects to Ruby. Would this work too, and how
would I have to define javaData? Can it be simply a List<Object>?

Further, I'm surprised that you refer to the Ruby class by mentioning
the file containing the Ruby source code (foo.rb) inside your main
program. I thought we must (can?) compile the Ruby program to Java using
jrubyc, and deliver only 'class' or 'jar' files. While this is not a
strict requirement, I would, if possible, prefer delivering only
compiled code, not source code. Can this be done?

Ronald
3b8ad006781b1a3ab36dc76e2a05ce9c?d=identicon&s=25 Cris Shupp (cshupp)
on 2015-11-19 15:25
Ronald,

You can pass arbitrary java objects.  In my case, I arbitrarily passed
in an array list, and using this implementation I obfuscated that fact:

 def doIt(array_list)
    r_val = []
    array_list.each do |s|
      r_val << s + ": Rubified!!"
    end
    r_val
  end

JRuby can use dark magic to do this, I could just as easily do this:

  def doIt(array_list)
    # r_val = []
    # array_list.each do |s|
    #   r_val << s + ": Rubified!!"
    # end
    # r_val
    r_val = java.util.ArrayList.new
    it = array_list.iterator
    while (it.hasNext)
      n = it.next
      r_val.add(n << ": Still rubified!!!")
    end
    r_val
  end

now I am treating the ArrayList as any old POJO.

Things get dicey if we have overloaded methods in java as Ruby does not
allow this.  If you run into this problem look up java_send.  Here is a
quick example:

irb(main):001:0> a = java.lang.StringBuilder.new
=> #<Java::JavaLang::StringBuilder:0x443118b0>
irb(main):002:0> a.java_send :append, [java.lang.String], "I am Here\n"
=> #<Java::JavaLang::StringBuilder:0x443118b0>
irb(main):003:0> puts a
I am Here
=> nil
irb(main):004:0> a.java_send :append, [java.lang.CharSequence], "I am
not here!\n"
=> #<Java::JavaLang::StringBuilder:0x443118b0>
irb(main):005:0> puts a
I am Here
I am not here!


As far as bundling with a jar...

making the following change in the runScript method:

    //DoIt it =
(DoIt)container.runScriptlet(org.jruby.embed.PathType.ABSOLUTE,"c:\temp\foo.rb");
    DoIt it =
(DoIt)container.runScriptlet(org.jruby.embed.PathType.CLASSPATH,"foo.rb");

and placing foo.rb in a jar file on the classpath will work.

The only way I can think of to completely hide your ruby source code is
to place the string in a java file and call the runScriptlet method with
a string argument.

If I take my original foo.rb and compile it here is the decompiled code:

import org.jruby.Ruby;
import org.jruby.ir.IRScope;
import org.jruby.ir.runtime.IRRuntimeHelpers;

public class foo
{

    public static void main(String args[])
    {
        Ruby ruby = Ruby.newInstance();
        ruby.runInterpreter(IRRuntimeHelpers.decodeScopeFromBytes(ruby,
script_ir.getBytes("ISO-8859-1"), "foo.rb"));
    }

    public static IRScope loadIR(Ruby ruby, String s)
    {
        return IRRuntimeHelpers.decodeScopeFromBytes(ruby,
script_ir.getBytes("ISO-8859-1"), s);
    }

    private static final String script_ir = (new
StringBuilder()).append("\000\000\000\001\000\000\0014\377\377\377\377\016\b\023t\000\000Nt\006\001s\000Nt\005\002_\000?\000Nt\000\003{\000fNl\001h\000t\000\003?\001Gt\000\004f\030f/f\037\000\004to_at\000\004\000t\000\005\024\000\004eacht\000\005\377\377\377\377\377wS\001t\000\006?\005\035\001\001pS\001l\001h\000t\000\007%t\000\007\0232L\025_GLOBAL_ENSURE_BLOCK_\000\b\023t\004\000\001\tl\001i\000\000Nt\006\001s\000Nt\005\002_\0001L\016_CLOSURE_START\000?\002\033\000\001-l\001i\000\001f\030t\004\003\001\033\000\001-l\001i\000\001f\004t\004\004\001\033\000\001%t\004\004\001\001f\030t\004\005\001\026l\001h\001\003[]=\002t\004\003\001t\004\005\001%t\004\005\00131L\025_GLOBAL_ENSURE_BLOCK_\000\020t\004\006\001Wt\004\007\001\002\001t\004\006\001%t\004\007\0011L\007CL1_LBL\000\002\007\006foo.rb\000\b\000\000\001\001h\000\001\001h\000\f\000\020foo.rb_CLOSURE_1\001\b\003\f_CLOSURE_END\001\016_CLOSURE_START\001\007CL1_LBL\001\000\377\000\000\002\000\000\000\000\000\001\001\001i\377\000\000\002\000\000\000\000\000\001\001i\000p").toString();

}


We can see script_ir is private and taunting us.

You could probably write a ruby script that converts ruby scripts to be
member escaped strings in a java file if it is that
important.


Good Luck,

Cris
0fa73332c8e4a3b06ea439fd3f034322?d=identicon&s=25 Ronald Fischer (rovf)
on 2015-11-19 16:04
Thanks a lot for the great example!!!! I will take it as a base for my
work.

I wonder whether there is a good tutorial to learn this kind of things.
I found several JRuby tutorials, but they covered mainly the case where
JRuby is using some Java class, or where a JRuby script is called from
Java without much parameter passing. The JRuby API docs contain example
for certain classes, but the great picture is missing somehow.

Can you recommend a good introductory source for people who already know
Ruby well, know a little bit of Java, and need to learn about the
interaction of those two?

Ronald
3b8ad006781b1a3ab36dc76e2a05ce9c?d=identicon&s=25 Cris Shupp (cshupp)
on 2015-11-19 16:49
Ronald,

https://pragprog.com/book/jruby/using-jruby

That link is for Charles Oliver Nutter's JRuby book.

Charles is essentially "Mr. JRuby".  He will cover having Ruby call into
Java.  Sadly the other direction isn't covered well.  The third time I
read his book I found that one sentence that made me have that 'aha
moment' and I had a java object call into ruby for the first time.

The scripting container stuff...

I downloaded the JRuby source and stared at it until I could get it to
do what I needed it to do, which happens to be very near what you need.

Good Luck,

Cris
This topic is locked and can not be replied to.