Click here to Skip to main content
15,880,608 members
Articles
Article
(untagged)

Practical JRuby on Rails Web 2.0 Projects: Chapter 6: Java Integration

Rate me:
Please Sign up or sign in to vote.
5.00/5 (1 vote)
13 May 2008CPOL29 min read 14.6K   5  
JRuby provides access to the Java ecosystem from within Ruby and vice versa. This chapter explores a variety of strategies for integration with Java classes, primitives, arrays, and more. It also points out some gotchas to watch out for along the Ruby way to Java.

TitlePractical JRuby on Rails Web 2.0 Projects: Bringing Ruby on Rails to the Java Platform
AuthorOla Bini
PublisherApress
PublishedSep 2007
ISBN978-1-59059-881-8
Pages330

Java Integration

The last few chapters mainly served to introduce Ruby and Rails, and only incidentally used JRuby to achieve this. From now on much more Java will be involved. That doesn't necessarily mean that you need to write and build Java classes, but you'll interact with the Java ecosystem in different ways that aren't possible with regular Ruby.

To make this transition easier on you, this chapter will be a detour into these issues. It will be a focused chapter; I won't talk about the next project application at all. This is the crash course about the areas where JRuby differs from Ruby.

I'll begin by describing how you can reference Java classes, Java primitives, and Java arrays, and what you can do with them from JRuby. The primitives and arrays especially are handled in a way that isn't natural for a Java programmer, so it's important to get these facts down.

One of the nicer things you can do when interfacing Java and Ruby is to implement Java interfaces, extend Java classes from Ruby, and then send that code back to Java. For example, you could implement an OutputStream in a few lines of Ruby code, send it right back into some FilterOutputStream or whatnot, and it will just work.

JRuby also adds several handy new ways of working with Java collections and Java arrays by adding many new methods to them. Because of this, some of the more useful things you can do are to call map on a SortedMap, for example. The possibilities are endless, and useful.

There's also another side to the Java integration in JRuby. Sometimes you want to use Ruby from Java. I'll describe three different techniques for how to do this: two that are recommended, and one that is not so recommended. That part of the chapter will be of most interest to you if you want to create a Java application that uses Ruby as an extension language, or to provide macro/plug-in capabilities. It could also be interesting if you want to use Ruby as a scripting language for web pages instead of Java Server Pages (JSP) or Velocity.

The final part will discuss some gotchas. Because the Java integration in JRuby is on the boundary between two different kinds of systems, it also contains most of the things that can go wrong if you don't know all the tricks and interesting things that can happen. I'll describe in some detail how the Java integration works, so that you understand the capabilities and limitations inherent in it.

But enough talking about what we're going to talk about. Let's get started.

Using Java Resources

The major selling point for using JRuby instead of regular Ruby is that all the Java code in the world is available to your Ruby program, and it's as easy to use as Ruby libraries. JRuby aims to be compatible with Ruby. Because of that, you first need to say you want to use the Java features in your Ruby program. There are two ways of doing this: the first explicit, the second implicit. The explicit way looks like this:
require 'java' 

That's all there is to it. The implicit way of getting at the Java goodness is to reference the Java module in your code. Say you'd like to use the Java class se.ologix.HelloWorld. In that case you could just do it like this:

k = Java::se.ologix.HelloWorld 

Because you referenced the Java module, you don't have to require java; it's done beneath the covers. That's it when it comes to getting hold of the Java information. One thing can be good to know, though. If you want to write code that runs on both ordinary Ruby and JRuby, and you'd like to see if you are using JRuby or not, there is one cumbersome way:

jruby = false 
begin
  require 'java' 
  jruby = true 
rescue 
end 

As you can see, if there isn't any java to require, a LoadError will be raised and the variable jruby won't be set to true. However, this can fail for several reasons. One of the big ones is that maybe there is a library on the load path called java. A better way to see if you're on JRuby or not is to check the RUBY_PLATFORM constant. If RUBY_PLATFORM = /java/, then you know you're running JRuby:

jruby = RUBY_PLATFORM = /java/ 

Currently, the best solution to finding out this information is to check for the JRUBY_VERSION:

