Cheri: A Builder Framework for (J)Ruby and Java
Download (Zip)
VERSION 0.0.1
FEATURE PREVIEW 2
Java Support    Types and Arrays
A Builder Framework for (J)Ruby and Java


Update: 9 June 2007: At long last, the first (real) beta version of Cheri, the successor to JRBuilder, has been released. You can download it from its new home at RubyForge, or just type
   gem install cheri
at the nearest command prompt. In addition to Cheri::Swing, the module that supersedes JRBuilder, Cheri includes Cheri::Html and Cheri::Xml, as well as a tool for building builders, and a JRuby runtime browser, Cheri::JRuby::Explorer, written using Cheri::Swing and Cheri::Html. Whew! I hope it will prove worth the wait. This is a beta release (version 0.0.2), but the Cheri::Swing component is pretty stable. Documentation will be up at the site in the next 24 hours.

Update: 6 Mar 2007: JRuby 0.9.8 has been released! Get it now at the download page.

I ended up incorporating pretty much all of the extended Java array features from the Cheri Java preview into JRuby 0.9.8 itself, though in a slightly different form, so download JRuby 0.9.8 and you'll have it. Cheri remains the new name of the JRBuilder/Bindery project, but I've been too busy the past few weeks to get new features out. Once I wrap up one more project for JRuby (a new and improved sprintf), I'll get back to it, so stay tuned to this space!

Cheri is the new project name for the JRBuilder project, a builder for hierarchically-structured applications, especially (though not exclusively) geared towards use with JRuby. JRBuilder includes a Groovy-like SwingBuilder tool, as well as a proof-of-concept implementation of data binding called Bindery.

