Making the Complex Usable with JRuby

Brian Sam-Bodden
  • May 2010
  • Ruby
  • JRuby

One of the factors that made Java hugely successful is the myriad of open source libraries and frameworks. The successful ones have had now a decade or more to mature and grow. A side effect of being successful is both intended and unintended complexity. In the next few pages I will show you how a JRuby Domain Specific Language (DSL) can breed new life into an old powerhouse Java library.

In recent years it has become clear that the lasting legacy of Java is not Java “the language” but Java the platform and specially the open-source community that it has fostered over the last 15 years.

Many developers (including yours truly) experienced a great deal of success with the Java language but as the years went by it started to become apparent that the safety “features” of the language that made it so popular were getting in the way of progress. This became painfully clear in the area of Web Development and it became borderline embarrassing when we were introduced to the Ruby on Rails framework and witness the productivity boost that the combination of a dynamic language like Ruby and a DSL-based approach to a framework brought to the table.

Today, we recognize that the Java language is just another player in the world of the Java Virtual Machine. The JVM is an amazing and pervasive piece of software that in the early days of Java played second fiddle to the language. This is no more, everyone know acknowledges that this amazing, highly tuned, multiplatform computing engine dominates the present and permeates the future of computing.

As I write this article there are more than 200 language implementations available for the JVM, many of them now being used in the “enterprise” and not only in academia. I strongly believe that this decade marks the official beginning of the “polyglot” era of programming with the JVM smack in the center of this new world.

Ruby

Ruby is a dynamically typed language created by Yukihiro “Matz” Matsumoto. Ruby is a general purpose, multi-paradigm language inspired primarily by Smalltalk, Perl, Lisp and Eiffel.

