JRuby

JRuby

By R. Mark Volkmann, OCI Partner

April 2007


Introduction

This article provides an introduction to JRuby, an implementation of Ruby on the Java Virtual Machine (JVM).

Ruby Overview

Ahh Ruby... If you didn't already have at least a small interest in Ruby you probably wouldn't be reading this article. What's so interesting about Ruby? Well the people in the know tell us that Ruby is special because ...

"Standard" Ruby is supported by an interpreter that is implemented in C. There is no compiler. Optimization is minimal, leaving Ruby somewhat slower than Python on average. Some of Ruby's libraries are implemented in Ruby and some in C. No formal language specification exists.

There is ongoing work to create a new VM for Ruby that will deliver improved performance. In fact two such efforts are underway. One is "Yet Another Ruby VM" (YARV). This will be the basis of Ruby 2.0, which may still be over a year away from release. Another is Rubinus which is modeled after Smalltalk VMs.

This article assumes basic knowledge of Ruby syntax. If you need to refresh your memory or learn it for the first time, see http://en.wikibooks.org/wiki/Ruby_Programming/Syntax.

JRuby Overview

JRuby is Ruby on the JVM. It is a Ruby interpreter written entirely in Java. JRuby allows using Java capabilities with Ruby syntax. It also allows using Ruby libraries from Java.

Many programming languages have been implemented on the JVM. The list includes BeanShell, Bex (BeanShell variant), Groovy, Jaskell (Haskell), Jawk (AWK), JudoScript, Jython (Python), JRuby (Ruby), Pnuts, Quercus (PHP), Rhino (JavaScript), SISC (Scheme), Sleep (Perl/Objective-C), Jacl (TCL) and more. A full list is available at http://scripting.java.net/.

JRuby supports all Ruby syntax and built-in libraries. It supports most of the Ruby standard libraries. Those implemented in Ruby were easy to retain. Those implemented in C had to be ported to Java. Only the most frequently used of those libraries have been ported so far. Many popular Ruby libraries work under JRuby today. These include Active Record (with JDBC), DRb, Rake (Ruby's answer to Make and Java's Ant), Rails, RSpec (behavior-driven development) and RubyGems.

JRuby is currently significantly slower than standard Ruby. Most code takes two to three times as long to run. For more information, see http://headius.blogspot.com/2006/08/nibbling-away-at-performance.html and http://antoniocangiano.com/articles/2007/02/19/ruby-implementations-shootout-ruby-vs-yarv-vs-jruby-vs-gardens-point-ruby-net-vs-rubinius-vs-cardinal.

Initial efforts on the JRuby project have centered on these activities.

The 1.0 release of JRuby is expected in May 2007, in time for announcement at the JavaOne conference. After that, the team will continue improving the interpreter and compiler. A mixed mode is expected where some code is compiled and some is interpreted. This is due to the highly dynamic nature of the Ruby language.

Other Ruby Implementations

There is interest in alternate Ruby implementations outside of Java. Three separate efforts to create Ruby implementations that run under .NET have started.

JRuby History

Stephen Matthias Aust ported the Ruby grammar from the C-based Ruby interpreter to Jay, a Java-based parser which is still used by JRuby. Jan Arne Petersen started JRuby project in 2001, building on the work by Aust. Thomas Enebo began work on JRuby in late 2002 and became the project lead in late 2003. Charles Nutter began work on JRuby in 2004. Sun Microsystems hired Nutter and Enebo to develop JRuby full-time in September, 2006. This was at least partially motivated by Tim Bray at Sun who is a major advocate of dynamic languages. Sun is committed to keeping JRuby open source and will provide more Ruby development tools such as support in NetBeans. Ola Bini became a committer in October, 2006. Nick Sieger became a committer in January, 2007.

Reasons To Use JRuby

There are three primary motivations for using JRuby.

  1. Use Java Libraries (for example, Swing) from code written in Ruby syntax.
  2. Use Ruby libraries (for example, ActiveRecord) from code written in Java syntax using either the Bean Scripting Framework (BSF) or the JSR 223 Scripting API
  3. Get a faster implementation of Ruby (not yet faster, but likely will be soon).

