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 ...
- it's object-oriented
- it's dynamically-typed, supporting "duck typing"
- it has a compact, yet easy to read syntax
- it features blocks that are closures
- classes and objects are open, which means that methods can be added
- it's the language of the Rails web app framework
"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.
- creating a formal specification for the language and libraries (see http://www.rubyspec.org/)
- creating a larger library of tests
- improving compatibility with standard Ruby
- getting Rails to run under JRuby
- passing existing Rails unit tests
- improving performance of the interpreter
- writing a Ruby to Java bytecode compiler (initial results are about twice as fast as C-based Ruby interpreter)
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.
- Gardens Point Ruby.NET Compiler - http://www.plas.fit.qut.edu.au/rubynet
a Ruby compiler implemented in C#, funded by Microsoft and developed at the Queensland University of Technology in Australia - IronRuby - http://www.wilcob.com/Wilco/IronRuby.aspx
a Ruby interpreter similar to IronPython, implemented in C# - RubyCLR - http://www.rubyclr.com
a Common Language Runtime (CLR) bridge for Ruby
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.
- Use Java Libraries (for example, Swing) from code written in Ruby syntax.
- 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
- 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.
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.Main
which is in $JRUBY_HOME/lib/jruby.jar
.
Here's an example from a file named hello.rb
.
- name = ARGV[0] || "you"
- 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.
- include_class("java.lang.String") do |pkg_name, class_name|
- "J#{class_name}"
- end
- 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.
- module Swing
- include_package "javax.swing"
- end
- 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.
- require "java"
- include_class "java.util.ArrayList"
- list = ArrayList.new
- %w(Red Green Blue).each { |color| list.add(color) }
-
- # Add "first" method to proxy of Java ArrayList class.
- class ArrayList
- def first
- self.size == 0 ? nil : self.get(0)
- end
- end
- puts "first item is #{list.first}"
-
- # Add "last" method only to the list object ... a singleton method.
- def list.last
- self.size == 0 ? nil : self.get(self.size - 1)
- end
- 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.bar
, foo.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.
- require "java"
- url = java.net.URL.new("http://www.ociweb.com")
- puts url.to_external_form # method name is toExternalForm
- 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?
, collect
, each_with_index
, find
, find_all
, grep
, max
, min
, sort
, sort_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.
- require "java"
- list = java.util.ArrayList.new
-
- list << "Mark"
- # Since everything in Ruby is an object,
- # numbers and booleans can be added to Java collections.
- list << 19
- list << true
- list.each { |element| puts element } # "each" method added to ArrayList
-
- # The following line invokes the Java ArrayList#toString method
- # instead of the Ruby Array#to_s method.
- 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
.
- package com.ociweb.demo;
-
- public class Car {
- private String make;
- private String model;
- private int year;
-
- public Car() {}
-
- public Car(String make, String model, int year) {
- this.make = make;
- this.model = model;
- this.year = year;
- }
-
- public String getMake() { return make; }
- public String getModel() { return model; }
- public int getYear() { return year; }
-
- public void setMake(String make) { this.make = make; }
- public void setModel(String model) { this.model = model; }
- public void setYear(int year) { this.year = year; }
-
- public String toString() {
- return year + " " + make + " " + model;
- }
- }
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.
- require "java"
-
- Car = com.ociweb.demo.Car
-
- c = Car.new("Honda", "Prelude", 1997)
- puts c
-
- class RaceCar < Car
- attr_accessor :top_speed
-
- def initialize(
- make=nil, model=nil, year=0, top_speed=0)
- super(make, model, year)
- @top_speed = top_speed
- end
-
- def to_s
- "#{super} can go #{@top_speed} MPH"
- end
- end
-
- c = RaceCar.new("Ferrari", "F430", 2005, 196)
- puts c
-
- c = RaceCar.new("Porche", "917")
- c.year = 1971
- c.top_speed = 248
- 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.
- require "java"
-
- BorderLayout = java.awt.BorderLayout
- JButton = javax.swing.JButton
- JFrame = javax.swing.JFrame
- JLabel = javax.swing.JLabel
- JOptionPane = javax.swing.JOptionPane
- JPanel = javax.swing.JPanel
- JTextField = javax.swing.JTextField
-
- # A BlockActionListener is an ActionListener whose constructor takes a Ruby block.
- # It holds the block and invokes it when actionPerformed is called.
- class BlockActionListener < java.awt.event.ActionListener
- # super call is needed for now - see JRUBY-66 in JIRA
- def initialize(&block); super; @block = block; end
- def actionPerformed(e); @block.call(e); end
- end
-
- # Extend the Swing JButton class with a new constructor that takes the button text
- # and a Ruby block to be invoked when the button is pressed.
- class JButton
- def initialize(name, &block)
- super(name)
- addActionListener(BlockActionListener.new(&block))
- end
- end
-
- # Define a class that represents the JFrame implementation of the GUI.
- class HelloFrame < JFrame
- def initialize
- super("Hello Swing!")
- populate
- pack
- self.resizable = false
- self.defaultCloseOperation = JFrame::EXIT_ON_CLOSE
- end
-
- def populate
- name_panel = JPanel.new
- name_panel.add JLabel.new("Name:")
- name_field = JTextField.new(20)
- name_panel.add name_field
-
- button_panel = JPanel.new
- # Note how a block is passed to the JButton constructor.
- greet_button = JButton.new("Greet") do
- name = name_field.text
- # Demonstrate display of HTML in a dialog box.
- msg = %(<html>Hello <span style="color:red">#{name}</span>!</html>)
- JOptionPane.showMessageDialog self, msg
- end
- button_panel.add greet_button
- # Note how a block is passed to the JButton constructor.
- clear_button = JButton.new("Clear") { name_field.text = "" }
- button_panel.add clear_button
-
- contentPane.add name_panel, BorderLayout::CENTER
- contentPane.add button_panel, BorderLayout::SOUTH
- end
- end # of HelloFrame class
-
- 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.
- jruby.base=$JRUBY_HOME
- jruby.home=$JRUBY_HOME
- jruby.lib=$JRUBY_HOME/lib
- jruby.script={jruby for Unix variants, jruby.bat for Windows}
- 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.
- $JRUBY_HOME/lib
- $JRUBY_HOME/lib/ruby/site_ruby/1.8
- $JRUBY_HOME/lib/ruby/site_ruby/1.8/java
- $JRUBY_HOME/lib/ruby/site_ruby
- $JRUBY_HOME/lib/ruby/1.8
- $JRUBY_HOME/lib/ruby/1.8/java
- 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:
registerScriptingEngine
- makes an engine available for useexec
- executes script codeeval
- executes script code and returns its valueregisterBean
- adds an object to the object registrylookupBean
- gets an object from the object registrydeclareBean
- creates an object in a scripting language global variable that won't be retrieved withLookupBean
Here are the steps to use BSF with JRuby.
1. Add the following JARs found in the JRuby lib
directory to the classpath:
bsf.jar
jruby.jar
jvyaml.jar
- a Java YAML parser and emitter
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.
- String language = "ruby";
- String engineName = "org.jruby.javasupport.bsf.JRubyEngine";
- String[] extensions = {"rb"};
- 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,
- manager.registerBean("frame", aFrame); // in Java
-
- 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.
- # Get a List of the squares of the integers from 1 to 5.
- String scriptCode = "(1..5).collect {|e| e**2 }";
- List<Long> squares =
- (List<Long>) manager.eval("ruby", "java", 1, 1, scriptCode);
-
- # Print them.
- for (long square : squares) {
- System.out.println(square);
- }
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!
- require "rubygems"
- require "active_record"
-
- ActiveRecord::Base::establish_connection(
- :adapter=>"mysql",
- :host=>"localhost",
- :database=>"music",
- :user=>"someuser", :password=>"somepassword")
-
- class Artist < ActiveRecord::Base
- has_many :recording
- # Sort based on artist name.
- def <=>(other); name <=> other.name; end
- end
-
- class Recording < ActiveRecord::Base
- belongs_to :artist
- # Sort based on recording name.
- def <=>(other); name <=> other.name; end
- 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.
- require "models"
-
- # $recordings is a Java ArrayList that is created in Query.java
- # and declared as a BSF bean.
- # This code populates it with Ruby Recoding objects.
-
- Recording.find_all_by_year(2003).sort.each do |r|
- $recordings << r
- 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
.
- package com.ociweb.activerecord;
-
- import com.ociweb.bsf.BSFHelper;
- import com.ociweb.jruby.JRubyHelper;
- import java.util.*;
- import org.apache.bsf.BSFException;
- import org.jruby.*;
-
- public class Query {
- private BSFHelper bsf = new BSFHelper();
- private JRubyHelper helper = new JRubyHelper();
-
- public static void main(String[] args) throws Exception {
- new Query();
- }
-
- private Query() throws BSFException, java.io.IOException {
- // Pass a Java object into Ruby code which will populate it.
- System.out.println("2003 Recordings");
- List recordings = new ArrayList();
- bsf.declareBean("recordings", recordings);
- bsf.evalFile("2003recordings.jrb");
-
- // Retrieve data that Ruby populated into recordings.
- Iterator iter = recordings.iterator();
- while (iter.hasNext()) {
- RubyObject recording = (RubyObject) iter.next();
-
- // The attributes of Recording are id, name, year and artist_id.
- String recordingName =
- (String) helper.getAttribute(recording, "name");
-
- // Get the Artist object associated with this Recording object.
- // What is the intermediate object here?
- RubyObject artist = helper.callMethod(recording, "artist");
- artist = (RubyObject) artist.getInstanceVariable("@target");
-
- String artistName = (String) helper.getAttribute(artist, "name");
-
- System.out.println(" " + recordingName + " by " + artistName);
- }
- }
- }
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.
- Install Java 6.
- Install JRuby.
- Download scripting engines from http://scripting.dev.java.net. See the "Documents & files" link in the left nav. Download either
jsr223-engines.tar.gz
orjsr223-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 obtainjruby-engine.jar
. - Add
jruby.jar
andjruby-engine.jar
to the classpath. - Use the
ScriptEngine
eval
method to evaluate scripting language code.
It takes aString
of script code or aReader
.
To read from a file in the file system, usenew BufferedReader(new FileReader(path))
.
To read from a file in the classpath, usenew InputStreamReader(ClassLoader.getSystemResourceAsStream(path))
.
Theeval
method returns the return value of the script, if any. Here's a simple example.
- import javax.script.*;
-
- public class JSR223Demo {
-
- public static void main(String[] args) throws ScriptException {
- ScriptEngineManager manager = new ScriptEngineManager();
- ScriptEngine engine = manager.getEngineByName("jruby");
- engine.eval("puts "Hello World!"");
- }
- }
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.
- class Calculator
- def average_of_3(n1, n2, n3)
- (n1 + n2 + n3) / 3.0
- end
-
- def average(*array)
- sum = 0
- array.each { |n| sum += n }
- sum.to_f / array.size
- end
- end
-
- def getCalculator
- Calculator.new
- 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.
- import java.io.*;
- import javax.script.*;
-
- public class Demo {
-
- public static void main(String[] args)
- throws IOException, NoSuchMethodException, ScriptException {
- ScriptEngineManager manager = new ScriptEngineManager();
- ScriptEngine engine = manager.getEngineByName("jruby");
- engine.eval(new BufferedReader(new FileReader("src/demo.rb")));
-
- Invocable invocable = (Invocable) engine;
- Object calculator = invocable.invokeFunction("getCalculator");
- double average = (Double) invocable.invokeMethod(
- calculator, "average_of_3", 1, 4, 5);
- System.out.println("average = " + average);
-
- average = (Double) invocable.invokeMethod(
- calculator, "average", 1, 4, 5);
- System.out.println("average = " + average);
- }
- }
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
- [1] JRuby project homepage (includes mailing lists)
http://www.jruby.org - [2] JRuby Wiki
http://www.headius.com/jrubywiki - [3] Nutter's Blog
http://headius.blogspot.com - [4] Enebo's Blog
http://www.bloglines.com/blog/ThomasEEnebo - [5] Bini's Blog
http://ola-bini.blogspot.com - [6] Java Posse Podcast interview with Nutter and Enebo
http://javaposse.com/index.php?post_id=171709 - [7] Ruby homepage
http://www.ruby-lang.org - [8] Programming Ruby: The Pragmatic Programmers Guide, Second Edition book
http://www.pragmaticprogrammer.com/titles/ruby/ - [9] Agile Web Development with Rails, Second Edition book
http://www.pragmaticprogrammer.com/titles/rails2/