jruby = defined?(JRUBY_VERSION) 

Next we'll look at how to use the things you can find in the Java module. Every Java library you want to use must be on the CLASSPATH prior to running JRuby. There are two ways of circumventing this restriction, though, but they don't work for all Java classes. The last part of Appendix B has more information about this caveat. However, in most cases both ways work, so try and see. The first is by using require on a JAR file:

require 'commons-discovery.jar' 

This adds commons-discovery.jar to the CLASSPATH JRuby uses to find classes. The second way is more explicit. It demands that you execute require 'java' before doing it:

$CLASSPATH << 'commons-discovery.jar'

Classes

In many cases, it's incredibly easy to use Java classes. There are several ways to get hold of references to Java classes, though, and different solutions fit different use cases. All the examples from here on depend on the Java integration libraries being loaded, as described earlier.

The first way to get hold of classes is the oldest, and is still the only way in JRuby to reference classes without a package. This version uses a method called include_class:

include_class "java.util.HashMap" 

x = HashMap.new 
x.put("foo","bar") 

After executing include_class with a string describing the class to load, a local constant with the same name as the class will be made available to your code. In some cases this isn't so good. For example, you might want to change the name of the included class, so it doesn't clash with existing Ruby classes. Then you can send a block to include_class, and that block is used to decide the name for the resulting constant:

include_class("java.lang<wbr />.String") {|pkg,name| "JString"} 

JString.new "Hello, world" 

The include_class method can take an array of class names, and in that case the block version is especially convenient:

include_class(["java.lang.String", 
               "java.lang.Runtime", 
               "java.util.Map"]) {|pkg, name| "J#{name}"} 

JString.new "Hello, world" 

Usually, though, it's easier just to refer to the classes directly. If the top package is java, javax, org, or com, you can use the direct way of referring to Java classes. The easiest way is to use this in conjunction with setting local constants. You can also use import on a single class name, if you miss Java:

JString = java.lang.String 
import org.jruby.Main 
org.jruby.util.ByteList.new 

As you can see, you don't even need to set a local constant. It's easy enough just to call the method you're after directly. So, what do you do when the package you want to reference isn't one of the four shortcut packages? Well, you could use include_class, but you can also find all these reference methods in the Java module. You can find the Java String there, but also all other packages that can exist:

JString = Java::java.lang.String 
HelloWorld = Java::se.ologix.HelloWorld 

This can often make it much more obvious what's happening, even if it's slightly more verbose.

So, when you have a class reference, what can you do with it? Well, you can do most of the things with it from Java code. JRuby also tries to be smart about things, doing implicit conversions of different kinds. Say that you want to call System.out.println from Ruby. You do it mostly the same way:

java.lang.System.out.println(<wbr />"Hello, world") 

It's important to notice that the "Hello, world" string is a Ruby String, not a Java String.

These are different types, but a Ruby String is converted beneath the covers for all obvious cases. For example, a Ruby Fixnum matches against all the integer types in Java, and also the primitive wrapper types. The goal with all this integration is that you won't have to care what type objects are. For almost all cases, things will just work.

To create a new instance of a Java class, just call the new method on it:

m = java.util.HashMap.new 

m.put "hello", "world" 
m.put "goodbye", "life" 

puts m.get("hello") 

iter = m.keySet.iterator 
  while iter.hasNext 
  key = iter.next 
puts "#{key}=#{m.get(key)}" 
end 

As you can see, all these things work more or less exactly as they would in Java. The big difference is that you don't have to write parentheses in most cases, and you don't have to care about the types of variables. Ruby takes care of that. Obviously, this is not the best way to work with Java collections from Ruby. We'll take a look at the Ruby way later in this chapter.

There's one thing to be careful about when creating a new instance of a class and things aren't working as expected. What's wrong with this example?

m = java.util.SortedMap.new 
m.put "Hello", "World" 

Yes, SortedMap isn't a class; it's an interface. In Java that code wouldn't even compile. However, due to Ruby's dynamic nature, this is allowed. JRuby also allows it, but it probably won't do what you expect. This code creates an anonymous implementation of SortedMap that raises an error any time you try to call a method on it. This is the main way you can implement interfaces from Ruby, as you'll see later, so the behavior is expected.

