Sponsored Link •
I put several pieces of information together and experimented to fill in the missing bits. When I was done, I had a program that implemented Java interfaces and accessed external classes, as well as core classes.
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!)
The original of this article (along with any updates) is here: http://blogs.sun.com/coolstuff/entry/using_java_classes_in_jruby
First, of course, it's necessary to bring in the module that provides the bridge to the JVM:
Some things to note:
include statement and in the
include_class statement. But they're required in the
require statement, which comes between them. The mnemonic 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.)
As a side-note, wildcards in the
CLASSPATH setting would be cool. But the
requirestatements, at least, should allow for wildcards. Something like that would have saved me a lot of trouble:
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
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
The "magic" prefixes are "magic" for external packages, as well as for core JVM classes. If you forget the prefix, 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()
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:
CLASSPATH (did not work for me. JRuby didn't seem to read the environment)
$RUBY_HOME/lib (haven't tried this)
(also didn't work for me)
-I<directory> on the command line for every directory that contains a jar file.
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:
But not this:
-I entries on the command line, the latter fails with "no such file to load".
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()
Foo does not need an
include_class statement, since it is not explicitly named in the code.)
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 ...
Rob Di Marco's post at Innovation on the Run:
|Eric Armstrong has been programming and writing professionally since before there were personal computers. His production experience includes artificial intelligence (AI) programs, system libraries, real-time programs, and business applications in a variety of languages. He works as a writer and software consultant in the San Francisco Bay Area. He wrote The JBuilder2 Bible and authored the Java/XML programming tutorial available at http://java.sun.com. Eric is also involved in efforts to design knowledge-based collaboration systems.