Cheri (which I pronounce 'cherry', for reasons I'll explain below, but you may say 'sher-EE' if you prefer) is a major reworking and expansion of the JRBuilder code, opening up the architecture (if JRBuilder can be said to have any at all) to make it easy to add and extend features. While retaining its emphasis on Java-Ruby integration in the JRuby environment, Cheri aims to be a more broadly useful tool.

While some pieces of Cheri/JRBuilder remain in scattered clumps on my garage floor as I finish rebuilding the engine (now where did I put that dang part?), other components are usable now. One of them I offer you today.

One area that remains under-addressed in JRuby-Java integration is array creation and conversion. (This is a topical issue; just moments ago [as I write this] I ran across a post by Charles Oliver Nutter on jruby-dev that briefly mentioned needed array support, in the context of a broader discussion of Java integration in JRuby).

The Cheri::Java module (download preview) provides an easy-to-use and (I think) natural, Rubyesque solution to array creation and conversion. It adds java_array methods (both class and instance) to the Ruby Array class, and Ruby=>Java conversion methods to the Ruby Numeric and String classes.

The java_array methods can create or convert arrays of arbitrary dimensionality, up to the maximum supported by the VM (255 in most cases). An initial fill value may be specified for new arrays, and when converting to an array with larger (or more) dimensions. Cheri attempts to sensibly convert seemingly insensible Ruby arrays, such as [1,[2,3],[4,[5,6]]]. (It creates a Java array with the dimensions required to hold the largest/most dimensions of the Ruby array, then propagates any value found where an array was expected.)

Cheri accepts simple names for common types, such as :Int (or 'Int') for java.lang.Integer. Users can create custom conversion routines (procs), either inline (as a block to java_array), or by adding them to Cheri's type-converter table (optionally with a custom simple name/symbol). This feature will be especially useful for conversions to java.util.Date.

The conversion methods (obj.java_byte, obj.java_float, etc.) are particularly useful for setting values in arrays of Java primitives (and their object counterparts); except for arrays of long/Long and double/Double (which back Ruby's Fixnum and Float, respectively), these cannot be set using unconverted Ruby values. While I expect that, initially, some people might be leery of the conversion syntax, I will point out that it is analogous to Ruby's to_i, to_f, to_s, and to_sym methods, as well as byteValue, floatValue, and so on defined in java.lang.Number. The java prefix should prevent any collision or confusion with methods defined in other Ruby modules. (I did consider using to_java_byte, to_java_array, and so on, but felt this was unnecessarily cumbersome.)

Much of Cheri's array/type functionality may (and should) eventually be implemented in JRuby directly, if not necessarily in the same form (though I hope Cheri's approach will serve at least as an inspiration, if not a model, for JRuby's implementation). Until then, it is available today in Cheri, so download it and give it a try!

Stay tuned for Cheri's full builder platform release, coming out (possibly in stages) over the next couple of weeks.

About the name Cheri: I originally (about a month ago) came up with the name Cherry, which I thought was clever because it's catchy, ala Groovy, and because it's in more or less same color family as Ruby, and because fruit names are all the rage these days (see Hpricot). A search for 'cherry ruby' on Google turned up no conflicting uses, just some wines and (of course) rubies. (I searched 'cherry java' as well, with the same lack of results.) It appeared to be a safe choice.

Then, a couple of days ago, I started thinking about building a Gem, and headed over to RubyForge. Sure enough, they already had a Cherry (an XML parser with templating, sounds cool, though I haven't had a chance to try it). Well, by now I'm too invested in the name to let it go completely (the Cheri incarnation of JRBuilder's Bindery is B'ing), not to mention all those hours slaving over GIMP to create logos, though I had to redo some. So Cheri it is. (I looked it up; it is the Middle English spelling of Cherry.)

Please feel free to contact me with comments or suggestions. My email address is in the download (in the code and the LICENSE file).

Bill Dortch
14 February 2007

require 'cheri_java_preview'

# Create a new, empty array of java.lang.Object,
# length 10
arr = Array.java_array 10
=> #<#<Class:01x111bfbc>:0x4310d0 @java_object=[Ljava.lang.Object;@1700391>

# Set the first value to a java.lang.Byte, value 5
arr[0] = 5.java_byte
=> #<Cheri::Java::JByte:0xf894ce @java_object=5>

# Create a 5 x 5 array of Java doubles, initialized to PI
arr = Array.java_array :double, [5,5], 3.14159
=> #<#<Class:01xdfcb47>:0x2d7440 @java_object=[[D@272961>
arr[3][3]
=> 3.14159

# Create a Java primitive byte array from a Ruby array
arr = [1,2,3,4,5,6,7,8].java_array :byte
=> #<#<Class:01xff9053>:0x15f157b @java_object=[B@497904>
arr[5]
=> 6
# Set the first value to a Java primitive byte
arr[0] = 99.java_byte
=> #<Cheri::Java::JByte:0x8d0b0f @java_object=99>

# Create a 2-dimensional java.math.BigDecimal array from
# a Ruby array
arr = [[1,2,3],[4,5,6],[7,8,9]].java_array :decimal
=> #<#<Class:01xe33e18>:0x1d686c1 @java_object=[[Ljava.math.BigDecimal;@164b9b6>
arr[2][2]
=> #<Cheri::Java::JBigDecimal:0x128edf2 @java_object=9>

# Change the value at [1][1] to (java.math.BigDecimal) 99.99
arr[1][1] = 99.99.java_decimal
=> #<Cheri::Java::JBigDecimal:0x7b4703 @java_object=99.99>

# Create an n-dimensional array from an irregular Ruby array
arr = [1,[2,[3,[4,[5,[6,[7]]]]]]].java_array :string
=> #<#<Class:01x1a5af9f>:0x1b09282 @java_object=[[[[[[[Ljava.lang.String;@8ab08f>
arr[0][0][0][0][0][0][0]
=> "1"

# Supply a custom converter inline (silly example, just squares the values)
JInteger = java.lang.Integer
arr = [1,2,3].java_array(:Int) {|val| JInteger.new(val*val) }
=> #<#<Class:01x1c45731>:0x107f742 @java_object=[Ljava.lang.Integer;@171120a>
arr[2]
=> 9

# Create a custom String->Date converter and add it to
# Cheri's type-converter table, with a custom symbol
JDate = java.util.Date
# if we end up delegating to Cheri, we'll need the class name
SDate = 'java.util.Date'.to_sym
SimpleDateFormat = java.text.SimpleDateFormat
format = SimpleDateFormat.new('yyyy-MM-dd')

# create the proc, could use lambda{} as well, or Method.to_proc
date_converter = Proc.new { |val|
  date = nil
  if val.kind_of?(String)
    begin
      date = format.parse(val)
    rescue
    end  
  end
  if date
    date
  else
    # Not a (valid) date string, let Cheri try to handle it
    Cheri::Java::Types.convert_to_type(SDate,val)
  end
}

# add the proc to Cheri's type-converter table, with our custom symbol
Cheri.add_type_converter(JDate,date_converter,:my_date)

# now use it
arr = ['2007-02-14','2000-09-18','1983-04-13'].java_array :my_date
=> #<#<Class:01x1956391>:0x1d382ab @java_object=[Ljava.util.Date;@643edd>
arr[1]
=> #<JDate:0x127e2ee @java_object=Mon Sep 18 00:00:00 PST 2000>


# You could also create the proc and add it in a single step:
Cheri.add_type_converter(JDate, :my_date) do |val|
  date = nil
  if val.kind_of?(String)
    begin
      date = format.parse(val)
    rescue
    end  
  end
  if date
    date
  else
    # Not a (valid) date string, let Cheri try to handle it
    Cheri::Java::Types.convert_to_type(SDate,val)
  end
end

arr = ['2007-02-14','2000-09-18','1983-04-13'].java_array :my_date

# Some additional examples of creating/converting arrays

# A new 5x5 String array
arr = Array.java_array java.lang.String,5,5
arr = Array.java_array 'java.lang.String',[5,5]
arr = Array.java_array :string,[5,5],99.99 # supplies a fill value

# Convert various arrays
 
# convert to java.lang.Object array of [Double,String,Boolean,Date]
arr = [0.0,'hello',true,Time.now].java_array

ruby_array = [
  [ [1.0,2.0,3.0], [4.0,5.0,6.0] ],
  [ [1.1,2.1,3.1], [4.1,5.1,6.1] ],
  [ [1.2,2.2,3.2], [4.2,5.2,6.2] ]
]

# 3x2x3 array of Java primitive float
java_array = ruby_array.java_array :float

# 3x2x3 array of java.lang.String
java_array = ruby_array.java_array :string

# 3x2x3 array of java.math.BigDecimal
java_array = ruby_array.java_array :decimal

# 4x3x3 array of java.math.BigDecimal, new space filled with 98.6
java_array = ruby_array.java_array :decimal, [4,3,3], 98.6

# The resulting java.math.BigDecimal array will look like this:
[
  [ [1.0,2.0,3.0], [4.0,5.0,6.0], [98.6,98.6,98.6] ],
  [ [1.1,2.1,3.1], [4.1,5.1,6.1], [98.6,98.6,98.6] ],
  [ [1.2,2.2,3.2], [4.2,5.2,6.2], [98.6,98.6,98.6] ],
  [ [98.6,98.6,98.6], [98.6,98.6,98.6], [98.6,98.6,98.6] ]
]


Copyright © 2007 Bill Dortch