What JRuby Offers That Ruby Doesn't

JRuby classes can inherit from Java classes and implement Java interfaces. They can even add methods to existing Java classes. These methods are visible from JRuby, but not Java. We'll discuss this in more detail later.

Running on the JVM provides many benefits. As mentioned earlier, this allows Java libraries to be used from Ruby syntax. It also provides features Java enjoys such as use of native threads, Unicode support (surprisingly weak in Ruby), and portability to any platform with a JVM (which is most platforms).

The fact that JRuby runs on the JVM can make it easier to "sneak" it into environments where installing the Ruby interpreter wouldn't be allowed. It only requires an additional JAR file.

Current Limitations

JRuby classes currently can only implement one Java interface, but this restriction will be removed soon. Java classes can't inherit from a JRuby class. As stated earlier, most code takes two to three times as long to run with JRuby as it does with standard Ruby. Currently there is no debugger that works with JRuby, however, Sun is adding integrated Java/JRuby debugging to NetBeans.

Tool Support

Many IDEs support Ruby including Eclipse (using the RDT plugin or RadRails), IntelliJ IDEA 6.0, and NetBeans (in work). Many editors offer Ruby support such as syntax highlighting. Examples include emacs, jEdit, TextMate and Vim. Spring 2, an IOC framework and more, supports "beans" implemented in Java, JRuby, Groovy and BeanShell.

SuperConsole is a graphical interactive console, similar to Ruby's IRB. It is available in three forms: executable JAR, Java Web Start and a Mac OS X application. It supports class and method name completion using the tab key. When there is more than one match, a popup list of choices is displayed. It can be downloaded from http://www.jruby.org.

SuperConsole

Installing JRuby

To install JRuby, download a binary release from http://www.jruby.org. Unzip/untar the downloaded archive, set the JRUBY_HOME environment variable to point to resulting directory, and add $JRUBY_HOME/bin to the PATH environment variable.

To checkout the latest version from the trunk of the Subversion respository, run the following command.

svn co http://svn.codehaus.org/jruby/trunk/jruby

To build this, ensure that Java and Ant are installed, and run "ant".

Using JRuby From Commandline

To run a JRuby script from the commandline, run jruby {script-name}. The suggested file suffix is .jrb when using JRuby extensions and .rb otherwise. This runs the class org.jruby.Mainwhich is in $JRUBY_HOME/lib/jruby.jar.

Here's an example from a file named hello.rb.

  1. name = ARGV[0] || "you"
  2. puts "Hello #{name}!"

When this is run with jruby hello.rb, it outputs "Hello you!". When run with jruby hello.rb Mark, it outputs "Hello Mark!".

Using Java Classes in JRuby

JRuby scripts must contain the following line in order to use Java classes.

require "java"

There are five options for referring to Java classes from JRuby.

1.   Provide full class name when using.

frame = javax.swing.JFrame.new("My Title")

2.   Assign full class name to a constant.

JFrame = javax.swing.JFrame
frame = JFrame.new("My Title")

3.    Use include_class.

include_class "javax.swing.JFrame"
frame = JFrame.new("My Title")

4.    Use include_class with an alias. This is useful when the name of a Java class matches the name of a Ruby class. 

  1. include_class("java.lang.String") do |pkg_name, class_name|
  2. "J#{class_name}"
  3. end
  4. msg = JString.new("My Message")

5.    Use include_package to scope Java classes in a Ruby module namespace. This can only be used inside a module and has performance issues.

  1. module Swing
  2. include_package "javax.swing"
  3. end
  4. frame = Swing::JFrame.new("My Title")

Proxy Classes

JRuby creates proxy classes for Java classes. This allows methods to be added just like in Ruby. In the example below, the method first is added to the Java ArrayList class and the method last is added to a specific object from that class.

  1. require "java"
  2. include_class "java.util.ArrayList"
  3. list = ArrayList.new
  4. %w(Red Green Blue).each { |color| list.add(color) }
  5.  
  6. # Add "first" method to proxy of Java ArrayList class.
  7. class ArrayList
  8. def first
  9. self.size == 0 ? nil : self.get(0)
  10. end
  11. end
  12. puts "first item is #{list.first}"
  13.  
  14. # Add "last" method only to the list object ... a singleton method.
  15. def list.last
  16. self.size == 0 ? nil : self.get(self.size - 1)
  17. end
  18. puts "last item is #{list.last}"

