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 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.
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 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.
Download and extract the latest binary release from the JRuby
website, http://www.jruby.org. Add the
bin directory to the PATH
environment variable.
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.
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.
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'"
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
.
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
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'.
The RubyGems Manuals, http://rubygems.org
You have Ruby and JRuby installed on the same computer and want to ensure that a Ruby script is processed by the correct interpreter.
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
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
You already have a number of RubyGems installed and want to use those gems from JRuby without reinstalling the gems.
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
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.
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.
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.
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)
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)
You want to reference a Java class which is contained in a JAR file that isn’t already included in your classpath.
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'
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"
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.
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.
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.
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.
Import the Java class so that the class can be referenced, and add methods as you would to any Ruby class.
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.
You use the Eclipse Integrated Development Environment (IDE) for Ruby development and want to run Ruby code easily with the JRuby interpreter.
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.
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, 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.
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.
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.
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.
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.
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.
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.
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.
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.
You would like to detect the platform used by the Ruby runtime and customize your code for a JRuby runtime environment.
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.
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.
Get JRuby Cookbook now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.