Summary
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.
Advertisement
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!)
First, of course, it's necessary to bring in the module that provides the bridge to the JVM:
include Java
Some things to note:
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'
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. 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:
require "some/path*.jar"
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::
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:
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:
Put them in the CLASSPATH (did not work for me. JRuby didn't seem to read the environment)
Put them in $RUBY_HOME/lib (haven't tried this)
For NetBeans on Windows, in JRUBY_EXTRA_CLASSPATH
(also didn't work for me)
Give up and specify the jar files with a full path, as I did in Step #2.
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.)
Nice to see someone else figuring out how to use Java classes in JRuby.
Last month I wrote some experimental JRuby programs and posted them to http://c2.com/cgi/wiki?JayRuby. They provided enough proof-of-concept to convince the hobby XP group I'm in (http://xpnyc.org/) to switch our DupDetector project (http://xpnyc.org/index.php?title=DupDetector) from conventional Ruby to JRuby so that we can implement our "in-house customer's" GUI in Java Swing.
Very cool, Elizabeth. Ruby + Java classes have a great future. In fact, someone with language skills could probably write a language that looked very much like JavaFX Script--and have all the benefits of Ruby, in addition to a DSL for Swing.