The output from this code is

first item is Red
last item is Blue

Method Calling Details

As in Ruby, parentheses aren't required when calling methods. foo.bar() is the same as foo.bar and foo.bar(baz) is the same as foo.bar baz.

Java get/set/is methods can be invoked like Ruby accessors. value = foo.getBar() is the same as value = foo.barfoo.setBar(value) is the same as foo.bar = value and foo.isBar() is the same as foo.bar?.

If an attempt is made to invoke the method bar without a receiver (no object reference and dot preceding it), it will be interpreted as a reference to a local variable. To have it interpreted as a method call, use self.bar.

Java methods with camel-cased names can be invoked using the Ruby underscore convention as shown in the example below. Underscore versions of methods get added to JRuby proxy classes.

  1. require "java"
  2. url = java.net.URL.new("http://www.ociweb.com")
  3. puts url.to_external_form # method name is toExternalForm
  4. puts url.to_uri # method name is toURI

Automatic Conversions

JRuby automatically converts between certain Java and Ruby data types. Some of these conversions are shown in the table below.

Ruby Type  Java Type
 Boolean   boolean, java.lang.Boolean  
 Fixnum   byte, java.lang.Byte,
  short, java.lang.Short,
  int, java.lang.Integer
 Float   float, java.lang.Float,
  double, java.lang.Double
 String   char, java.lang.String
 Array   java.util.List
 Hash   java.util.Map

Ruby Methods Added to Core Java Classes

JRuby adds many methods to core Java classes.

The java.lang String class and the java.lang type wrapper classes get the "spaceship" operator  (used to compare values) and all methods in the Ruby Comparable module which are defined in terms of the spaceship operator (<, <=, ==, =>, >, and between?).

Many methods are added to the java.util collection classes. The Collection interface which is the base interface of List and Set gets each (iterator), << (append), + (add another collection), - (remove another collection), and all the methods in Ruby's Enumerable module (these include all?any?collecteach_with_indexfindfind_allgrepmaxminsortsort_by, etc.). The List interface gets [][]=sort and sort! The Map interface gets each[] and []=.

Here is an example of using the java.util.ArrayList in JRuby-style.

  1. require "java"
  2. list = java.util.ArrayList.new
  3.  
  4. list << "Mark"
  5. # Since everything in Ruby is an object,
  6. # numbers and booleans can be added to Java collections.
  7. list << 19
  8. list << true
  9. list.each { |element| puts element } # "each" method added to ArrayList
  10.  
  11. # The following line invokes the Java ArrayList#toString method
  12. # instead of the Ruby Array#to_s method.
  13. puts list

The output from this code is

Mark
19
true
[Mark, 19, true]
 

JRuby Inheriting From Java Classes

JRuby classes can inherit from Java classes. Let's look at an example.

Here's a Java class named Car.

  1. package com.ociweb.demo;
  2.  
  3. public class Car {
  4. private String make;
  5. private String model;
  6. private int year;
  7.  
  8. public Car() {}
  9.  
  10. public Car(String make, String model, int year) {
  11. this.make = make;
  12. this.model = model;
  13. this.year = year;
  14. }
  15.  
  16. public String getMake() { return make; }
  17. public String getModel() { return model; }
  18. public int getYear() { return year; }
  19.  
  20. public void setMake(String make) { this.make = make; }
  21. public void setModel(String model) { this.model = model; }
  22. public void setYear(int year) { this.year = year; }
  23.  
  24. public String toString() {
  25. return year + " " + make + " " + model;
  26. }
  27. }

