O'Reilly logo

JRuby Cookbook by Justin Edelson, Henry Liu

Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

Start Free Trial

No credit card required

Chapter 1. Getting Started

Introduction

JRuby is an open source implementation of the Ruby programming language for the Java Virtual Machine (JVM). It allows Ruby applications to be run within a Java Virtual Machine and interface with libraries written in either Java or Ruby. Although the JRuby project was initiated in 2001, interest in JRuby has grown significantly over the last few years, reflecting an overall growth in interest in Ruby sparked by the success of the Ruby on Rails framework. Sun has contributed to JRuby’s success by employing members of the core development team and providing support for JRuby in the NetBeans development environment, among other efforts. The website for the JRuby project is currently http://www.jruby.org.

Ruby

Ruby is a dynamic object-oriented programming language created by Yukihiro Matsumoto, known by the nickname Matz, in the mid-1990s. Ruby follows a style of versioning similar to the Linux kernel, where an even minor version number indicates a stable release and an odd minor version number indicates a development release. As a result, there are two current versions of Ruby: 1.8.6, released in March 2007, is the current stable release, and 1.9.0, released in December 2007, is the current development release. The standard Ruby interpreter[1] is written in C. There are several alternate implementations of the interpreter, including JRuby, IronRuby (for Microsoft’s .NET framework), and Rubinius. Ruby does not have a formal language specification; however, one is being developed through the wiki at http://spec.ruby-doc.org.

As an object-orientated language, many of the underlying concepts of Ruby will be familiar to Java developers, even if the syntax is not. The biggest exception to this is Ruby’s support for blocks. In Ruby, a block is a grouping of code that gets passed to a method call. The receiving method can invoke the block any number of times and can pass parameters to the block. Support for a similar type of element, a closure, is being contemplated for inclusion in Java 7; there are several competing proposals and it is unclear which proposal, if any, will be adopted. Example 1-1 contains a simple Ruby class demonstrating the two ways of defining a block in Ruby. The former syntax, using braces, is typically used to create a block for a single statement. The latter syntax, using the do and end keywords, is typically used for multistatement blocks.

Example 1-1. Introduction to Ruby blocks
class HelloWorldSayer
    def hello_world
        yield "Hello"
        yield "World"
        yield "from Ruby"
    end
end

sayer = HelloWorldSayer.new
sayer.hello_world { |message| puts message.swapcase }

# or

sayer.hello_world do |it|
    puts it.swapcase
end

Note

The Ruby yield function transfers control to the block argument.

This isn’t to suggest that blocks are the only substantial difference between Ruby and Java, but it is certainly one of the most significant, as block usage is so prevalent within typical Ruby code. For example, outputting the list of numbers between 1 and 10 in Java would look something like the code in Example 1-2. The corresponding Ruby code is shown in Example 1-3.

Example 1-2. Loop in Java
for (int i = 1; i <= 10; i++) {
    System.out.println(i);
}
Example 1-3. Loop in Ruby
1.upto(10) { |x| puts x }

Ruby has an active developer community both online and in local developer groups. The Ruby language website, http://www.ruby-lang.org, has more information about these user groups. A wide array of books about Ruby have been published, perhaps most famously Programming Ruby: The Pragmatic Programmers’s Guide (Pragmatic Bookshelf) by Dave Thomas, Chad Fowler, and Andy Hunt, known as the “pickaxe book” because of its cover, and The Ruby Programming Language by David Flanagan and Yukihiro Matsumoto (O’Reilly).

JRuby

JRuby began its life as a direct port of the C-based interpreter for Ruby 1.6 written by a programmer named Jan Arne Petersen in 2001. For the next few years, it was an interesting project, but had serious performance limitations. Following the release of Ruby 1.8 in 2003 and then the release of the Ruby on Rails web framework in 2004, a significant amount of effort has been put into developing JRuby, especially in the areas of compatibility and performance. In September 2006, Sun Microsystems effectively endorsed JRuby when it hired two of the lead developers, Charles Nutter and Thomas Enebo, to work on JRuby full-time. Since then, a third lead developer, Nick Sieger, has become a Sun employee.[2]

For Sun, JRuby represents an opportunity to expand the prevalence of the Java Virtual Machine. Although the JVM was originally tied very closely to the Java language, the emergence of projects like JRuby, Jython (a Java implementation of Python), Groovy (a scripting language inspired by Ruby), and Scala (a functional/object-oriented programming language) have proved that the JVM can host a wide variety of languages. This trend culminated with the development of Java Specification Request (JSR) 223, Scripting for the Java Platform. JSR 223 defines a standard API (Application Programming Interface) for scripting languages to integrate with the JVM. Implementations of the JSR 223 API are available for 25 different languages from https://scripting.dev.java.net. This API will be discussed further in Chapter 3.

For users, JRuby represents a different opportunity: to take advantage of the power of a dynamic language such as Ruby while still being able to leverage existing Java libraries and application servers. This area will be explored in the first two chapters.

With the release of JRuby 1.1 in April 2008, JRuby has closed the performance gap with the C Ruby interpreter and is in many cases faster. In terms of compatibility, the JRuby project strives to duplicate the behavior of the standard Ruby interpreter whenever possible, even at the expense of consistency with Java. Most of the core Ruby classes are included, as is much of the standard Ruby library, the RubyGems package management system, RDoc documentation support, and the Rake build system. Despite these efforts at compatibility, there are some areas where JRuby deviates from behavior exhibited by the C Ruby interpreter. The most visible example of this is how JRuby handles threads. In this case, however, JRuby is actually ahead of the standard Ruby interpreter in that Ruby 2.0 is expected to have a similar threading model to what JRuby already supports.

