Ruby for the Java world

Script your Java applications and efficiently reuse your Java libraries with this dynamic language

1 2 Page 2
Page 2 of 2

JRuby: Ruby in a Java world

As a Java programmer, you won't want to use Ruby in production until you can get it to interact with existing Java applications and libraries, which hold within them a tremendous variety of essential functionality. JRuby, an open source Ruby interpreter for the JVM, simplifies Ruby-Java integration. You can call Java libraries from Ruby, script Java applications with an embedded interpreter, or even use Ruby libraries from Java. Exactly the same Ruby code runs in JRuby and the standard Ruby interpreter, except where Ruby code calls into native (C-coded) or Java libraries.

The JVM is often contrasted with .Net's multilanguage Common Language Runtime as supporting only a single language. But in fact, the JVM executes not only Java, but also Python, JavaScript, Groovy, Scheme, and many other languages, which means that where necessary, Ruby code can interact with these languages as well. (See "Programming Languages for the Java Virtual Machine" by Robert Tolksdorf.)

As of mid-July 2006, JRuby remains in prerelease mode (version 0.9). But it's catching up rapidly: a team of volunteers has released five versions since January 2005. JRuby's maturity is continually evaluated with a test suite that calibrates it against the standard interpreter, and it now passes more than 90 percent of the tests plus provides basic support for Ruby on Rails.

To try JRuby, be sure that Java SE 5 is installed and that JAVA_HOME is set. (Java Runtime Environment 1.4 was supported in JRuby 0.8.3 and below; it will be supported from the next patch release after 0.9.) Download the compressed file from the project's page and uncompress it. Set the JRUBY_HOME environment variable to the JRuby base directory. You can experiment interactively with jirb in the bin directory. For most purposes, you'll use the jruby interpreter—create a file and pass its name as a parameter to the jruby batch/shell script in directory bin.

In addition to running ordinary Ruby code, you can also use JRuby to construct Java objects, call Java methods, and inherit from Java classes. A Ruby class can even implement Java interfaces—necessary for statically calling Ruby methods from Java.

To initialize the libraries used for accessing Java from Ruby, start with require "java". Then specify the Java classes to use with the include_class method, for example, include_class "javax.jms.Session". You can include an entire Java package into a Ruby module with include_package. Like Java's wildcard package-import statement, it's advisable to avoid the namespace pollution of include_package; in JRuby, there is the additional penalty of a performance hit as the interpreter searches all packages for the desired class. Stick to include_class where possible.

The names of many Java standard classes overlap with the names of Ruby classes. To resolve collisions, pass a code block into the include_class function, returning a new name for the Java class, and JRuby will use this as an alias (see Listing 4).

Listing 4. Include a Java class with clashing name

 require "java"
# The next line exposes Java's String as JString
include_class("java.lang.String") { |pkg, name| "J" + name }
s = JString.new("f")

Alternatively, you can create a Ruby module that includes the Java class definitions, but in a separate namespace. For example:

Listing 5. Java module importing multiple Java classes

 

require "java" module JavaLang include_package "java.lang" end

s = JavaLang::String.new("a")

What's JRuby good for?

Dynamic languages like Ruby are most commonly used for specialized areas such as gluing other systems together; JRuby takes on this role in the Java world. For example, JRuby can pull data from one system, transform it and insert it into another. When the requirements change, modifying a JRuby script is as easy as changing a configuration file, thereby avoiding the complex compile-and-deploy cycle of Java integration code.

In addition to calling Java from Ruby, you can call Ruby from Java, making your application scriptable. With JRuby's minimal syntactic overhead, you can create an easy-to-use domain-specific language for users to work with (see "Creating DSLs with Ruby" by Jim Freeze). For example, a gaming engine's scripting system can present Ruby classes describing characters, vehicles, and other game entities.

Moreover, with Ruby's dynamism, users can change definitions of scriptable classes. The Ruby objects allow direct method access to state and behavior. With Java, on the other hand, you typically pass around a Map with user-configurable keys, lacking the full functionality of objects.

A Ruby script is like a souped-up configuration file. Configuration for Java applications typically comes in an XML or properties file, but these are limited only to parameters decided on at development time. With a Ruby script fed to your scripting system either from a text file or from an embedded editor, the user can freely customize behavior wherever you choose to place a scripting hook. In this way, Ruby combines configuration with behavior, providing the functionality of Java plug-in APIs, but without a Java IDE or a compiler, and sparing the additional steps of building and deploying jar files.

For example, a user-provided script can hook into an application's management events to filter for certain suspicious conditions, then send a notification to a system administrator and log into a special security-issues database, or a start-up script can purge old files and reinitialize custom datastores. Likewise, many rich clients allow users to change the position of menus and toolbars—with a JRuby hook, a user's new menu items trigger any behavior the user wants.