Here's a Ruby class named RaceCar that inherits from the Java Car class, along with some code that exercises both the Java and Ruby classes.

  1. require "java"
  2.  
  3. Car = com.ociweb.demo.Car
  4.  
  5. c = Car.new("Honda", "Prelude", 1997)
  6. puts c
  7.  
  8. class RaceCar < Car
  9. attr_accessor :top_speed
  10.  
  11. def initialize(
  12. make=nil, model=nil, year=0, top_speed=0)
  13. super(make, model, year)
  14. @top_speed = top_speed
  15. end
  16.  
  17. def to_s
  18. "#{super} can go #{@top_speed} MPH"
  19. end
  20. end
  21.  
  22. c = RaceCar.new("Ferrari", "F430", 2005, 196)
  23. puts c
  24.  
  25. c = RaceCar.new("Porche", "917")
  26. c.year = 1971
  27. c.top_speed = 248
  28. puts c

The output from this code is

1997 Honda Prelude
2005 Ferrari F430 can go 196 MPH
1971 Porche 917 can go 248 MPH

Using Swing From JRuby

Swing is the de facto standard library for building GUI applications in Java. SWT is an alternative to consider. Using JRuby to implement these types of applications can dramatically reduce the amount of typing required. It can even change the way the code is written. For example, wouldn't it be nice if the code to be executed when a button is clicked could be specified when the button is created? That isn't supported by Swing, but here's how JRuby can extend Swing to allow it and build the GUI below.