As David Heinemeier Hansson puts it “Beauty leads to happiness, happiness leads to productivity, thus beauty leads to productivy”. If you have never worked with Ruby, after a few hours coding it will immediately hit you. Things just make sense! My first thought was “there is no way in hell this was build by committee!” Indeed it wasn’t. Matz set out to create a language for him to enjoy in which the main ideals were that programming should be fun and that the language should behave in predictable ways for those experienced with it core principles (this is often referred as the “Principle of Least Surprise”.

Rather than praise Ruby, let me show some quick examples the simplicity of Ruby. Say we have to do some basic, brain-dead file manipulation; load a text file and print its contents line by line. In Java we would do something along of the lines of:

package com.integrallis.braindeadio;
          
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
          
public class MyFileReader {
  public static void main(String[] args) {
    try {
      BufferedReader in = new BufferedReader(new FileReader("read_me.txt"));
      String str;
      while ((str = in.readLine()) != null) {
        System.out.println(str);
      }
      in.close();
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
}

Listing BSB-1

After years of doing Java we have come to accept code like that shown in Listing BSB-1. I equate this code to wearing a deep-ocean, mesh-reinforced diving suit to go to the beach. If you go to the beach there is the possibility that you might go under, and that in the depths of the ocean a great white might come after you. Yes, it is possible but also highly improbable.

With Ruby we don’t even have to commit to create a permanent record of the operation performed in Listing BSB-1, we can get our work done directly in the Interactive Ruby Shell (IRB).

>> File.open('read_me.txt', 'r').each do |line| 
?>   puts line
>> end
Flexibility
Power
Simplicity
Beauty
=> #
>>

Listing BSB-2

As seen in Listing BSB-2, Ruby provides a File class, which has an open method that takes the name of the file and a file mode (in this case ‘r’ for read-only). So far so good, next is where the Ruby Way kicks in! The result of the call to open returns a File which mixes-in the Enumerable module, which gives it an iterator via the each method. The each method expects a block, the code wrap in the “do” and “end”. We can think of a block as an anonymous function and one of their basic usages of a block is loop abstraction. As opposed to Java, with Ruby we don’t need to peer into the internals of what we are iterating over, instead we provide a function with a known interface to the owner of the collection we want to iterate over. The result is less code and better encapsulation!

But Ruby was designed by a programmer and is therefore for programmers. Methods that would make sense to be included in the core of the language usually are! Therefore it is no surprise that the File class also has a “readlines” method, which returns an array and what do you know! Ruby knows how to print an Array:

>> puts File.readlines('read_me.txt')
Flexibility
Power
Simplicity
Beauty
=> nil
>>

Listing BSB-3

As a fairly accomplish Java developer I can tell you without reservation that Ruby is the most productive environment I’ve ever develop applications in.

JRuby

JRuby is pure Java implementation of Ruby that started in the early 2000s. JRuby aims to bring the beauty and simplicity of Ruby to the Java world and bring the Java ecosystem to the Ruby world. This means that you gain the best of both worlds; From a Java application you get to use Ruby applications and libraries and from a Ruby application you get access to Java Libraries (in a Ruby-eske way).

JRuby also runs significantly faster than most other Ruby implementations (if you don't count VM startup), it supports native threads, Unicode and can be fully compiled ahead of time or just in time.

JRuby also bridges the gap left by Java the language. Dave Thomas once described Java as “the safety scissor of the programming world”. Following that analogy, Ruby is then a samurai sword and via JRuby it brings powerful features to the Java world: Blocks, Closures, Open Classes, Meta-programming and Duck-typing all crucial ingredients when building a Domain Specific Language.

Calling Java From JRuby

In order to enable Java features in a Ruby program we need to add the ‘java’ module to our program. We can also include specific Java packages and group them under a Ruby module (modules are like un-instatiable, namespaced code containers; they can contain classes, methods, constants and free-flowing code)

require 'java'
          
module Java3D
include_package "javax.media.j3d"
include_package "com.sun.j3d.utils.geometry"
include_package "com.sun.j3d.utils.universe"
end
          
module Swing
include_package "java.awt"
include_package "javax.swing"
end
          
module Math
include_package "java.lang.Math"
end

Listing BSB-4

Requiring the Java module gives your Ruby code access to the bundled Java libraries. We used three modules to include and namespace specific Java packages: Java3D; containing multiple Java3D packages, Swing; containing the root AWT and Swing packages and Math; exposing the java.lang.Math class.

We these powerful Java libraries in place we can now let Ruby loose and write some code to use the Java 3D API. The Ruby class ThreeDimensionalCube creates a rotating, multi-colored cube; the power of Java meets the ease of development of Ruby:

class ThreeDimensionalCube
  include Java3D
  include Swing
  include Math
          
  def self.create_canvas
    config = Java3D::SimpleUniverse.get_preferred_configuration
    canvas = Java3D::Canvas3D.new(config)
          
    scene = create_scene_graph
    scene.compile
          
    # SimpleUniverse is a Convenience Utility class
    universe = Java3D::SimpleUniverse.new(canvas)
          
    # This will move the ViewPlatform back a bit so the
    # objects in the scene can be viewed.
    universe.get_viewing_platform.set_nominal_viewing_transform
    universe.add_branch_graph(scene)
          
    canvas
  end
          
  def self.create_scene_graph
    # Create the root of the branch graph
    obj_root = Java3D::BranchGroup.new
          
    # rotate object has composited transformation matrix
    rotate = Java3D::Transform3D.new
    temp_rotate = Java3D::Transform3D.new
          
    rotate.rot_x(PI / 4.0)
    temp_rotate.rot_y(PI / 5.0)
    rotate.mul(temp_rotate)
          
    obj_rotate = Java3D::TransformGroup.new(rotate)
          
    # Create the transform group node and initialize it to the
    # identity.  Enable the TRANSFORM_WRITE capability so that
    # our behavior code can modify it at runtime.  Add it to the
    # root of the subgraph.
    obj_spin = Java3D::TransformGroup.new
    obj_spin.set_capability(Java3D::TransformGroup::ALLOW_TRANSFORM_WRITE)
          
    obj_root.add_child(obj_rotate)
    obj_rotate.add_child(obj_spin)
          
    # Create a simple shape leaf node, add it to the scene graph.
    # ColorCube is a Convenience Utility class
    obj_spin.add_child(Java3D::ColorCube.new(0.4))
          
    # Create a new Behavior object that will perform the desired
    # operation on the specified transform object and add it into
    # the scene graph.
    y_axis = Java3D::Transform3D.new
    rotation_alpha = Java3D::Alpha.new(-1, 4000)
          
    rotator =
      Java3D::RotationInterpolator.new(rotation_alpha, obj_spin, y_axis,
      0.0, PI * 2.0)
          
    # a bounding sphere specifies a region a behavior is active
    # create a sphere centered at the origin with radius of 1
    bounds = Java3D::BoundingSphere.new
    rotator.set_scheduling_bounds(bounds)
    obj_spin.add_child(rotator)
          
      obj_root
  end
          
  def show
    frame = Swing::JFrame.new("3D Cube")
    frame.layout = Swing::BorderLayout.new
    frame.add("Center", ThreeDimensionalCube.create_canvas)
    frame.default_close_operation = Swing::JFrame::EXIT_ON_CLOSE
    frame.set_size(256, 256)
    frame.visible = true
  end
          
end
          
cube = ThreeDimensionalCube.new
cube.show

Listing BSB-5

We run the example (note that there is no need for a main method) with the last two lines in Listing BSB-5. First we instantiate a ThreeDimensionalCube object and then call the show method on said object. The result is shown in Figure BSB-1.

Bsb jruby 01

Figure BSB-1

Our Problem Domain: Dealing with XML

One of the pain areas of Java development has always been dealing with XML. We could attribute this to the overuse (or is it abuse) of XML in the Java world. But if you were unlucky enough to deal with the SAX and DOM API in Java you probably don’t have any hair left by now.

I was in the same boat, until I discovered XOM (XML Object Model) is one of the most versatile Java XML libraries in existence. XOM is a Tree-based API like DOM and JDom and it aims for correctness, simplicity and performance. In the Java world XOM provided the best of both worlds, fast parsing and a tree-model that allows navigation of the XML documents.

Using XOM is easy to create well-formed XML documents, for example Listing BSB-6 shows a “Hello World” example available with the XOM distribution.

import nu.xom.Document;
import nu.xom.Element;
          
/**
*  * Hello World!
*/
public class HelloWorld {
          
  public static void main(String[] args) {
    Element root = new Element("root");    
    root.appendChild("Hello World!");
    Document doc = new Document(root);
    String result = doc.toXML();
    System.out.println(result);
  }
}

Listing BSB-6

In listing BSB-7 shows another (more complex) example available from the XOM distribution. This example prints the headlines from an RSS feed.

import java.io.IOException;
import nu.xom.Builder;
import nu.xom.Element;
import nu.xom.Nodes;
import nu.xom.ParsingException;
          
public class RSSHeadlines extends MinimalNodeFactory {
  private boolean inTitle = false;
  private Nodes empty = new Nodes();
          
  public Element startMakingElement(String name, String namespace) {              
    if ("title".equals(name) ) {
      inTitle = true; 
    }
    return new Element(name, namespace);             
  }
          
  public Nodes makeText(String data) {        
    if (inTitle) System.out.print(data);
    return empty;      
  }
          
  public Nodes finishMakingElement(Element element) {
    if ("title".equals(element.getQualifiedName()) ) {
      System.out.println();
      inTitle = false;
    }
    return new Nodes(element);
  }
          
  public static void main(String[] args) {
    String url = 
      "http://bbc.co.uk/syndication/feeds/news/ukfs_news/world/rss091.xml";
    if (args.length > 0) {
      url = args[0];
    }
                 
    try {
      Builder parser = new Builder(new RSSHeadlines());
      parser.build(url);
    }
    catch (ParsingException ex) {
      System.out.println(url + " is not well-formed.");
      System.out.println(ex.getMessage());
    }
    catch (IOException ex) { 
      System.out.println(
      "Due to an IOException, the parser could not read " + url
      ); 
    }
  }
}

Listing BSB-7

If you ever built or parsed XML with any other Java API (other than JDOM or dom4J) you would be able to appreciate what XOM can do for you.

Close but Not Cigar!

Although XOM does a great deal of heavy lifting for you, if you are Rubyist, Listings BSB-6 and BSB-7 feel like they reveal too much about how XOM works internally and hide the intent of the code with too much plumbing.

Ruby has an XML building library, created by Jim Weirich called XML Builder (or Builder for short). Builder is a Ruby DSL for constructing XML documents. For example, to recreate the “Hello World” example we could do write a simple Ruby program like that shown in Listing BSB-8

require 'rubygems'
require 'builder'
          
builder = Builder::XmlMarkup.new
builder.root "Hello World!"
          
puts builder.target!

Listing BSB-8

So what makes Builder a DSL? Note the call to the method “root”. As a Java developer your first reaction is “root must be a method of this builder object, right?” Wrong! Under the covers the call to the method root is being trap by a hook method called method_missing. Think of it as an event listener that is invoke in the event that an object is sent a message it cannot handle.

Internally, Builder is seeing that we are trying to invoke a non-existent method called “root” and instead adds an XML tag to the underlying document being created.

Builder is not as robust or correct when it comes to building XML when compared to XOM and it is nowhere near as fast. Builder, as clearly stated by its name, only “builds”.

Being the greedy developer that I am, I wanted the best of both worlds. How can I get Builder’s semantics for XML building mixed with the XOM’s ability to efficiently parse and navigate XML with XPath?

Excemel: A JRuby DSL for XML Manipulation

The answer to the previous question is a simple one: Use XOM but make it look like Builder. Enter Excemel! A simple JRuby DSL for XML manipulation. Excemel borrows Builder’s semantics for building an XML document backed by an underlying XOM model. Excemel also allows you to parse and manipulate an existing document and exposes XPath functionality in a simple way.

Let’s start with the simple Hello World example again, as shown in Listing BSB-9.

require 'excemel'
          
doc = Excemel::Document.new :root => "root"
doc.text! "Hello World!"
          
puts doc.to_pretty_xml

Listing BSB-9

Excemel uses XOM under the cover to build an XML document. It uses Ruby’s method_missing in the same fashion that Builder does as shown in Listing BSB-10.

require 'excemel'
          
xm = Excemel::Document.new :root => "html" # 
xm.head {                                  #   
  xm.title "History"                       #     
}                                          #                                
xm.body {                                  #   
  xm.comment! "HI"                         #     
  xm.h1 "Header"                           #     <h1>Header</h1>
  xm.p "paragraph"                         #     <p>paragraph</p>
}                                          #   
          
puts "<---- Plain XML ----->"
puts xm.to_pretty_xml
          
puts "\n<---- Use XPath to find the title ----->"
puts %[the title is #{xm.query("//title")}]

Listing BSB-10

But that’s where the similarities end. Excemel can do some things that Builder can’t, like reading XML directly from a file as shown in Listing BSB-11.

require 'excemel'
          
doc = Excemel::Document.new :file => "cd_catalog.xml"
puts doc.to_pretty_xml

Listing GAN-9

Listing GAN-10 demonstrates the invocation of the method in Listing GAN-9.

gsmEvent "stage", { 
  transitions from:"modified", to:"staged"
}

Listing BSB-11

Listing BSB-12 shows the full power of a JRuby DSL. Remember the RSS headline extractor example shipped with XOM? With Excemel we can bring intent back to the forefront of our code. In about 1/3 of code of the original XOM version Excemel can more clearly perform the same task (with the same efficiency and accuracy, it is XOM under the covers!)

require 'excemel'
          
doc = Excemel::Document.new :url =>   
  "http://news.bbc.co.uk/rss/newsonline_uk_edition/world/rss.xml"
headlines = doc.query '//title'
headlines.each {|h| puts h }

Listing BSB-12

Conclusion

I hope that this short walk through the world of JRuby will open your eyes to the power of mixing a powerful dynamic language with a robust, fast and battle-tested Java library. Dynamic languages on the VM are paving the way of the future. Groovy and Ruby via JRuby can help us improve the current state of Java and make programming fun again!

References

Share