This chapter goes through the JRuby installation process, some core Java/Ruby integration information, and finally a variety of IDE integration options.

Installing JRuby

Problem

You want to install JRuby.

Solution

Download and extract the latest binary release from the JRuby website, http://www.jruby.org. Add the bin directory to the PATH environment variable.

Discussion

Windows

The JRuby website makes binary releases available in both ZIP and TGZ file formats. Since Windows XP, Windows operating system software has included support for extracting ZIP files. Commercial and open source software packages are available that include support for TGZ files, such as WinZip (http://www.winzip.com), 7-Zip (http://www.7-zip.org), and IZArc (http://www.izarc.com).

It is not necessary to install JRuby in any particular location on your computer. My preference is to install Java libraries and executables in subdirectories of C:\java. The results of extracting the binary for the latest release at the time of this writing, 1.1, can be seen in Figure 1-1.

After extraction, JRuby is ready to be used. The simplest way to see JRuby in action is by running jirb, JRuby’s version of Interactive Ruby (irb). Like irb, jirb allows you to execute Ruby statements and immediately see the results of each statement. JRuby includes both command-line and GUI versions of jirb in the bin directory. The command-line version, seen in Figure 1-2, can be run by executing bin\jirb.bat; the GUI version, seen in Figure 1-3, can be run by executing bin\jirb_swing.bat. In both figures, some trivial Ruby code has been executed. You can see that both the output of the puts method (Hello World) and its result (nil) have been output.

Warning

If you launch either jirb.bat or jirb_swing.bat from Windows Explorer and all you see is a black window appear and then disappear quickly, the likely cause is that you do not have the JAVA_HOME environment variable set, or the value of this environment variable is incorrect. To set environment variables in Windows, use the System control panel’s Advanced tab. JAVA_HOME should point to the directory in which you have Java installed.

Extracted JRuby binary build
Figure 1-1. Extracted JRuby binary build
Command-line jirb
Figure 1-2. Command-line jirb
jirb GUI
Figure 1-3. jirb GUI

You can also test JRuby from the command line by using the -e (evaluate) option:

C:\java\jruby-1.1\bin\jruby -e "puts 'Hello World'"

To avoid having to retype the full path to JRuby’s bin directory, add it to the PATH environment variable by opening the System control panel and clicking on the Advanced tab. On the Advanced tab, click the Environment Variables button. This will bring up the Environment Variables dialog, seen in Figure 1-4. Using the New and Edit buttons for System variables, add a JRUBY_HOME environment variable and also prepend the value %JRUBY_HOME%\bin to the PATH environment variable. You could also simply prepend the full path to the bin directory to PATH, but using a separate environment variable makes upgrading a bit easier.

Windows Environment Variables
Figure 1-4. Windows Environment Variables

Once you have configured the environment variables, click OK. These changes will only be reflected in newly opened windows (something to keep in mind if you have any command-line windows open). After adding the bin directory to your PATH, you can then simply run the test shown previously by executing:

jruby -e "puts 'Hello World'"

Linux and Mac OS X

The JRuby website makes binary releases available in both ZIP and TGZ file formats. Although most Linux distributions and OS X include utilities for extracting both types of files, TGZ files are preferable because files extracted from them include permission settings, something that is not the case with ZIP files.

Note

The JPackage Project at http://www.jpackage.org has a release available in RPM format. At the time of this writing, JPackage did not have the latest JRuby version available, but that may not be the case when you’re reading this.

If you have root privileges on the system where you want JRuby installed, you should install JRuby based on whatever standards already exist. This could mean installing JRuby in /usr/local/jruby, /usr/share/jruby, or /opt/jruby, among other options. Based on OS X conventions, Mac users should install in /opt/local/jruby or /usr/local/jruby. If you do not have root privileges, then you likely need to install it inside your home directory, such as ~/jruby. By default, the JRuby releases extract to a directory containing the version number, so we’ll simply create a symbolic link between ~/jruby and ~/jruby-1.1. This will facilitate upgrades later:

$ cd ~
$ tar -xzf jruby-bin-1.1.tar.gz
$ ln -s jruby-1.1 jruby

Set JRUBY_HOME to the installation directory and add JRuby’s bin directory to the PATH environment variable; add lines to the ~/.profile similar to those in Example 1-4.

Example 1-4. Example .profile file that adds JRuby to the PATH environment variable
export JRUBY_HOME=~/jruby
export PATH=$JRUBY_HOME/bin:$PATH

Once the bin directory has been added to your PATH, you can test the install by running a simple Ruby script:

$ jruby -e "puts 'Hello World'"
Hello World

Warning

You must add JRuby’s bin directory to your PATH in order to use any of the command-line utilities included with JRuby, including jirb.

Managing Packages with RubyGems

Problem

You want to install Ruby on Rails or other Ruby packages for use with JRuby.

Solution

Use the RubyGems support built into JRuby. Once JRuby has been installed, you can immediately start using RubyGems to manage Ruby packages by running the gem script included in JRuby’s bin directory. To install a package, run:

$ gem install packagename

For example, to install the Ruby on Rails web framework, use:

$ gem install rails

Discussion

RubyGems is the standard package management and distribution system for Ruby packages. There are thousands of packages, referred to as gems, available through the default RubyGems repository at http://gems.rubyforge.org. Although some gems are specific to the C Ruby implementation or JRuby, most are compatible with any Ruby implementation.

Common RubyGems commands include install, query, update, uninstall, and rdoc. The full list can be output by using the help command:

$ gem help commands
GEM commands are:

    build             Build a gem from a gemspec
    cert              Manage RubyGems certificates and signing settings
    check             Check installed gems
    cleanup           Clean up old versions of installed gems in the local
                      repository
    contents          Display the contents of the installed gems
    dependency        Show the dependencies of an installed gem
    environment       Display information about the RubyGems environment
    fetch             Download a gem and place it in the current directory
    generate_index    Generates the index files for a gem server directory
    help              Provide help on the 'gem' command
    install           Install a gem into the local repository
    list              Display all gems whose name starts with STRING
    lock              Generate a lockdown list of gems
    mirror            Mirror a gem repository
    outdated          Display all gems that need updates
    pristine          Restores installed gems to pristine condition from files
                      located in the gem cache
    query             Query gem information in local or remote repositories
    rdoc              Generates RDoc for pre-installed gems
    search            Display all gems whose name contains STRING
    server            Documentation and gem repository HTTP server
    sources           Manage the sources and cache file RubyGems uses to search
                      for gems
    specification     Display gem specification (in yaml)
    uninstall         Uninstall gems from the local repository
    unpack            Unpack an installed gem to the current directory
    update            Update the named gems (or all installed gems) in the local
                      repository
    which             Find the location of a library

For help on a particular command, use 'gem help COMMAND'.

Commands may be abbreviated, so long as they are unambiguous.
e.g., 'gem i rake' is short for 'gem install rake'.

See Also

Using Both Ruby and JRuby

Problem

You have Ruby and JRuby installed on the same computer and want to ensure that a Ruby script is processed by the correct interpreter.

Solution

Use the -S command-line argument for the ruby and jruby executables. For example, RubyGems is traditionally invoked with a command like:

gem install rails

Instead, use:

$ jruby –S gem install rails

or:

$ ruby –S gem install rails

Discussion

Popular Ruby packages such as Rake, Ruby on Rails, and RubyGems include their own executable Ruby scripts that most guides, both online and print, instruct you to invoke directly. Whether these scripts run with Ruby or JRuby depends on how you’ve configured the PATH environment variable, which platform you use, and what package is involved. Because there are so many variables, this recipe prescribes using a single, consistent method, passing the script name through the -S command-line argument to either the ruby or jruby executables.

The -S command-line option instructs Ruby and JRuby to load a script file from the PATH. JRuby includes its own copies of the Rake and RubyGems scripts in bin/rake and bin/gem, respectively, but they are verbatim copies of the original scripts. As a result, it doesn’t matter which version of the script you execute, only the interpreter with which you execute it.

This advice is particularly significant in the context of the RubyGems script, gem. To create a new Rails application, you could run either:

$ ruby –S rails sampleapp

or:

$ jruby –S rails sampleapp

and see the same result. However, running:

$ ruby –S gem install rails

and:

$ jruby –S gem install rails

will install the Rails gem in two different locations. You can see this by passing environment to the gem script:

$ ruby -S gem environment
RubyGems Environment:
  - RUBYGEMS VERSION: 1.0.1 (1.0.1)
  - RUBY VERSION: 1.8.5 (2007-09-24 patchlevel 114) [i386-linux]
  - INSTALLATION DIRECTORY: /usr/lib/ruby/gems/1.8
  - RUBY EXECUTABLE: /usr/bin/ruby
  - RUBYGEMS PLATFORMS:
    - ruby
    - x86-linux
  - GEM PATHS:
     - /usr/lib/ruby/gems/1.8
  - GEM CONFIGURATION:
     - :update_sources => true
     - :verbose => true
     - :benchmark => false
     - :backtrace => false
     - :bulk_threshold => 1000
  - REMOTE SOURCES:
     - http://gems.rubyforge.org
$ jruby -S gem environment
RubyGems Environment:
  - RUBYGEMS VERSION: 1.0.1 (1.0.1)
  - RUBY VERSION: 1.8.6 (2008-01-07 patchlevel 5512) [java]
  - INSTALLATION DIRECTORY: /home/justin/jruby-1.1/lib/ruby/gems/1.8
  - RUBY EXECUTABLE: /home/justin/jruby-1.1/bin/jruby
  - RUBYGEMS PLATFORMS:
    - ruby
    - universal-java-1.6
  - GEM PATHS:
     - /home/justin/jruby-1.1/lib/ruby/gems/1.8
  - GEM CONFIGURATION:
     - :update_sources => true
     - :verbose => true
     - :benchmark => false
     - :backtrace => false
     - :bulk_threshold => 1000
  - REMOTE SOURCES:
     - http://gems.rubyforge.org

Sharing RubyGems

Problem

You already have a number of RubyGems installed and want to use those gems from JRuby without reinstalling the gems.

Solution

Set the GEM_HOME environment variable to your existing RubyGems installation location. This value can be seen in the output of gem environment, where it is referred to as the installation directory:

$ ruby -S gem environment | grep -i 'installation directory'
- INSTALLATION DIRECTORY: /usr/lib/ruby/gems/1.8
$ export GEM_HOME=/usr/lib/ruby/gems/1.8
$ jruby -S gem environment | grep -i 'installation directory'
- INSTALLATION DIRECTORY: /usr/lib/ruby/gems/1.8

Discussion

Whereas some RubyGems are implemented entirely in Ruby, many are implemented in a combination of Ruby and C (or, in a growing number of cases, Ruby and Java). Pure-Ruby gems can be installed using either JRuby or C Ruby. However, those implemented in a mixture can only be installed using a compatible interpreter. The list of supported platforms for each interpreter can be seen in the output of gem environment. Because the RubyGems runtime knows this list of supported platforms, it is possible to mix gems supporting different platforms in the same directory; the runtime will select the appropriate libraries.

Referencing Java Classes from Ruby

Problem

You want to write Ruby code that uses one or more Java classes.

Solution

First, you need to tell JRuby that you will be referencing Java classes from your Ruby code. Do this by including an include declaration at the top of your Ruby file:

include Java

The syntax for referencing a specific Java class depends on the package in which the class resides. For packages starting with java, javax, org, and com, you can simply reference the fully qualified class name or use an import statement, as shown in Example 1-5.

Example 1-5. Creating a Java TreeMap from Ruby
# using the fully-qualified class name
map = java.util.TreeMap.new

# using an import statement
import java.util.TreeMap
map = TreeMap.new

For classes that reside in a package that does not begin with java, javax, org, or com, as well as classes in the default package, you need to use the include_class function, as in Example 1-6.

Example 1-6. Referencing a Java class with include_class
include_class 'EDU.oswego.cs.dl.util.concurrent.ConcurrentHashMap'

map = ConcurrentHashMap.new

Note

The include_class function can also handle classes in packages starting with java, javax, org, and com if you don’t want to switch back and forth.

The include_class function can also be used to create aliases in cases where a Java class name conflicts with a Ruby class name. To do this, pass a block to the function. Example 1-7 aliases the Java String class as JString so that it does not conflict with Ruby’s String class.

Example 1-7. Creating an alias to avoid class name conflicts
include Java

include_class 'java.lang.String' do |package,name|
    "JString"
end

p JString.new("A quick brown fox").indexOf("brown")

You can pass multiple class names to the include_class as a list. In this case, you could provide the appropriate alias using a case statement, as seen in Example 1-8.

Example 1-8. Aliasing multiple classes with case
include_class ['java.lang.String','java.lang.Integer'] do |package,name|
    case name
    when "String"
        "JString"
    when "Integer"
        "JInteger"
    end
end

An alternative to this aliasing technique is wrapping a Java package in a Ruby module using the include_package function, as seen in Example 1-9.

Example 1-9. Wrapping a Java package with a Ruby module
include Java

module JavaLang
    include_package 'java.lang'
end

p JavaLang::String.new("A quick brown fox").indexOf("brown")

Discussion

JRuby makes referencing Java classes relatively natural from the perspective of a Java developer. For the most commonly used packages, you can use import just as you would in Java code.

When calling methods on a Java class, JRuby handles some type conversion for you—instances of basic Ruby classes such as FixNum, Float, and String are converted to instances of the corresponding Java classes when passed to Java objects. JRuby includes implementations of the java.util.List and java.util.Map interfaces for handling Ruby Array and Hash objects. Ruby Array objects can also be coerced into Java Array objects by calling the to_java method. Example 1-10 includes a combination of Java and Ruby code, which demonstrates this functionality.

Example 1-10. Ruby to Java type conversion
package org.jrubycookbook.ch01;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import org.jruby.Ruby;
import org.jruby.javasupport.JavaEmbedUtils;

public class PrintJavaClass {

    // Output the class and interface list for a single object
    public String output(Object o) {
        String className = o.getClass().getName();
        List<Class> interfaces = Arrays.asList(o.getClass().getInterfaces());

        return String.format("%s, implements %s\n", className, interfaces);
    }

    // Output the class and interface list for each object in an array
    public String output(Object[] objects) {
        PrintWriter writer = new PrintWriter(new StringWriter());
        for (Object o : objects) {
            String className = o.getClass().getName();
            List<Class> interfaces = Arrays
                    .asList(o.getClass().getInterfaces());

            writer.printf("%s (inside array), implements %s\n", className,
                    interfaces);
        }
        return writer.toString();
    }

    public static void main(String[] args) {
        Ruby runtime = JavaEmbedUtils.initialize(Collections.EMPTY_LIST);
        String script = "@printer = org.jrubycookbook.ch01.PrintJavaClass.new\n"
                + "def output(o)\n"
                + "puts \"#{o.to_s} - #{@printer.output(o)}\"\n"
                + "end\n"
                + "output(1)\n"
                + "output(0.5)\n"
                + "output('string')\n"
                + "output(true)\n"
                + "output([4, 8, 15, 16, 23, 42])\n"
                + "output([4, 8, 15, 16, 23, 42].to_java)\n"
                + "output({ 'NY' => 'New York', 'MA' => 'Massachusetts'})\n";

        runtime.evalScriptlet(script);
        JavaEmbedUtils.terminate(runtime);
    }
}

Note

See Executing Ruby from Java for an explanation of the JavaEmbedUtils class used in Example 1-10.

When executed, this class outputs:

1 - Class is java.lang.Long, implements [interface java.lang.Comparable]
0.5 - Class is java.lang.Double, implements [interface java.lang.Comparable]
string - Class is java.lang.String, implements [interface java.io.Serializable,\
 interface java.lang.Comparable, interface java.lang.CharSequence]
true - Class is java.lang.Boolean, implements [interface java.io.Serializable,\
 interface java.lang.Comparable]
4815162342 - Class is org.jruby.RubyArray, implements [interface java.util.List]
[Ljava.lang.Object;@8b058b - Received an array
In array: class is java.lang.Integer, implements [interface java.lang.Comparable]
In array: class is java.lang.Integer, implements [interface java.lang.Comparable]
In array: class is java.lang.Integer, implements [interface java.lang.Comparable]
In array: class is java.lang.Integer, implements [interface java.lang.Comparable]
In array: class is java.lang.Integer, implements [interface java.lang.Comparable]
In array: class is java.lang.Integer, implements [interface java.lang.Comparable]
NYNew YorkMAMassachusetts - Class is org.jruby.RubyHash, implements\
 [interface java.util.Map]

JRuby provides access to public static methods and variables through the :: operator. Example 1-11 shows how you would access the static methods and variables of the Java Math class.

Example 1-11. Accessing static methods and variables
require 'java'

puts java.lang.Math::max(100,200)
puts java.lang.Math::PI

Converting a Ruby Array into a Java Array

Problem

You need to pass a Ruby array to a method that accepts a Java array of a specific type.

Solution

Call the Ruby array’s to_java method with an argument specifying the component type of the array. For example, creating an array of javax.xml.transform.stream.StreamSource objects would be done like this:

import javax.xml.transform.stream.StreamSource

cnn = StreamSource.new "http://rss.cnn.com/rss/cnn_topstories.rss"
mtv = StreamSource.new "http://www.mtv.com/rss/news/news_full.jhtml"
# Call a transforming Java API. This method would have been declared
# with this signature:
# public String transform(StreamSource[] sources)
p transformer.transform([cnn,mtv].to_java(StreamSource))

Primitives, as well as java.lang.String, have Ruby symbols assigned to them. For example, to create an array of int primitives:

[1,2,3,4,5,6,7,8,9,10].to_java(:int)

Discussion

This JRuby feature is critical for accessing Java APIs. For example, calling a method through Java Management Extensions (JMX) involves passing two arrays to the invoke() method of javax.management.MBeanServer, one of Object instances, storing the method parameters, and one of String instances, storing the method signature. To call invoke() from JRuby, you would do something like this:

brokerName = ObjectName.new('org.apache.activemq:BrokerName=localhost,Type=Broker')
params = ["MyQueue"].to_java()
signature = ["java.lang.String"].to_java(:string)
server.invoke(brokerName, 'addQueue', params, signature)

Adding JAR Files to the Classpath

Problem

You want to reference a Java class which is contained in a JAR file that isn’t already included in your classpath.

Solution

Call Ruby’s require method with the path to the JAR file. This path can be relative to the current working directory:

require 'lib/commons-logging-1.1.jar'

or an absolute path:

require '/opt/java/commons-logging/bin/commons-logging-1.1.jar'

If you are using Windows, this path can have either type of slash:

require 'c:\java\commons-logging-1.1\bin\commons-logging-1.1.jar'
# or
require 'c:/java/commons-logging-1.1/bin/commons-logging-1.1.jar'

Discussion

Although this is an extremely useful feature of JRuby, it should be used with caution, especially if you use absolute paths that are platform- and installation-specific. Relative paths can seem like a better solution, but are actually more limiting, as they are evaluated from the current working directory, not the script’s directory. Yet all is not lost.

An interesting aspect of this feature of JRuby is that the JAR file is added to the classpath dynamically, while the application is running. This allows you to use Ruby’s string interpolation functionality to create absolute paths. Example 1-12 includes a method that creates a path to a JAR file in a local Maven repository.[3]

Example 1-12. Creating a JAR file path dynamically
# Set the HOME environment variable if USERPROFILE is set
ENV['HOME'] = ENV['USERPROFILE'] if (ENV['USERPROFILE'])

def require_from_maven(group,artifact,version)
    maven_path = "#{group}/#{artifact}/#{version}/#{artifact}-#{version}.jar"
    require "#{ENV['HOME']}/.m2/repository/#{maven_path}"
end

Application code could use require to include this script and then use the require_from_maven method to reference a specific JAR file:

require 'require_from_maven'
require_from_maven "commons-logging", "commons-logging", "1.1"

Extending a Java Class in Ruby

Problem

To use a Java API, you need to create a Ruby class that subclasses a Java class.

Solution

Use the standard Ruby superclassing operator < and specify the Java class you want to subclass. Example 1-13 shows a Ruby class that extends the Java Thread class and overrides the run() method.

Example 1-13. Subclassing a Java class in Ruby
include Java

class MyThread < java.lang.Thread
    def run
        puts 'hello world'
    end
end

MyThread.new.start

Discussion

The fact that the same syntax is used to extend both Java and Ruby classes is an important design feature of JRuby, as it furthers the seamless integration between the two languages.

Warning

One notable exception to this recipe is classes that use Java 5 generics. Currently, these cannot be subclassed with Ruby classes.

Abstract Java classes can also be extended by Ruby classes. Examples 1-14 and 1-15 show an example of an abstract Java class and a concrete Ruby class that extends the former. The hello() method, declared abstract in the Java class, is implemented in the Ruby class.

Example 1-14. An abstract Java class
package org.jrubycookbook.ch01;

public abstract class AbstractElement {
    public abstract void hello();

    public void sayHello(int count) {
        for (int i = 0; i < count; i++) {
            hello();
        }
    }
}
Example 1-15. Ruby class that subclasses an abstract Java class
include Java

import org.jrubycookbook.ch01.AbstractElement

class RubyElement < AbstractElement
    def hello
        puts 'hello world'
    end
end

RubyElement.new.sayHello 5

Implementing a Java Interface in Ruby

Problem

To use a Java API, you need to create a Ruby class that implements a Java interface.

Solution

Create your class with method names that match the names in the Java interface. As of version 1.1, JRuby runtime supports the use of duck typing for implementing Java interfaces. Duck typing, seen in many dynamic languages, including Ruby, means that the type of an object is determined based on the methods implemented by the object. Example 1-16 shows this technique in action as a new Java thread by passing the constructor an object that implements the java.lang.Runnable interface. The HelloThread class contains a zero-argument run method that corresponds to the method defined in java.lang.Runnable. JRuby requires no additional type information in the HelloThread class to instantiate the Thread object.

Example 1-16. Ruby implementation of a Java interface
include Java

class HelloThread
    def run
        puts 'hello world'
    end
end

java.lang.Thread.new(HelloThread.new).start

Discussion

There are few situations when duck typing isn’t sufficient and you’ll need to provide additional type information to the interpreter. One case is when a duck-typed JRuby object is passed as an argument to an overloaded Java method. Without additional Java type information, the JRuby interpreter doesn’t definitively know which method to execute. The solution is to use Ruby’s include statement to assign an explicit Java interface to a Ruby class. This provides the JRuby interpreter with enough information about the object to execute the correct method. In Example 1-17, the HelloThread class is assigned the Runnable interface. As a result, JRuby calls the desired exec() method and runnable is output to the console.

Example 1-17. Declaring Java interfaces in JRuby
Balloon.java

public interface Balloon {
    void pop();
}

Bubble.java

public interface Bubble {
    void pop();
}

Child.java

public class Child{
    public void give(Bubble bubble){
        System.out.println("Thanks for the bubble.");
        bubble.pop();
    }
    public void give(Balloon balloon){
        System.out.println("Thanks for the balloon.");
        balloon.pop();
    }
}
main.rb

include Java

class MylarBalloon
    include Java::Balloon
    def pop
      puts 'Oh No!!!'
    end
end

child = Java::Child.new
child.give(MylarBalloon.new)

Because Ruby scripts implicitly create a top-level class, it is not even necessary to define a new class to implement a Java interface. This functionality, seen in Example 1-18, can be especially useful when prototyping and testing.

Example 1-18. JRuby working with Java interfaces—condensed version
include Java

def pop
    puts 'Bang'
end

child = Java::Child.new
child.give(self)

Ruby modules are a natural fit to help implement Java interfaces. In some ways they resemble abstract Java classes, but Ruby modules are different in that a class may include many modules. Example 1-19 shows the use of a module to implement a Java interface and the reuse of this module.

Example 1-19. Implementing a Java interface with a module
include Java

module RunModule
    def run
        1.upto(10) { |i| puts "You're number #{i}" }
    end
end

class HelloThread
    include RunModule
end

java.lang.Thread.new(HelloThread.new).start

JRuby allows you to create an instance of the interface by using the impl method that’s dynamically attached to all Java interfaces. The method accepts a block as an argument that is executed for every function call in the interface. The block defines two arguments: the name of the method in the interface that initiated the block’s execution, and a variable input parameter to accommodate the method arguments. Example 1-20 uses the impl method to define the sorting behavior for a Java Comparator.

Example 1-20. Using JRuby’s impl method
include Java

v = java.util.Vector.new
v.add_element("Lions")
v.add_element("Tigers")
v.add_element("Bears")

java.util.Collections::sort(v, java.util.Comparator.impl do |method, *args|
  case method.to_s
    when "compare"
       args[0] <=> args[1]
    when "equals"
      args[0] == args[1]
  end
end)

v.each do |val|
  puts val
end

Another interesting technique of working with an interface is to use a Ruby block as the input to a method where you would normally use a single-method Java interface. The Ruby block style can be used with nonoverloaded methods that expect to be called with a single argument that is a Java interface. When a block is passed to such a method, the JRuby runtime attempts to generate a proxy object that implements the interface. Overloaded and multiple methods make this process ambiguous and unworkable. Example 1-21 illustrates how this feature can make the Java Swing development significantly more concise.

Example 1-21. Implementing a Java interface with a Ruby block
frame = javax.swing.JFrame.new
frame.set_size 500,200

a = javax.swing.JButton.new("hello")
b = javax.swing.JButton.new("world")

#define the function using a block
a.add_action_listener do |evt|
  puts 'hello'
end

# define the function using a Ruby Proc
p = lambda{ |evt| puts 'world'}
b.add_action_listener &p

frame.add a
frame.add b
frame.set_layout(java.awt.GridLayout.new(1, 2))
frame.show

A Ruby Proc object can also be passed once it is transformed into a Ruby block using the & operator.

Note

Java interfaces that define a single method are sometimes referred to as single abstract method types, abbreviated as SAM types. All of the proposals for adding closures/blocks to Java 7 attempt to make implementation of these types significantly simpler and closer to what JRuby provides.

Opening Java Classes with JRuby

Problem

You want to add methods to a Java class.

Solution

Import the Java class so that the class can be referenced, and add methods as you would to any Ruby class.

Discussion

In Ruby, class definitions are never finalized; new methods can be added at any time. This is perhaps one of the most significant differences between Java and Ruby. In Java, class definitions are tightly bound to filenames and directory structures. The complete definition of the Java class java.util.HashMap will be found in a file named /java/util/HashMap.class. In Ruby, no such relationship exists and classes can be defined across multiple source files. With JRuby, it’s possible to apply this language feature to Java classes. Example 1-22 shows a simple example of enhancing the java.util.HashMap class with a method named is?.

Example 1-22. Adding a method to HashMap
include Java

import java.util.HashMap

class HashMap
    def is?(key,value)
        value == get(key)
    end
end

As you can see in this example, within the new method we can call methods defined by the original Java class. Once this code is executed, JRuby instances of the HashMap class, including those already created, will have this new method. This even applies to instances of the class created by Java code. Examples 1-23 and 1-24 contain a Java class that creates a HashMap object and Ruby code that opens the HashMap class and exercises the new method.

Example 1-23. A simple class to generate a HashMap object
package org.jrubycookbook.ch01;
import java.util.*;

public class MapMaker {
    public static Map makeMap() {
        Map m = new HashMap();
        m.put("k1", "v1");
        m.put("k2", "v2");
        return m;
    }
}
Example 1-24. Applying open class semantics to an instance created with Java code
include Java

import java.util.HashMap
import org.jrubycookbook.ch01.MapMaker

h = MapMaker.makeMap()

class HashMap
    def isNot?(key,value)
        value != get(key)
    end
end

puts (h.isNot? 'k1', 'v1')
puts (h.isNot? 'k2', 'v3')

However, any added methods are only visible to the JRuby runtime. If you were to pass an instance of this modified HashMap class to Java code, the new methods would not be available.

JRuby also includes a utility method called extend_proxy that allows you to add new methods to all implementations of a particular interface. Example 1-24 could be rewritten to use this functionality so as to work with any implementation of java.util.Map. This can be seen in Example 1-25.

Example 1-25. Using extend_proxy to open all implementations of an interface
include Java

import org.jrubycookbook.ch01.MapMaker

h = MapMaker.makeMap()

JavaUtilities.extend_proxy('java.util.Map') do
    def isNot?(key,value)
        value != get(key)
    end
end

puts (h.isNot? 'k1', 'v1')
puts (h.isNot? 'k2', 'v3')

Setting Up Eclipse for JRuby Development

Problem

You use the Eclipse Integrated Development Environment (IDE) for Ruby development and want to run Ruby code easily with the JRuby interpreter.

Solution

When using the Ruby Development Tools (RDT) plugin, create a new Ruby VM definition that is pointed at your JRuby installation location and whose type is set to JRuby VM. When using the Dynamic Language Toolkit (DLTK) plugin, create a new Ruby interpreter definition that references the JRuby launch script: bin\jruby.bat (for Windows) or bin/jruby (for Linux and Mac OS X) from your JRuby installation.

Discussion

Both RDT and DLTK can be configured to work with multiple Ruby interpreters. RDT has a specific setting available for the JRuby interpreter, whereas DLTK simply treats JRuby as a generic Ruby interpreter.

RDT

RDT, available from http://rubyeclipse.sourceforge.net, supports configuration of Ruby interpreters based on the installation directory. To add JRuby as an interpreter, open the Preferences dialog and locate the Installed Interpreters page. Click the Add button to open the Add RubyVM dialog (seen in Figure 1-5). In this dialog, select JRuby VM as the RubyVM type and select the JRuby installation directory as the RubyVM home directory. You can also override the display name with something more user-friendly. Once you’re satisfied with the settings, click OK.

RDT Add RubyVM dialog
Figure 1-5. RDT Add RubyVM dialog

DLTK

The Dynamic Language Toolkit project, hosted at http://www.eclipse.org/dltk, is a broad project sponsored by the Eclipse Foundation to provide general support for dynamic languages in the Eclipse development environment. Currently, support is available through the DLKT project for Ruby, TCL, and Python. The DLTK Ruby plugin does not make a distinction between a standard Ruby interpreter and the JRuby interpreter. Just as when configuring RDT, open the Preferences dialog and locate the Interpreters page. Click the Add button to open the “Add interpreter” dialog, seen in Figure 1-6. Select the bin\jruby.bat (for Windows) or bin/jruby (for Linux and Mac OS X) as the interpreter executable. As with RDT, you can change the interpreter name to something more user-friendly. Finally, click OK to add the interpreter.

DLTK “Add interpreter” dialog
Figure 1-6. DLTK “Add interpreter” dialog

Running JRuby as a Java application

Although both RDT and DLTK can easily interface with the JRuby interpreter because they are both designed for Ruby development, you are not able to manage the classpath used by the Java Virtual Machine inside which JRuby is running. This is a problem when referencing Java classes located in external JAR files. Since the JRuby interpreter is simply a Java class, it can be run as such within Eclipse. To do this, open the Run dialog by selecting “Open Run Dialog...” from the Run menu. Select Java Application and click the New button to create a new launch configuration. For the Main class, enter org.jruby.Main. In the Arguments tab, put the path to the Ruby file you want to run in the Program arguments section (along with any other application-specific arguments). The VM arguments should include the jruby.base, jruby.home, and jruby.lib system properties. Set jruby.base and jruby.home to the JRuby installation directory and jruby.lib to the JRuby lib directory for the last one. Eclipse has an expression language available to this dialog that allows you to reference the JRUBY_HOME environment variable while setting these properties with this value:

-Djruby.base="${env_var:JRUBY_HOME}" -Djruby.home="${env_var:JRUBY_HOME}"
-Djruby.lib="${env_var:JRUBY_HOME}/lib"

Finally, in the Classpath tab, add bsf.jar and jruby.jar from JRuby’s lib directory and any other JAR files needed by your code. Then, click the Run button to execute.

Eclipse also supports expressions that prompt the user for input. You can use this functionality to make the launch configuration more reusable. You can prompt for a file, which opens the operating system’s standard file selection dialog, with:

${file_prompt:Ruby Script Name}

To prompt specifically for a file within the workspace, use:

${resource_loc:${string_prompt:Ruby Script Name}}

In this case, the user is prompted for a location within the Eclipse workspace that is then converted into a filesystem path. You can see these expressions in use in Figure 1-7.

Generic JRuby launch configuration
Figure 1-7. Generic JRuby launch configuration

Running this configuration opens a dialog, seen in Figure 1-8, where you can enter the workspace path to the Ruby script you want to execute. On subsequent executions, Eclipse automatically populates this dialog with the last value entered.

Eclipse variable input dialog
Figure 1-8. Eclipse variable input dialog

Note that using this type of launch configuration doesn’t require using RDT or DLTK, although those plugins would still provide useful functionality, including code completion and RDoc integration.

Setting Up NetBeans for JRuby Development

Problem

You want to develop Ruby applications with NetBeans.

Solution

Download NetBeans 6.5 from http://www.netbeans.org and run the installer. NetBeans is available in a variety of bundles; both the Ruby and All bundles include support for Ruby development. In addition to Ruby, the All bundle includes support for Java, Web, Mobile, and C/C++, as well as both Apache Tomcat and Sun GlassFish application servers.

If you are already using NetBeans 6.5, Ruby support can be installed using the Plugins dialog, seen in Figure 1-9. This plugin adds new NetBeans project types for Ruby and Rails, graphical debuggers for Ruby and Rails, a Ruby Code Editor, and a RubyGems client.

Installing the NetBeans Ruby plugin with the Plugins dialog
Figure 1-9. Installing the NetBeans Ruby plugin with the Plugins dialog

Once the Ruby plugin has been installed, use the Ruby page in the Ruby Platforms dialog seen in Figure 1-10 to manage the Ruby runtimes used by your projects. Notice the options to add new runtimes or modify an interpreter’s gem repository location and debug level. By default, your Ruby project will use the JRuby runtime shipped with the Plugin, but you can assign a specific Ruby Platform to your application by using the project’s properties dialog.

Discussion

After several years of playing second fiddle to Eclipse, Sun has recently made some significant investments in the NetBeans project, and it shows—nowhere more so than in the Ruby plugin. The NetBeans Ruby Code Editor includes syntax highlighting, code coloring, refactoring support, and powerful code completion capabilities. The code completion functionality can be seen in Figure 1-11. The editor displays a list of possible methods in a small window, including built-in and user-defined Ruby classes. Hitting the space bar at this point inserts the complete name into the editor.

NetBeans Ruby Platform Manager dialog
Figure 1-10. NetBeans Ruby Platform Manager dialog
NetBeans Ruby code completion
Figure 1-11. NetBeans Ruby code completion

You can also change the editor’s font and highlighting colors or change the key bindings to match your personal preferences. Configuration is done in the Options dialog seen in Figure 1-12. Choose the Fonts & Colors tab and select a Profile from the list. OS X Ruby developers might be interested in a TextMate theme, Aloha (http://pages.huikau.com/AlohaTheme.nbm), for a more familiar color palette and highlighting rules. The Keymap page has bindings for Eclipse, Emacs, and older versions of NetBeans.

NetBeans Fonts & Colors Options dialog
Figure 1-12. NetBeans Fonts & Colors Options dialog

Platform Detection in a JRuby Application

Problem

You would like to detect the platform used by the Ruby runtime and customize your code for a JRuby runtime environment.

Solution

You can detect whether your application is running in JRuby by evaluating the JRUBY_VERSION system variable. This value will always be defined in a JRuby application but never in any other Ruby runtime. The generate_random_number method in Example 1-26 uses the random number generator from the Java Math class in a JRuby environment; otherwise, the application calls Ruby’s rand method.

Example 1-26. JRuby platform detection
class DetectionExample

  def generate_random_number
    if(defined?(JRUBY_VERSION))
     require 'java'
     puts 'executing java method'
     java.lang.Math.random
    else
     puts 'executing ruby method'
     rand(0)
    end
  end

end

d = DetectionExample.new
puts d.generate_random_number

Discussion

The RUBY_PLATFORM variable has information about the runtime environment and is set to java in JRuby. It was used with early versions of JRuby for platform detection, but the JRUBY_VERSION variable was later added to identify unequivocally that the code was running in JRuby and not another Ruby interpreter written in Java. The new variable also opened up the possibility for JRuby version-specific code.



[1] Usually referred to as Matz’s Ruby Interpreter (MRI).

[2] A fourth lead developer, Ola Bini, works for the influential IT consulting company ThoughtWorks.

[3] This use of the Maven repository is naïve, as it assumes the JAR file is already in the local repository. Buildr, a build system for Java written in Ruby, includes support for downloading JAR files from remote Maven repositories. More information about Buildr can be found in Chapter 6.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

Start Free Trial

No credit card required