|
|
|
|
This article provides an introduction to JRuby, an implementation of Ruby on the Java Virtual Machine (JVM).
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/Programming:Ruby_Syntax.
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.dev.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.
There is interest in alternate Ruby implementations outside of Java. Three separate efforts to create Ruby implementations that run under .NET have started.
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.
There are three primary motivations for using JRuby.
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.
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.
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.
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".
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!".
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.
frame = javax.swing.JFrame.new("My Title")
JFrame = javax.swing.JFrame
frame = JFrame.new("My Title")
include_class.include_class "javax.swing.JFrame"
frame = JFrame.new("My Title")
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")
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")
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
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
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 |
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 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
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 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.
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 with
LookupBean
Here are the steps to use BSF with JRuby.
lib directory
to the classpath:
bsf.jar
jruby.jar
jvyaml.jar - a Java YAML parser and emitter-Xmx256m.
String language = "ruby";
String engineName = "org.jruby.javasupport.bsf.JRubyEngine";
String[] extensions = {"rb"};
BSFManager.registerScriptingEngine(language, engineName, extensions);
BSFManager with the following code.
BSFManager manager = new BSFManager();
manager.registerBean("frame", aFrame); // in Java
frame = $bsf.lookupBean("frame") # in JRuby
manager.declareBean("frame", aFrame, JFrame.class);
frame ($frame in JRuby)
is now available in the scripting language.
exec method as shown below.
manager.exec("ruby", "java", 1, 1, "$frame.setTitle('My Title')");
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); }
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
http://www.ociweb.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 improves on BSF. It's a standard part of Java 6. Here are the steps to use it with JRuby.
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.
jruby.jar and jruby-engine.jar
to the classpath.
ScriptEngine eval method
to evaluate scripting language code.String of script code or
a Reader.new BufferedReader(new FileReader(path)).new InputStreamReader(ClassLoader.getSystemResourceAsStream(path)).eval 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.
ScriptEngine eval method
as shown above.
ScriptEngine to Invocable.
Invocable invocable = (Invocable) scriptEngine;
invocable.put(name, value);
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
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!
OCI is the leading provider of Object Oriented technology training in the Midwest. More than 3,000 students participated in our training program over the last 12 months. Targeted toward Software Engineers and the development community, our extensive program of over 50 hands-on workshops is delivered to corporations and individuals throughout the U.S. and internationally. OCI's Educational Services include Group Training events, Open Enrollment classes, and Courseware Licensing.
For further information regarding OCI's Educational Services programs, please visit our Educational Services section on the web or contact us at training@ociweb.com.
|
|
|