Using Java Classes in JRuby

I recently had occasion to write a JRuby script that used Java APIs in the XDocs CMS. I found most of the information I needed scattered around the web (the URLs are listed in the Resources section at the end). I decided to collect the relevant stuff in one place, leaving out the stuff that seemed extraneous, and adding the additional little bits that turned out to be necessary. This post contains the results. (But things may well change, so let me know if there are errors or it needs to be brought up to date!)


Requiring JAR Files 

First, of course, it's necessary to bring in the module that provides the bridge to the JVM:

include Java

Notes:

  • Capitalization is significant.

  • This version generates an error, saying a module name is required but a string has been supplied:
    include 'java'

  • This pre 1.0 form is still out there on some web pages. It doesn't generate any obvious errors, but it doesn't work:
    require 'java'

Note:
Quotes are an error in this include statement and in the include_class statement. But they're required in the require statement, which comes between them. My mnemonic for remembering that is "Quotes are required, not included."

The program then needed to require every jar file that the program eventually used, even if it wasn't directly referenced in the JRuby script.

Many of the jar files I wound up requiring were implicit. Given a code sample like this:
   x = someMethod().someOtherMethod.aThirdMethod()
then every intermediate class had to be required into the code. (Since I was converting a Groovy script, I often didn't know what those classes were.)  But even when those objects accessed classes internally, those classes had to be required into the program so the JVM knew where to find them.

So I kept running the program, getting a missing class error, looking for it in the 30 or 40 jar files that make up the CMS, and then adding a new require statement when I found it. I eventually wrote a script to search the jars. But it was an interesting lesson. The API documents tell me what package a class is in, but it would be delightful if there were a cross-reference to the JAR it's contained in.)

Note:
As Mat Schaffer and Alex MacCaw, pointed out, I could have saved myself a lot of trouble by using Dir to take advantage of wildcards. So while this doesn't work:
   require "/some/path/\*.jar"
This does:
  Dir["/some/path/\*.jar"].each { |jar| require jar }
(Is that cool, or what?) 

Including Class Names and Referencing Them

A lot of the time, requiring the jar files is pretty much all you need to do. JRuby can generally figure out what the type is by inspection (the return value from a method, say), so you don't have to explicitly include those classes. But there are times when you do need to specify the class name in the code (for example, to access a static method). In those cases, you need to add an include_class statement to your code.

When including classes, and when referencing classes in the code, package names that start with java, javax, org, and com are "magic". You can use those package prefixes as you would any other variable. Other packages referenced in the code need to be prefixed with Java::

Note:
Those prefixes are "magic" for external packages, as well as for core JVM classes. (If you forget the prefix when it is needed, JRuby gives you an "invalid method" error, because it assumes that the first part of the package is a method that returns an object.)

You can also set up constants to shorten the paths:

JFrame = javax.swing.JFrame
...
_frame = JFrame.new()

Another cool feature: JRuby has syntax converters that let you invoke Java methods using JRuby conventions so, for example, getThis() in a Java class can be invoked as get_this() in JRuby.

Accessing External Classes 

So far, so good. But somehow I couldn't find quite enough information on the web to access 3rd party classes. After examining the writeups listed in the Resources and doing a lot of experimenting, I was able to make things work by doing the following:

1. Tell JRuby where to find the JAR files.
    Options:

  1. Put them in the CLASSPATH (did not work for me. JRuby didn't seem to read the environment)
  2. Put them in $RUBY_HOME/lib (haven't tried this) 
  3. For NetBeans on windows, in JRUBY_EXTRA_CLASSPATH (also didn't work for me)
  4. Give up and specify the jar files with a full path, as I did in Step #2.
  5. Follow the example Rob Di Marco's example and put -I<directory> on the command line
    for every directory that contains a jar file.

AND
2. Require each jar by name, specifying the full path if the directory it's in hasn't been specified on
    the command line, as in Step #1.

    To my surprise, the CLASSPATH setting in the environment wasn't picked up when the
    JRuby script was running on Solaris. So this worked:
         require "/some/path/MyStuff.jar"
    But not this:
         require "MyStuff.jar"

    Without the -I entries on the command line, the latter fails with "no such file to load".

AND
3. Do an include_class on each class that needs to be named in the code, using a fully
   qualified package name. (Specify the Java:: prefix if the package name doesn't start
   with one of the "magic" packages.)

   So if a static method returns a Foo object, the code will look like this:
   include_class Java::some.package.MyClass
   ...
   x = MyClass.staticGetMethod()

(Note that Foo does not need an include_class statement, since it is not explicitly named in the code.) 

Implementing a Java Interface

As per the Nabble page,
http://www.nabble.com/What-is-the-current-syntax-for-defining-a-class-in-JRuby-that-implements-a-Java-interface--t4277539.html

Include interfaces into the class as though they were modules:

class SomeClass
  include java.lang.Runnable
  include java.lang.Comparable
  def run
...
end
  ...

Or group them together into a single module and include that:

module RunCompare
  include java.lang.Runnable
  include java.lang.Comparable
end

class SomeClass
  include RunCompare
  def run
...
end
...

Resources

Headius:
http://www.headius.com/jrubywiki/index.php/Calling_Java_from_JRuby

Sun:
http://java.sun.com/developer/technicalArticles/scripting/jruby/

Nabble: http://www.nabble.com/What-is-the-current-syntax-for-defining-a-class-in-JRuby-that-implements-a-Java-interface--t4277539.html

Rob Di Marco's post at Innovation on the Run:
http://www.innovationontherun.com/scraping-dynamic-websites-using-jruby-and-htmlunit/

Another post I just now found on the web:

Comments:

Just including an interface is enough to implement it? Could you add an example of an actual implementation, ie. for Runnable?

Posted by Jörn Zaefferer on September 17, 2007 at 04:34 PM PDT #

To actually implement Runnable would look like this

require 'java'

class Foo
include java.lang.Runnable
def run
# Runnable stuff hear
end
end

# To Run
java.lang.Thread.new(Foo.new).start

Anyone looking for another example can check out something I recently wrote about using JRuby to scrape web pages that use JavaScript to create content.

http://www.innovationontherun.com/scraping-dynamic-websites-using-jruby-and-htmlunit/

Posted by Rob Di Marco on September 18, 2007 at 04:41 AM PDT #

Nice writeup, Rob. I hope to use it one day!

Posted by Eric Armstrong on September 18, 2007 at 07:41 AM PDT #

For:

require "/some/path/\*.jar"

consider the following:

Dir["/some/path/\*.jar"].each { |jar| require jar }

Posted by Mat Schaffer on September 19, 2007 at 02:05 AM PDT #

[Trackback] A user asked how to invoke a Java EE 5 Web service from JRuby. This TOTD explains how a simple Metro Web service deployed on GlassFish V2 can be easily invoked from JRuby. Create a simple Web service using...

Posted by Arun Gupta's Blog on October 12, 2007 at 02:37 AM PDT #

When calling a third party java class within a JRuby code, i included following statement.
include_class 'Java::package1.Helloworld'

but, it gives the following error.

javasupport:46: undefined local variable or method `package1' for main:Object (NameError)

needs your help.

thanks!

regards,
buddhika

Posted by Thilina Buddhika on October 16, 2007 at 03:37 PM PDT #

>>Note:
>>Wildcards in the CLASSPATH setting would be cool. But the require >>statements,
>>at least, should allow for wildcards:
>> require "/some/path/\*.jar"
>>Something like that would have saved me a lot of trouble!
Why not use Dir? Dir['/some/path/\*.jar'].each {|r| require r }

Posted by Alex MacCaw on November 02, 2007 at 12:57 AM PDT #

Is it possible to extend a concrete java class or abstract java class? I read somewhere that it has been possible since version 9, but I can't find any examples of this anywhere and my attempts always fail.

Posted by Tommy Barker on December 02, 2007 at 11:26 PM PST #

this code works fine. Hope this resolves your concern.

----------------------------------------------------------------------------

include Java

JFrame = javax.swing.JFrame

class RubyFrame < JFrame
end

frame = RubyFrame.new("test")
frame.setSize(500,500);
frame.pack();
frame.setVisible(true);

Posted by Thilina Buddhika on December 03, 2007 at 05:57 PM PST #

Right, thanks. But what if I want to extend my own class? For my java class I have:

package javamonkeyexamples;

public class HelloWorld1{

public void printSomething(){
System.out.println("this is an example of extending something");
}

}

and for ruby, I have:

include Java
HelloWorld1 = Java::javamonkeyexamples.HelloWorld1

class RubyHello < HelloWorld1
end

something = RubyHello.new

When I try to initialize the ruby code, I get:

java.lang.IllegalArgumentException: cannor add classes to package javamonkeyexamples (NativeException)

Am I doing something wrong?

Posted by tommy on December 09, 2007 at 12:02 PM PST #

Here we are trying to use a third party java class. So we need to tell the JRuby interpreter about the location of that java class. So we need to make a jar with that java class. Then specify about that jar file in your code. So the only line you need to add is the following. After the line 'include java', add the following line

require "/home/some/path/<jar name>.jar"

try this.....

good luck !!

thanks,
buddhika

Posted by Thilina Buddhika on December 09, 2007 at 12:18 PM PST #

Well your way still didn't work. It turned out the problem was with my environment. Either vista or netbeans made my java project read only (I think vista since I was not working in My Documents, Argh!). So when I extend something in ruby, I guess it adds something to the project it is referencing. When I removed the read only on the java project folder, everything worked, both my way and your way. For netbeans, I made my method work by including the java classes in my source folders. I did this by right clicking on the ruby project, then selected properties. I then selected sources on the left hand side of the property window and added the path to the java class I wanted to extend. What a pain....

-Tommy

Posted by Tommy on December 11, 2007 at 08:30 PM PST #

Hello,
I have got problem calling Java from JRuby. I posted the problem here http://www.ruby-forum.com/topic/169181#new . Am I doing something wrong?

Best regards

Posted by flash on October 26, 2008 at 07:27 AM PDT #

hi8

Posted by sasi on August 25, 2009 at 08:11 PM PDT #

i have made a ruby application on netbeans 6.5 in which i simply want to access data/ methods from my java class.i created the jar and added it to the library of the appln..
the code is ::
..........................
include Java
require 'C:\\Documents and Settings\\admin\\My Documents\\NetBeansProjects\\RubyApplication1\\myclasses.jar'
include_class Java::Eg1
..........................
but gives the following error-
C:/Program Files/NetBeans 6.8/ruby2/jruby-1.4.0/lib/ruby/site_ruby/shared/builtin/javasupport/core_ext/object.rb:46:in `include_class': `to_a' did not return Array (TypeError)
from C:\\Documents and Settings\\admin\\My Documents\\NetBeansProjects\\RubyApplication1\\lib\\main.rb:13

please help

Posted by vrushali on January 16, 2010 at 02:43 AM PST #

This is a very good blogs for those who face any problem in java programming, i follow it regulaly..
thanks for your efforts.

[url="http://www.itcareer.co.in"]itcareer[/url]

Posted by it career on February 10, 2010 at 12:05 PM PST #

Post a Comment:
  • HTML Syntax: NOT allowed
Search

Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today