JRuby tries to make method names and variables work as expected from the Ruby perspective. One of the ways it does this is by adding aliases for most methods. Because Ruby usually uses method names do_foo_to_bar, where the Java equivalent would be doFooToBar, JRuby makes aliases available in the underscore style for all methods. That means the HashMap example from earlier could've been written like this:

m = java.util.HashMap.new 

m.put "hello", "world" 
m.put "goodbye", "life" 

puts m.get("hello") 
  iter = m.key_set.iterator 
  while iter.has_next 
key = iter.next 
puts "#{key}=#{m.get(key)}" 
end 

It's maybe not a big difference, but it does make your Ruby code more consistent. The second thing to note is that common getters and setters in Java are also aliased to make things more obvious from a Java perspective. Say you have a Java class called com.example.FooBar, which has three methods called getName, setName, and getValue. These methods are also available in the Ruby attribute accessor form, so this code does what's expected:

foo = com.example.FooBar.new 
foo.name = foo.name + foo.value 

What happens is that all instances of getFoo are available as foo. All instances of setFoo are available as foo=. I encourage you to use these shortcuts because, as I said before, it makes your code more Ruby-like, even if it's calling Java code.

A thing to notice is the interface java.lang.Runnable. Say you have Java code implementing this interface, and you want to call run in a block, say something like this:

t = java.lang.Thread.new 

foo { t.run } 

You could instead use a shortcut:

t = java.lang.Thread.new 

foo &t 

The t variable is automatically turned into a block by the to_proc method that java.lang.Runnable has.

Primitives

Working with primitives from JRuby isn't recommended. Of course, sometimes you need to do so. However, in most cases you can just count on the automatic conversions to handle things correctly. You need to be able to do a few things, though. First of all, you need to get at the primitive classes. There are two ways to do this, the easy and the hard, where hardness is mostly about typing more:
b1 = Java::byte 
b2 = java.lang.Byte::TYPE 

If you remember your Java, the TYPE static variable is available on all the primitive wrappers, giving you easy access to that class instance. You cannot do much with this class, though; not in itself. The only thing it's good for is when you need to provide a Class instance and to handle primitive arrays, which the next section will cover.

If you want to create primitive versions of objects directly, use the wrapper classes instead. These will be automatically coerced into the real primitive types when needed:

a = java.lang.StringBuffer.new 

a.append java.lang.Character.new(13244) 

This call uses the method java.lang.StringBuffer.append(char).

Arrays

Usually you only need to use arrays in the sense of creating them so you can send them in to Java code. Arrays returned from Java code are mostly indistinguishable from the Ruby class Array. In the cases where you need a real Ruby Array, you can call to_a on the Java array.

So, in many cases, it suffices to be able to create a Java array from a Ruby Array. In these cases, the to_java method does what you want it to. The thing making it annoying is that you must specify what type of array it should be. If you don't, the to_java method will create a basic Object[]. So, you can create Java arrays from Ruby Arrays like this:

[1,2,3].to_java        # new Object[] {new Integer(1),  
                       #               new Integer(2), new Integer(3)} 
[1,2,3].to_java :byte  # new byte[] {1,2,3} 
[1,2,3].to_java :char  # new char[] {1,2,3} 
[1,2,3].to_java :float # new float[] {1,2,3} 
# etc 
["str", "str2"]"to_java java.lang.String    # new String[]{"str","str2"} 

As you can see, you can provide either a symbol representing the primitive type, or a class instance. The primitive types you can use are :boolean, :byte, :char, :double, :float, :int, :long, and :short. There are also shortcuts for java.lang.Object, java.lang.String, java.math.BigDecimal, and java.math.BigInteger, as well as for by :object, :string, :big_decimal or :decimal, and :big_integer or :big_int. In most cases, this is all you need. However, sometimes you might need to create an empty array, or an array of more than one dimension. In these cases there are [] helpers on all Java classes in the system. So, for example, you could create an empty String[] with length 3 like this:

java.lang.String[].new 3 

Or you could create it like this:

java.lang.String[3].new 