For convenience in scripting Java applications, the Bean Scripting Framework (BSF) provides a standard interface between the JVM and multiple dynamic languages, including Ruby, Python, BeanShell, Groovy, and JavaScript; Java Specification Request (JSR) 223, the successor of BSF, will be a standard part of Java 6. The Java code can send variables into the JRuby namespace; JRuby can manipulate these Java objects directly or return a value back to Java. With the BSF and JSR 223, the syntax for interoperation between Java and any scripting language is the same. Listing 6 shows a basic example of BSF use with Ruby, and the full code is in the online samples under the directory bsf_example. Note that BSF does not include JRuby support out of the package; but simple instructions for adding it are available in the JRuby documentation.

Listing 6. Embed a Ruby interpreter

 ...
// JRuby must be registered in BSF.
// jruby.jar and bsf.jar must be on classpath.
BSFManager.registerScriptingEngine("ruby",
    "org.jruby.javasupport.bsf.JRubyEngine",  new String[]{"rb"});
BSFManager manager = new BSFManager();
// Make the variable myUrl available from Ruby.
manager.declareBean("myUrl", new URL("http://www/jruby.org"), URL.class);
// Note that the Method getDefaultPort is available from Ruby
// as getDefaultPort and also as defaultPort.
// The following line illustrates the combination of Ruby syntax
// and a Java method call.
String result = (String) manager.eval(
    "ruby", "(java)", 1, 1, "if $myUrl.defaultPort< 1024 then " +
    "'System port' else 'User port' end");
...

It is also possible to instantiate a JRuby interpreter directly from Java, but as this ties your Java code to Ruby-specific Java wrapper classes, it's best to stick with BSF/JSR 223. (An upcoming release of JRuby will also allow direct embedding of the interpreter with cleaner encapsulation of Ruby objects, without the need for BSF or JSR 223.)

Limitations

It's important to remember that JRuby is still in prerelease mode and still has some limitations, which will be fixed before the 1.0 release.

In particular, a Ruby class can't extend abstract Java classes. Unfortunately, to get around this limitation, you can't simply create a concrete Java subclass, implementing abstract methods with dummy methods, which the Ruby class then extends. This is because of a second limitation in Ruby/Java inheritance in current prerelease versions of JRuby: Java code cannot polymorphically call a Ruby-coded method that overrides a (concrete) Java method.

The limitations make the use of Swing especially difficult. For example, it's impossible to extend AbstractTableModel to take advantage of the functionality that it adds to the TableModel interface. You can work around this by converting inheritance to delegation: Extend the abstract class with a concrete Java "shim" class that delegates to an object of interface type, where the interface includes all the abstract Java methods. A Ruby class implements the delegate interface. Though the wordiness of this approach obviates the usual advantages of JRuby, it does offer a workaround for this limitation and provides flexibility where it is needed: in the subclass functionality (see table_example in attached code listings).

Ruby comes with standard libraries that provide a wide range of functionality. Those that do not use native code are included in the JRuby release. The JRuby team is gradually porting most libraries, although some that are a thin layer over native code, like the GUI library Tk, will probably not be ported. Java's standard libraries, which are included, provide all necessary functionality.

JRuby now provides basic support for Ruby on Rails in the WEBrick Web server; the next major milestone is full support in any servlet container. RoR support will let programmers combine the ease of the Web framework with the vast array of existing Java libraries, while also validating JRuby as an alternative Ruby interpreter.

Java SE 5 is required for JRuby 0.9 because of a bug, but JRE 1.4 will be supported in the next release.

The JRuby team has so far prioritized correctness over performance; optimization is a top priority for the 0.9.x releases.

Example

The example jms_example.rb (located in the source code that accompanies this article) illustrates the use of JRuby at greater length. It shows how to transform messages from one class structure to another, simulating software that integrates two differently defined distributed game engines. The code uses advanced functional and metaprogramming techniques to transform XML messages of any type as Ruby objects. Comments in the code walk you through the logic.

Achieving this level of configurability in Java is nearly impossible; at best, users can plug in compiled code. But in Ruby, the dynamic code is as natural and easy to use as any statically defined code.

Ruby and the future of Java

Ruby has a lot to teach Java programmers. The Ruby on Rails framework shows how simple it can be to develop a Web application; with JRuby, RoR will soon be able to reuse existing Java functionality. JRuby is set to join Jython, JavaScript, and other dynamic languages in the scripting of Java applications. Developers who want to keep their skills up to date should learn dynamic languages, which are likely to take over more and more domains in application development. Even when not using dynamic languages, Java developers can benefit from the insights Ruby gives into concepts like functional programming and metaprogramming.

My thanks to Charles O. Nutter and Thomas E. Enebo, the leaders of the JRuby project, for their valuable comments on this article and for creating JRuby. Responsibility for any flaws remains with me.

Joshua Fox (http://www.joshuafox.com) is a software architect based in Israel. Now working at Mercury Interactive on the BTO product suite, his previous experience includes commercial ontological systems, as well as high-performance Internet collaboration servers.

This story, "Ruby for the Java world" was originally published by JavaWorld.

Copyright © 2006 IDG Communications, Inc.

1 2 Page 2
Page 2 of 2
How to choose a low-code development platform