There's one command you need to enter the united world of Ruby and Java: include Java
.
This will allow you to instantiate Java classes, call their methods and even derive from them as if they were just ordinary Ruby objects. There are a few subtle differences, this article will show you how to manage them so you can quickly devise new applications and deploy them to your users at lightning-speed.
This article is based on a sample application that implements a simple ObjectSpace browser using JRuby and Swing. Ruby's ObjectSpace feature provides a way to access all objects of the system. For example, all living strings can be printed like this:
ObjectSpace.each_object(String) do |string|This yields about 28000 strings when run from within my irb session. Using Swing and JRuby, we can now display the different classes, their instances and the available methods in a nice graphical interface. You can even invoke parameterless methods by clicking on them in the rightmost panel:
puts string
end
ObjectSpace support in JRuby is disabled by default because of the performance penalty it imposes on the runtime, but more on that later.
I'd like to point out a few interesting details of the implementation and give some hints on how to get started on using the Java integration features of JRuby.
Java Integration
Once you've included Java into your script, you can start subclassing existing Java classes. This can be done by simply specifying the fully qualified name of the Java class. In the example application, the main window extends JFrame. It also includes the javax.swing and java.awt packages into the class' scope so you don't have to specify the full name for every usage:
class MainWindow < javax.swing.JFrame
include_package 'javax.swing'
include_package 'java.awt' ...
Alternatively, you can also include specific classes using the include_class
function, which doesn't pollute the namespace with constants for classes you'll never use.
Calling super constructors works just as with plain Ruby code, which means that the frame title can be set by calling super("JRuby Object Browser")
on the first line of the initialize
-method.
With the whole javax.swing package included into the class, instantiating Java classes is straightforward:
list_panel = JPanel.new
list_panel.layout = GridLayout.new(0, 3)
If you take a closer look at the second line, one might think that we directly access the layout-field of the JPanel, but that's not case. JRuby adds some convenience methods to Java objects, so the above statement could also be written in the accustomed way:
list_panel.setLayout(GridLayout.new(0, 3))
Instead of using getters and setters, you can seemingly access fields directly. There is even more syntactic sugar JRuby adds to Java objects that makes the whole experience feel more Ruby-like: you can use the snake_case notation instead of the actually defined camel case names for any method calls. From this follows the third way to call the setLayout
method:
list_panel.set_layout(GridLayout.new(0, 3))
Be cautious when calling setter-methods from within their own class, Ruby might think you want to create a local variable, so you have to call it explicitely with self as the receiver:
self.default_close_operation = WindowConstants::EXIT_ON_CLOSE
Another difference between Java and Ruby is the accessing of constants, like EXIT_ON_CLOSE
in the previous snippet. Just remember to replace all .
accesses with ::
if you're translating some code from Java to Ruby.
Until now, most changes JRuby brings to Swing development don't seem to be very revolutionary, but we haven't covered one important aspect yet: listeners. In Java, hooking up a listener with an event often means implementing an interface in an anonymous class:
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
...
}
});
This really clutters your code if you need to attach a lot of listeners. With JRuby, this can be reduced to the following two lines of code (and you can even omit the event
variable if you don't use it):
button.add_action_listener do |event|
...
end
These are the basics you need to know to start using Swing with JRuby. Even though JRuby makes GUI development with Swing more comfortable, you still have to write a lot of code manually, especially when you need to use complex layouts. If you want to make the creation of Swing-UIs even simpler, take a look at Three approaches to JRuby GUI APIs.
Deployment of JRuby Applications Made Easy
Ruby applications and libraries are usually distributed with the help of RubyGems, but in order to take advantage of it, you already need to have Ruby and RubyGems installed, which likely isn't the case for the average end-user. This problem has already been solved for traditional (MRI / C-Ruby) programs in the form of RubyScript2Exe[1], which bundles your scripts and a Ruby interpreter into a nice package that can be run on multiple platforms. Users of JRuby don't have to feel left out in the rain, quite the contrary, they have an even mightier tool at hand to quickly deploy applications: Java Web Start.
Java Web Start to the Rescue
Java Web Start is included in the Java Runtime Environment and should therefore be present on most systems. Making an application ready for Web Start is rather easy, all that is needed is a Jar containing all the files and a JNLP (Java Network Launching Protocol) description file. We demonstrate the process of creating a web-startable Ruby application on the basis of the ObjectSpace browser application.
The prerequisite for Web Start is a Jar that contains the application, so we'll take a look at that first. JRuby provides two different libraries: the "minimal" jruby.jar and jruby-complete.jar, which bundles the whole Ruby standard library. If you don't use anything from the standard library, you can use the smaller jruby.jar and decrease the download by roughly one megabyte.
The simplest way to get your script running is to add the .rb
files to jruby.jar. The following command adds our example, rob.rb
, to the zip.
jar uf jruby.jar rob.rb
To check if it worked, you can start your application with java, requiring our Ruby script. The application needs the ObjectSpace, which we need to enable by passing the jruby.objectspace.enabled=true
property to Java:
java -Djruby.objectspace.enabled=true -jar jruby.jar -r rob
The -r
option automatically requires the specified file and thus runs our script.
Ahead of Time Compilation
One of the exciting new features of JRuby 1.1 is the ahead of time (AOT) compilation support. Currently, just in time compilation in JRuby is restricted to 2048 methods, ahead of time compiling can help to mitigate that restriction. jrubyc
, the JRuby compiler, is still under development, so I'd advise to use the latest JRuby release available. Compiling a plain Ruby file to a classfile is as simple as invoking the compiler with the script(s) as arguments:
jrubyc rob.rb
This will create a ruby
directory containing a rob.class
file. Instead of including the ruby
directory into the jruby.jar as we did above, we're going to create a separate Jar to hold the application. After all, modifying the existing Jar doesn't really seem to be such a neat solution. Jars can be made by the tool of the same name:
jar -cfe rob.jar ruby/rob.class rubyThis will create a small Jar called rob.jar with our class inside, adding the ruby/rob.class as the main-class to be specified in the Manifest. This allows us to simplify the invocation, as we can now simply point to the class and don't have to speficy the require on the command line anymore. To execute it, we have to make sure that
rob.jar
is on the classpath:
java -Djruby.objectspace.enabled=true -cp rob.jar:jruby.jar ruby.rob
Web Start
Before we can continue to writing the JNLP-File, we need to sign the Jars. That's a unfortunate necessity because JRuby uses reflection and thus needs to have extended permissions, see the JRuby Wiki for more details. The easiest way is to create yourself a test certificate using the keytool
that comes with the JDK:
keytool -genkey -keystore myKeystore -alias myself
keytool -selfcert -alias myself -keystore myKeystore
From now on, every time you modify one of the Jars, you have to update the signature or you'll get a SecurityException when running it.
jarsigner -keystore myKeystore jruby.jar myself
jarsigner -keystore myKeystore rob.jar myself
Now that we have the Jars prepared, we can actually take a look at that JNLP file. The following presents a minimal configuration for the application. Some fields are required by the specification, like the title
, vendor
and j2se
tags as well as the security
section. The jar-tag denotes the final location of the Jars. It can also point to a local file, using a file://
-URL, which can be convenient during development. The ObjectSpace property needs to be set here too, this can be done with the property-tag.
<?xml version="1.0" encoding="utf-8"?>
<jnlp>
<information>
<title>Ruby Object Browser</title>
<vendor>Mirko Stocker</vendor>
</information>
<resources>
<jar href="http://misto.ch/rob/rob.jar"/>
<jar href="http://misto.ch/rob/jruby.jar"/>
<j2se version="1.5+"/>
<property name="jruby.objectspace.enabled" value="true"/>
</resources>
<application-desc main-class="ruby.rob"/>
<security>
<all-permissions/>
</security>
</jnlp>
If you skipped the AOT section or just want to keep the one-jar way, then you'll have to modify the jnlp file to also include the -e
arguments, so your application-desc
will look like:
[...]
<application-desc>
<argument>-r</argument>
<argument>rob</argument>
</application-desc>
[...]
The final step is to upload the Jars and the JNLP file to the specified location. You should now be able to open the link in your browser or using the javaws
tool directly from a shell.
Troubleshooting
In order for your browser to launch the application with Web Start, the JNLP file needs to be delivered with the application/x-java-jnlp-file
MIME-Type. So if your browser just displays the content of the JNLP file and javaws isn't launched automatically, you need to adapt the webserver configuration. For example, Apache needs the following directive in mime.types:
application/x-java-jnlp-file jnlp
Further Reading
The JRuby wiki is a great source for general information about JRuby.