Getting True Java Classes in JRuby

This is an interesting thing I ran across when dealing with JRuby 1.1.4 and Swing - some of this may be just my lack of understanding of some of the Java integration features, some of it may be lingering bugs in the Java integration, and some of it may just be what has to be done to make it work; anyway - here goes.

I was recently developing a Swing app in JRuby (something which I plan to cover in more detail in future blog posts), and was developing an implementation of a javax.swing.table.TableModel in JRuby. One of the things I wanted to implement was the Class getColumnClass(int col) method of AbstractTableModel, as this allows you to automatically inherit smart cell editors such as checkboxes.

However, using a combination of Quaqua L&F, JTable, and JRuby type wrappers was causing an obscure NPE in my code. I was doing something like this initially:

require 'java'
 
module Lang
  include_package 'java.lang'
end
 
# ... In the actual implementation...
def get_column_class(col)
  val = Lang::String::class
  case col
    when 1 then val = Lang::Boolean::class
  end
end

This was failing miserably - I was getting a wrapper around the class object in Ruby - if I did a puts val it would simply print out ‘class’, and if I did a puts val.name it would still simply print out ‘class’. My guess was that I wasn’t getting to the class variable in Java; it was likely just a name collision issue in Ruby.

So I re-focused - as a temporary hack I tried this:

def get_column_class(col)
  val = Lang::String.new.class
  case col
    when 1 then val = Lang::Boolean::TRUE.class
  end
end

This time, hoping to surface the real Java class object, I worked with instances (in turn trying to call the corresponding getClass in the Java world. However, while I didn’t wind up with just ‘Class’ again, I didn’t get Java objects either. I got Ruby class objects.

I realized after trying this that my untrained Ruby brain had hit the same problem in both cases - I was simply accessing the Ruby Object#class property - first it was ‘class’ because I was on a class object, and then it was corresponding ruby classes for the individual objects. Interestingly enough, the Ruby String class wrapper unboxed to the ‘java.lang.String’ class object just fine, but the Ruby Boolean class wrapper, which was ‘TrueClass’ was being returned as something else entirely.

I was left with what I saw as two options - one is probably faster, but feels dirty, and the other is probably slower, but is more explicit.

Option 1

val = Lang::String.new.get_class # get_class triggers the getter in Java

Option 2

val = Lang::Class::for_name 'java.lang.String'

The latter is the one that is likely slower in Java (and in turn will be in JRuby as well). However, since I don’t need to call it all the time, it is the choice I made for my table model by initializing the column types in the constructor - I also added a method to the Lang module to help:

My Solution of Choice

module Lang
  include_package 'java.lang'
  def get_java_class(str)
    return Lang::Class::for_name(str)
  end
end
 
# ... table model implementation including Lang
def initialize
  @col_types = [ get_java_class 'java.lang.String', get_java_class 'java.lang.Boolean' ]
end

Anyone else have a more elegant solution?

Comments