If you need a two dimensional array, you can just add a new parameter to the call. For example, you do this to create something akin to new String[3][3]:

java.lang.String[].new [3,3] 

or

java.lang.String[3][3].new 

or

java.lang.String[3,3].new 

To work with such an array, just do it the normal way:

d = java.lang.String[3,3].new 

d[0][0] = "Hello" 
d[0][1] = "World" 

Working with arrays in this way works fine with the primitive types too:

Java::byte[256].new 

If you need to reference the Java classes for arrays, you use the same form:

java.lang.String[][][] 

The preceding code returns the class represented by [[[Ljava.lang.String;. In the same manner, this code returns the class [[Z:

Java::boolean[][] 

There are some more caveats and things to know about handling Java code, but we'll look at those as we go along.

Extending Java

Being able to use Java code from Ruby is useful, and in many situations that's all you need. On the other hand, sometimes you want to send code back into Java, in the form of callbacks or handlers. The two major ways you can do this from JRuby are by implementing interfaces or by extending existing classes. JRuby has extensive support for both these situations.

Interfaces

In many cases, it's enough to implement an interface in Ruby and send that back to Java. It's simple to achieve this. Remember that you could call new on an interface class from Java? Well, to implement an interface in Ruby, you just import it as if the interface was a Ruby module. All methods you don't implement will call method_missing, in the manner of regular Ruby code:
class RaiseErrors 
  import org.xml.sax.ErrorHandler 
  def error exception 
    raise "ValidationError", exception.to_s 
  end 
  def fatalError exception 
    raise "ValidationError", exception.to_s 
  end 
  def warning exception 
  end 
end 

builder.set_error_handler RaiseErrors.new 

As you can see, there's nothing strange with implementing interfaces this way, and it works as you expect.


Caution

You should be careful to name methods with the exact same name as their Java counterparts. The aliasing used when calling methods isn't available when implementing them. That's why you have to write fatalError, and not fatal_error.

Caution

If the method you're implementing should return a Java value, make sure the value you return from the method can be coerced into Java without problems.
You can also implement more than one interface, in mostly the same way:
class RunCompare 
  import java java.lang.Runnable 
  import java.lang.Comparable 

  def compareTo o 
    this <=> o 
  end 
  
  def run 
    puts "running..." 
  end 
end

java.lang.Thread.new(RunCompare<wbr />.new) 
java.util.TreeSet.new(RunCompar<wbr />e.new) 

Notice that you can only do this the first time you open up a class. Because Java integration needs to generate a proxy class beneath the covers, this can only be done once. As you can see from the example, you can send instances of this class into everything that can take either a Runnable or a Comparable. One thing to note about implementing interfaces is that up until now you've had to create a new class explicitly. This can pollute the namespace, and isn't always necessary. Many times you just want something like an anonymous implementation of the interface. You can also have that with the impl method:

button.set_action_listener java.awt.event.ActionListener<wbr />.impl { |_,e| 
  e.source.text = "Hello World" 
} 

impl is a method that exists on all interfaces. It takes a block in some form that should be executed when any of the methods in the interface is called. The block should take the same arguments as the method or methods in question, and a last argument that is a string that names the method called. If the interface methods take different lengths of arguments, you can use *args instead. For an explicit example, you could create an implementation of KeyListener like this:

java.awt.event.KeyListener.impl do |method, e| 
  case method 
    when "keyPressed": puts "A key was pressed" 
    when "keyReleased": puts "A key was released" 
    when "keyTyped": puts "A key was typed" 
  end 
end 

Of course, in many cases you don't care what method was called, and in those cases you can just name the method argument to an underscore or something else. If you don't need any arguments to the method, such as Runnable, you can just omit the arguments:

java.lang.Runnable.impl do 
 puts "Running" 
end 

Note that the impl method creates a new anonymous class beneath the covers that calls the block. There's nothing strange going on; the Java integration layer just hides those parts. At the moment, most of the Java integration layer is totally written in pure Ruby, and the parts involved in the impl method don't use any Java at all.

You can use the initialize method in the same way as you do from Ruby. In earlier versions of JRuby this didn't work as expected, and you had to call super explicitly to avoid strange errors. This is no longer true; both initialize and super calls in initialize work as you would like them to:

class Abc  
import java.lang.Runnable 

  def initialize(pu) 
    puts "init #{pu}"  
  end 
def run 
puts "running" 
end 
end 
java.lang.Thread.new(Abc.new(<wbr />"foo")).start 

Of course, interfaces only get you so far. Sometimes you need to be able to work with Java classes too.

Classes

Until recently, there was no way to extend Java classes and have those extensions be visible to Java code. You could inherit from any Java class, and the Ruby code would see all the changes.

But that has changed; now you can extend any Java class, create instances of that extended class, and the changes will be visible to Java code too. Here's a simple example:

class FooStream < java.io.OutputStream 
  def write(p) 
    puts p 
  end 
end 

s = java.io.BufferedOutputStream.new(FooStream.new) 
s.write([1,2,3].to_java(:byte), 0, 3) 
s.flush 

As you see here, the write method gets implemented in Ruby, and you use the array form of write to see that the BufferOutputStream calls the Ruby-implemented write method. It is important to get the number of arguments correct when overriding Java methods. Also note that if you override a method with just one argument, and there are several versions of that method with different argument types, the Ruby overloading will have to take care of them all.

For example, say you did something like this:

class MyStringBuffer < java.lang.StringBuffer 
  def append(v) 
  end 
end 

This would end up overriding all the append methods with one argument in StringBuffer.

That isn't a good thing, but because Ruby doesn't have static type information to help out, it's the only possible route at the moment. All these instructions about arguments work the same for constructors. Calling super in an initialize method ends up calling the constructor that matches the arguments most correctly.

You can implement interfaces and extend a Java class at the same time, using import to implement interfaces the same way you would do when not inheriting from a Java class:

class MyStringBuffer < java.lang.StringBuffer 
  import java.lang.Runnable 
end

Java Collections

One of the things I love most about JRuby's Java integration is its support for working with Java collections in much the same way as you would a Ruby collection. You can easily express many productive things in Ruby that are cumbersome in regular Java. For example, say that you want to sort a collection of FooObjects by an attribute called name. In Java this could look something like this:
Collections.sort(foos, new Comparator() { 
  public int compare(Object o1, Object o2) { 
    return ((FooObject)o1).getName().compareTo(((FooObject)o2).getName()); 
  } 
}); 

That's pretty much the best case scenario, provided the objects don't implement Comparable. How would you do the preceding with JRuby?

foos.sort_by {|o| <a href="%22http://o.name%22" target=""_blank"">o.name</a>} 

Of course, if you have Facets or Rails loaded you can do it like this:

foos.sort_by &:name 

Some difference, huh? So, Java collections get almost all the regular Ruby collection methods for free. In plain English, these are the current extensions to Java collections:

java.util.Map has an each method, a [] method, and a []= method. Map mixes in Enumerable and gets all the methods there for free. The interface java.lang.Comparable has the <=> method, and mixes in the Ruby module Comparable. All classes descended from java.util.Collection have each, <<, +, -, length, and mix in Enumerable. In this way, Java lists are almost indistinguishable from Ruby collections. Further, java.util.List has [] and []=, and also implements fast versions of sort and sort! that use java.util.Collections.sort beneath the covers.

You can create Java collections in much the same way as other Ruby collections. A Ruby Hash implements java.util.Map, and a Ruby Array implements java.util.List. This means you can create a new LinkedList like this:

java.util.LinkedList.new [1,2,3] 

One thing to note about all these methods is that for Lists, the [] and []= don't implement slicing or ranges at the moment. They are just wrappers for get and set. If you want slicing, you can use the to_a method:

a = java.util.ArrayList.new [1,2,3,4,5,6] 
a.to_a[2..4]  

Note that to_a returns a new array, and changes to that array won't reflect on the original Java collection. As you might remember, to_a exists in Enumerable, so you can call to_a on Maps too:

h = java.util.HashMap.new 
h['a'] = 'b' 
h['c'] = 'd' 

p h.to_a        #=> [['c','d'],['a','b']] 

The more useful parts of Enumerable all work as you would expect. I find myself using collect, select, and sort_by most, because it takes so much code to do the equivalent in Java.

inject, grep, reject, partition, and all the others work well too.

It's important to remember that if you find something that would be useful to add to all the Java collection classes, you can do so easily. You shouldn't use the regular Ruby class syntax for this, though, because there's a fair amount of magic involved. However, say you'd like all Java collections to have an implementation of each_cons. (each_cons returns each pair of values consecutively available in the collection). You could add the each_cons method with a little help from extend_proxy like this:

JavaUtilities.extend_proxy('java.util.Collection') {  
  def each_cons(&block) 
    iter = iterator 
    if iter.hasNext 
      first = iter.next 
      while iter.hasNext 
        val = first, iter.next 
        first = val[1] 
        block.call *val 
      end 
    end 
    nil 
  end 
} 

As you can see, the method definition works as usual, but you have to execute it within a block sent to JavaUtilities.extend_proxy. Say you have this defined and execute this code:

a = java.util.LinkedList.new [1,2,3,4,5] 
a.each_cons do |v| 
  p v 
end 

You'd see this:

[1,2] 
[2,3] 
[3,4] 
[4,5] 

The method would be available to all code implementing java.util.Collection, even new classes loaded after the fact.

Gotchas

As noted in the introduction to this chapter, several things can go wrong when interacting between Ruby and Java code. Most of these are caused by Ruby being much more dynamic than Java, which means it's hard for JRuby to know whether what you did was a mistake, or meant to be that way. So, when doing things that would have generated warnings or compile errors in Java, JRuby happily lets you go ahead with it, because it can't know if you intended to do what you did.

The failures when dealing with Java integration can also be cryptic. Sometimes everything looks as if it's working fine, but beneath the covers, something has gone wrong. In other situations, interpretation can shut down without any message at all. However, usually you get something to work with, at least. When researching the reason for a Java integration failure, it helps to check out the JRuby Wiki and JRuby FAQ. You're probably not alone in experiencing the problem in question. You can find links to these resources in Appendix C.

So, where should you look first when trying to find the cause of a strange error? First of all, check if the method you're calling takes the same amount of arguments that you've provided to it. Java programmers are used to the compiler finding this type of problem, but that isn't possible with JRuby, so the wrong number of arguments is the number one cause of Java integration problems. Another common cause of problems is that JRuby cannot find a method with matching argument types. These are a little harder to see, but in many cases it's caused by you sending in another value than you intended to.

When creating implementations of interfaces and extending classes, make sure that the argument arity (number of arguments) matches. If it doesn't, JRuby will happily continue running, and you won't notice anything wrong until the method you thought you overrode is called, but the wrong operation is executed. Also remember the earlier caution about overriding overloaded methods with the same argument arity. If you don't handle all cases, things invariably won't work.

All these problems can be easily remedied and combated by having extensive test suites covering both your Ruby and Java code. Because it's easy to write tests in Ruby that test Java code, there isn't any good reason not to do it. If you test all your code, these dynamic failures will be found before you try to execute the code in production. RSpec is an especially good framework for testing, particularly when programming with Test-Driven Development. Otherwise, the Test::Unit framework that comes with Ruby's standard library works well too. Just remember to test the Java integration code closely, because it's easier to get errors in those areas of your code.

Using Ruby from Java

As I mentioned in the beginning of this chapter, it can sometimes be useful to be able to call out to Ruby from Java. You could accomplish many things with this; some interesting examples would be to use Ruby as an extension language for writing macros and plug-ins to an application. You could also easily write a Java application that's configured by a custom configuration DSL, executed by JRuby. Of course, you can do many other things too.

What I'll show you here is three different ways to call out to custom Ruby code from Java.

I'll show a complete Java program for each approach, but they are about as minimal as they can get. The approaches are ordered by level of recommendation. That is, you should probably use JSR223 if you can; if not, use Bean Scripting Framework (BSF), and if that also fails, use the JRuby runtime directly.

The JRuby Runtime

Let me begin this section by saying this once again: you probably shouldn't call out to the JRuby runtime directly. There is almost certainly a better solution. The reason it's better to use one of the standardized frameworks is that the runtime demands that you wrap and unwrap values manually. There are many more things to keep in mind, and you can't be certain that it will just work, as BSF and JSR223 pretty much guarantee.

So, on to the example program. You want to get some data in there too, so the example program will print out the result from adding 13 to a value you send in from Java. Here's the code to achieve this (and this is pretty much the most minimal Ruby runtime code there can be). It will be more complex for more complicated tasks.

package c6; 

import org.jruby.Ruby; 
import org.jruby.javasupport.JavaEmbedUtils; 

public class JRubyRuntime { 
  public static void main(String[] args) { 
    Ruby runtime = Ruby.getDefaultInstance(); 
    runtime.getGlobalVariables().set("$label",  
                 JavaEmbedUtils.javaToRuby(runtime, 27)); 
  
    runtime.evalScriptlet("puts 13 + $label"); 
  } 
} 

At the moment, you need just two classes. First, the Ruby class is the central runtime. The JavaEmbedUtils class provides some utilities that can be useful for working with Ruby code.

So, what's happening here? Well, you define a new Java class and its main method. The easiest way to get hold of a Ruby runtime is to call the getDefaultInstance method. It almost always gives you what you want, if you don't want to redirect the engine's input and output, or something fancy like that. That runtime is something you'll have to use extensively when interacting with Ruby code.

The next line gets the GlobalVariables object, and sets the label global . The javaToRuby method on JavaEmbedUtils is handy, because it's often a nasty procedure to convert a Java object or primitive into its Ruby counterpart.

After you've set the global, you call the method evalScriptlet . This is one of about a dozen ways you can evaluate Ruby code with a runtime. Other versions include preparsing the code and sending the parsed abstract syntax tree (AST) to the evaluator. There are other possibilities, too.

To compile and run this code, place it in a file called c6/JRubyRuntime.java and execute these commands:

javac -cp $JRUBY_HOME/lib/jruby.jar c6/JRubyRuntime.java 
java -cp $JRUBY_HOME/lib/jruby.jar:. c6.JRubyRuntime 

If you're lucky, you'll see the result of adding 13 to 27. Amazing! To give you a glimpse of how you can call methods on objects you get back and things like this, this snippet shows how to get hold of a JRuby object for a number, and calling a Ruby method on that object called "succ", which returns the next value for the integer:

IRubyObject num  = JavaEmbedUtils.javaToRuby(runtime, 27); 
IRubyObject num2 = num.callMethod(runtime.getCurrentContext(), "succ");

When this code is executed in a relevant context, num2 will contain a Ruby Fixnum with the value 28. You probably understand why I don't recommend working directly with this interface. It works well for implementing libraries around the engine, but it's not that useful for application development.

Bean Scripting Framework

Bean Scripting Framework, or BSF for short, is an open source project hosted by Apache Jakarta. It was originally developed by IBM and later open sourced. The purpose is to make different so-called scripting engines available to a Java program with a language-agnostic interface. That means the code looks almost exactly the same, whether you are working with JavaScript, TCL, Python, Ruby, or any other language with BSF bindings. You need to have BSF and Commons Logging on your CLASSPATH for this example to work.
package c6; 
import org.apache.bsf.BSFManager; 

public class JRubyBSF { 
  public static void main(String[] args) throws Exception { 
    BSFManager.registerScriptingEngine("ruby",  
                                       "org.jruby.javasupport.bsf.JRubyEngine", 
                                       new String[] { "rb" }); 
                                       
    BSFManager manager = new BSFManager(); 
    manager.declareBean("label", "hello world", String.class); 
    manager.exec("ruby", "(java)", 1, 1, "puts $label"); 
  } 
} 

First of all, you only need to import the BSFManager. This is the central point for all evaluations with BSF, and should be the only thing needed. You first need to register Ruby as a script engine, so that BSF can recognize which evaluator to use. The String array passed to registerScriptEngine details which file endings this script engine should evaluate. After you've registered Ruby, you create a new manager, and then declare a bean on that manager. As you can see, there's no mention of wrapping or converting the object. BSF does this completely beneath the covers. The exec method takes several parameters that you can use for detailed error reporting. The first string is which language you're evaluating. The next is the file name this code belongs to. The numbers are line and column indexes where this code comes from.

To compile and run this code, place it in c6/JRubyBSF.java and execute these commands:

javac c6/JRubyBSF.java 
javac -cp ${CLASSPATH}:. c6.JRubyBSF 

If everything works as expected, you should see hello world printed on your console.

Of course, this approach is more verbose than using the JRuby engine directly. That is mainly caused by the initial setup and the need to provide some extra information to the evaluation process. You'll find that this code is much easier to maintain, though, and when the JRuby engine changes its interface (which it will do, over and over again), you won't have to change your own code to accommodate. BSF takes care of that. Also, if you would like to add another script engine for some reason, it will be easy to include it in the current setup, because BSF handles one language much like the other.

JSR223--Java Scripting

The superior way of running JRuby from your own application is through the Java Scripting API, also known as JSR223. It became a standard part in Java with the Java 6 SE release. The goals of JSR223 are pretty much the same as those of BSF. Simply enough, you should be able to use a scripting language with a standard Java installation, without any extra libraries except those for the engine of the language you're interested in. Java bundles a JavaScript engine, and will probably bundle more languages in the future.

The usage of Java Scripting is similar to BSF. You need to have Java 6 installed, and you need to download the JSR223 Engines package from its home page (http://scripting.dev.java.net). After you've done that, you should unpack it, and add the file jruby/lib/jruby-engine.jar to your CLASSPATH . With all that done, you should be set to go.

package c6; 

import javax.script.ScriptEngine; 
import javax.script.ScriptEngineManager; 

public class JRubyJSR223 { 
  public static void main(String[] args) throws Exception { 
    ScriptEngineManager m = new ScriptEngineManager(); 
    ScriptEngine rubyEngine = m.getEngineByName("jruby"); 
    rubyEngine.getContext().setAttribute("label", new Integer(4),  
                                         ScriptContext.ENGINE_SCOPE); 
    rubyEngine.eval("puts 2 + $label"); 
  } 
} 

As you can see, the concepts are mostly the same. You create a new ScriptEngineManager, and fetch an engine for JRuby from that. You then set an attribute named label on the context of the engine. The value should be 4 and the scope should be ENGINE_SCOPE, which is mostly the same as global scope. You then evaluate the same code as before. If everything is done correctly, you should see a 6 appear when this code runs.

In all these examples, you mostly do the same thing: get hold of an engine, set some value for a name, and then evaluate some code. The big positive about using Java Scripting is that it's available everywhere. You need the JRuby engine JAR, and then it will work. You don't have to handle BSF and Commons Logging, and you get all interaction with the script engine done in such a way that you'll never have to work with the engine's internal objects. Because those internal objects can be quite cumbersome in JRuby, that's a good thing.

Summary

As you've seen in this chapter, the Java integration issues can be complicated, but in most cases things work as you should expect them to. JRuby tries hard to get out of your way and do things in the most reasonable way possible. That doesn't mean there aren't corner cases where it's hard to do right without hints from the programmer, but JRuby also provides ways for you to help it.

Because the Java integration is one of the most important parts of JRuby, this chapter detailed quite a bit of information about this subject. You can turn back to this text when something is unclear in the example code in the rest of the book. This chapter didn't cover all there is to know about Java integration, but it did cover much of it. You can find more updated information on the Internet.

Now it's time to get started with the next project application, called CoMpoSe.


This sample chapter is an excerpt from the book, Practical JRuby on Rails Web 2.0 Projects: Bringing Ruby on Rails to Java, by Ola Bini, Copyright 2007, Apress, Inc.

The source code for this book is available to readers at http://www.apress.com/book/view/1590598814.

Ola Bini is a longtime developer from Sweden who started at an age of 9 years with Basic, on an Apple IIc; from there on he learned C, C++, Assembler, Lisp, Java, Ruby, and various other languages. He has no formal education except for a few Sun Java certifications. He has worked with system development at Karolinska Institutet since 2001 and runs his own consulting company (OLogix Consulting) in his spare time. He has contributed to various open source projects and is one of the three core developers of the JRuby project.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.
This is a Organisation (No members)


Comments and Discussions

 
-- There are no messages in this forum --