Hello Swing GUI
  1. require "java"
  2.  
  3. BorderLayout = java.awt.BorderLayout
  4. JButton = javax.swing.JButton
  5. JFrame = javax.swing.JFrame
  6. JLabel = javax.swing.JLabel
  7. JOptionPane = javax.swing.JOptionPane
  8. JPanel = javax.swing.JPanel
  9. JTextField = javax.swing.JTextField
  10.  
  11. # A BlockActionListener is an ActionListener whose constructor takes a Ruby block.
  12. # It holds the block and invokes it when actionPerformed is called.
  13. class BlockActionListener < java.awt.event.ActionListener
  14. # super call is needed for now - see JRUBY-66 in JIRA
  15. def initialize(&block); super; @block = block; end
  16. def actionPerformed(e); @block.call(e); end
  17. end
  18.  
  19. # Extend the Swing JButton class with a new constructor that takes the button text
  20. # and a Ruby block to be invoked when the button is pressed.
  21. class JButton
  22. def initialize(name, &block)
  23. super(name)
  24. addActionListener(BlockActionListener.new(&block))
  25. end
  26. end
  27.  
  28. # Define a class that represents the JFrame implementation of the GUI.
  29. class HelloFrame < JFrame
  30. def initialize
  31. super("Hello Swing!")
  32. populate
  33. pack
  34. self.resizable = false
  35. self.defaultCloseOperation = JFrame::EXIT_ON_CLOSE
  36. end
  37.  
  38. def populate
  39. name_panel = JPanel.new
  40. name_panel.add JLabel.new("Name:")
  41. name_field = JTextField.new(20)
  42. name_panel.add name_field
  43.  
  44. button_panel = JPanel.new
  45. # Note how a block is passed to the JButton constructor.
  46. greet_button = JButton.new("Greet") do
  47. name = name_field.text
  48. # Demonstrate display of HTML in a dialog box.
  49. msg = %(<html>Hello <span style="color:red">#{name}</span>!</html>)
  50. JOptionPane.showMessageDialog self, msg
  51. end
  52. button_panel.add greet_button
  53. # Note how a block is passed to the JButton constructor.
  54. clear_button = JButton.new("Clear") { name_field.text = "" }
  55. button_panel.add clear_button
  56.  
  57. contentPane.add name_panel, BorderLayout::CENTER
  58. contentPane.add button_panel, BorderLayout::SOUTH
  59. end
  60. end # of HelloFrame class
  61.  
  62. HelloFrame.new.visible = true

Another approach to using Swing from JRuby is the builder approach. See the JRBuilder library at http://www2.webng.com/bdortch/jrbuilder/.

Gems Under JRuby

Gems are the preferred mechanism for packaging, distributing and installing Ruby libraries and applications. JRuby provides scripts in its bin directory for working with gems. For example, to install the ActiveRecord gem, run the following command.

$JRUBY_HOME/bin/gem install activerecord -y

The -y options specifies that any gems on which activerecord depends should also be installed.

Currently, generating rdoc and ri documention from JRuby is slow. To avoid this when installing gems, include the --no-rdoc and --no-ri options.

To use gems with JRuby, the following system properties must be set, perhaps in an Ant build file.

  1. jruby.base=$JRUBY_HOME
  2. jruby.home=$JRUBY_HOME
  3. jruby.lib=$JRUBY_HOME/lib
  4. jruby.script={jruby for Unix variants, jruby.bat for Windows}
  5. jruby.shell={/bin/sh for Unix variants, cmd.exe for Windows}

In addition, the Ruby "load path" must be set by adding the following values to the $: global array.

  1. $JRUBY_HOME/lib
  2. $JRUBY_HOME/lib/ruby/site_ruby/1.8
  3. $JRUBY_HOME/lib/ruby/site_ruby/1.8/java
  4. $JRUBY_HOME/lib/ruby/site_ruby
  5. $JRUBY_HOME/lib/ruby/1.8
  6. $JRUBY_HOME/lib/ruby/1.8/java
  7. lib/ruby/1.8

We'll see an example using the ActiveRecord gem later.

Bean Scripting Framework (BSF)

BSF is a Java library that allows Java code to evaluate code written in various scripting languages. It also allows scripting language code to access Java objects and invoke Java methods. BSF engines exist for many scripting languages including Groovy, Javascript (Rhino), Python (Jython), Ruby (JRuby), JudoScript, NetRexx, ooRexx, ObjectScript, PROLOG (JLog), Tcl (Jacl) and XSLT (Xalan and Xerces).

The main class in BSF is BSFManager and the key methods in that class are:

Here are the steps to use BSF with JRuby.

1.   Add the following JARs found in the JRuby lib directory to the classpath:

2.   Consider increasing the maximum amount of memory available to the JVM, perhaps to 256 MB, by specifying the option -Xmx256m.

3.   Register the JRuby engine with the following code.

  1. String language = "ruby";
  2. String engineName = "org.jruby.javasupport.bsf.JRubyEngine";
  3. String[] extensions = {"rb"};
  4. BSFManager.registerScriptingEngine(language, engineName, extensions);

4.   Create a BSFManager with the following code.

BSFManager manager = new BSFManager();

5.    Optionally make Java objects accessible in the scripting language. There are two ways to do this. In the first option, register a bean in Java code and look it up in the scripting language. For example,

  1. manager.registerBean("frame", aFrame); // in Java
  2.  
  3. frame = $bsf.lookupBean("frame") # in JRuby

In the second option, declare a bean in Java code and access it through a global variable in the scripting language. For example,

manager.declareBean("frame", aFrame, JFrame.class);

The global variable frame ($frame in JRuby) is now available in the scripting language.

6.    Execute JRuby code from Java. There are two ways to do this. If a return value isn't needed, use the exec method as shown below.

manager.exec("ruby", "java", 1, 1, "$frame.setTitle('My Title')");

If a return value is needed, use the eval method as shown below.

  1. # Get a List of the squares of the integers from 1 to 5.
  2. String scriptCode = "(1..5).collect {|e| e**2 }";
  3. List<Long> squares =
  4. (List<Long>) manager.eval("ruby", "java", 1, 1, scriptCode);
  5.  
  6. # Print them.
  7. for (long square : squares) {
  8. System.out.println(square);
  9. }

The "1, 1" parameters are line and column numbers that provide context information. They must not be very useful because every example I've seen just sets them to 1, 1!

Java Using ActiveRecord

ActiveRecord is a Ruby library for accessing relational databases. It can be used from Java through JRuby. To do so, install the ActiveRecord gem as shown earlier. Under Java 5 and earlier, access it using BSF. Under Java 6 and later, use JSR 223 Scripting API (discussed later). We'll focus on BSF here. The classes BSFHelper and JRubyHelper were written to make this easier. They can be obtained from links on https://objectcomputing.com/mark/programming/ActiveRecord.html.

The example code below queries a MySQL database to find all the recordings from the year 2003 in a music collection.

The first step is to establish a database connection and describe relationships between the database tables to be used. This is done in the following Ruby source file named models.rb. ActiveRecord uses pluralization rules to find tables from Ruby class names. The Artist class corresponds to a table named "artists". The Recording class corresponds to a table named "recordings". ActiveRecord discovers the columns of each table by examining the database schema. Each table has an integer primary key named "id". The "recording" table has a foreign key named "artist_id" that refers to the "id" of the artist that made the recording. The relationships described in code state quite explictly that an artist has many recordings and a recording belongs to an artist. The  methods in the Artist and Recording classes are only provided to aid in sorting. The amazing thing about ActiveRecord is that it takes more text to explain what is happening than is needed in the code that does the work!

  1. require "rubygems"
  2. require "active_record"
  3.  
  4. ActiveRecord::Base::establish_connection(
  5. :adapter=>"mysql",
  6. :host=>"localhost",
  7. :database=>"music",
  8. :user=>"someuser", :password=>"somepassword")
  9.  
  10. class Artist < ActiveRecord::Base
  11. has_many :recording
  12. # Sort based on artist name.
  13. def <=>(other); name <=> other.name; end
  14. end
  15.  
  16. class Recording < ActiveRecord::Base
  17. belongs_to :artist
  18. # Sort based on recording name.
  19. def <=>(other); name <=> other.name; end
  20. end

The next step is to write the Ruby code that finds the desired data. This is done in the following Ruby source file named 2003recordings.jrb. It places the data in a Java ArrayList that is in the global variable $recordings. The Recording objects returned will have an association to the corresponding Artist object which is also returned.

  1. require "models"
  2.  
  3. # $recordings is a Java ArrayList that is created in Query.java
  4. # and declared as a BSF bean.
  5. # This code populates it with Ruby Recoding objects.
  6.  
  7. Recording.find_all_by_year(2003).sort.each do |r|
  8. $recordings << r
  9. end

The last step is to write the Java code that invokes the previous Ruby code. This is done in the following Java source file named Query.java.

  1. package com.ociweb.activerecord;
  2.  
  3. import com.ociweb.bsf.BSFHelper;
  4. import com.ociweb.jruby.JRubyHelper;
  5. import java.util.*;
  6. import org.apache.bsf.BSFException;
  7. import org.jruby.*;
  8.  
  9. public class Query {
  10. private BSFHelper bsf = new BSFHelper();
  11. private JRubyHelper helper = new JRubyHelper();
  12.  
  13. public static void main(String[] args) throws Exception {
  14. new Query();
  15. }
  16.  
  17. private Query() throws BSFException, java.io.IOException {
  18. // Pass a Java object into Ruby code which will populate it.
  19. System.out.println("2003 Recordings");
  20. List recordings = new ArrayList();
  21. bsf.declareBean("recordings", recordings);
  22. bsf.evalFile("2003recordings.jrb");
  23.  
  24. // Retrieve data that Ruby populated into recordings.
  25. Iterator iter = recordings.iterator();
  26. while (iter.hasNext()) {
  27. RubyObject recording = (RubyObject) iter.next();
  28.  
  29. // The attributes of Recording are id, name, year and artist_id.
  30. String recordingName =
  31. (String) helper.getAttribute(recording, "name");
  32.  
  33. // Get the Artist object associated with this Recording object.
  34. // What is the intermediate object here?
  35. RubyObject artist = helper.callMethod(recording, "artist");
  36. artist = (RubyObject) artist.getInstanceVariable("@target");
  37.  
  38. String artistName = (String) helper.getAttribute(artist, "name");
  39.  
  40. System.out.println(" " + recordingName + " by " + artistName);
  41. }
  42. }
  43. }

JSR 223: Scripting for the Java Platform

JSR 223 improves on BSF. It's a standard part of Java 6. Here are the steps to use it with JRuby.

  1. Install Java 6.
  2. Install JRuby.
  3. Download scripting engines from http://scripting.dev.java.net. See the "Documents & files" link in the left nav. Download either jsr223-engines.tar.gz or jsr223-engines.zip which contains all the available scripting engines. The engine for a particular scripting language can't currently be downloaded independently. Untar or unzip the downloaded file to obtain jruby-engine.jar.
  4. Add jruby.jar and jruby-engine.jar to the classpath.
  5. Use the ScriptEngine eval method to evaluate scripting language code.
    It takes a String of script code or a Reader.
    To read from a file in the file system, use new BufferedReader(new FileReader(path)).
    To read from a file in the classpath, use new InputStreamReader(ClassLoader.getSystemResourceAsStream(path)).
    The eval method returns the return value of the script, if any. Here's a simple example.
  1. import javax.script.*;
  2.  
  3. public class JSR223Demo {
  4.  
  5. public static void main(String[] args) throws ScriptException {
  6. ScriptEngineManager manager = new ScriptEngineManager();
  7. ScriptEngine engine = manager.getEngineByName("jruby");
  8. engine.eval("puts "Hello World!"");
  9. }
  10. }

To invoke functions and methods in the scripting language, follow these steps.

1.    Load the script that defines the functions to be invoked using the ScriptEngine eval method as shown above.

2.    Cast ScriptEngine to Invocable.

Invocable invocable = (Invocable) scriptEngine;

3.    Optionally specify data to be made available to the scripting language through global variables. Note that this doesn't work in JRuby 0.98 and earlier.

invocable.put(name, value);

4.    Invoke a Ruby function or method.

Object returnValue = invocable.invokeFunction(functionName [, params ]);
Object returnValue = invocable.invokeMethod(object, functionName [, params ]);

object is a script object returned by a previous invocation. The parameters can be any type.

Let's see this in action. First we'll write Ruby code in a file named demo.rb that defines a Calculator class. This class has two methods that calculate an average. The first takes three numeric values. The second takes a variable number of numeric values. The function getCalculator, not defined inside the class, returns a Ruby Calculator object.

  1. class Calculator
  2. def average_of_3(n1, n2, n3)
  3. (n1 + n2 + n3) / 3.0
  4. end
  5.  
  6. def average(*array)
  7. sum = 0
  8. array.each { |n| sum += n }
  9. sum.to_f / array.size
  10. end
  11. end
  12.  
  13. def getCalculator
  14. Calculator.new
  15. end

Here's the Java code that uses the Ruby code. Note how it first invokes getCalculator to obtain a reference to a Calculator object. It then passes that object to invokeMethod along with the name of the method to invoke and the parameters to pass to it.

  1. import java.io.*;
  2. import javax.script.*;
  3.  
  4. public class Demo {
  5.  
  6. public static void main(String[] args)
  7. throws IOException, NoSuchMethodException, ScriptException {
  8. ScriptEngineManager manager = new ScriptEngineManager();
  9. ScriptEngine engine = manager.getEngineByName("jruby");
  10. engine.eval(new BufferedReader(new FileReader("src/demo.rb")));
  11.  
  12. Invocable invocable = (Invocable) engine;
  13. Object calculator = invocable.invokeFunction("getCalculator");
  14. double average = (Double) invocable.invokeMethod(
  15. calculator, "average_of_3", 1, 4, 5);
  16. System.out.println("average = " + average);
  17.  
  18. average = (Double) invocable.invokeMethod(
  19. calculator, "average", 1, 4, 5);
  20. System.out.println("average = " + average);
  21. }
  22. }

The output from this code is

average = 3.3333333333333335
average = 3.3333333333333335

Summary

The future looks bright for JRuby, but there are still hurdles to be cleared. Performance improvements are needed and the number of outstanding bugs is still fairly high. For a list of these, see http://jira.codehaus.org/browse/JRUBY.

Optimism is high that within a year JRuby will be faster than standard Ruby. If this happens, JRuby could become the preferred platform for running Rails applications... make that ALL Ruby applications. If you're interested in JRuby and have cycles to spare, jump in and help the team resolve bugs and improve performance!

References