First some background.
As most of you will know, the current way to implement a Java interface with Ruby code is to extend it:
include_class "java.awt.event.ActionListener"
class MyListener < ActionListener
...
end
This works fine for many cases, and it's great for a simple single-interface implementation. However it breaks down if you want to extend either a Java or Ruby class at the same time or if you want to implement multiple interfaces.
A few weeks back, we on the JRuby dev list kicked around the idea of using mixin
inheritance to do interface implementation:
class MyListener
implement ActionListener
end
This has the advantage of allowing you to also extend a class and implement multiple interfaces, but there's a problem here. By the time we encounter MyListener, the class is already created and there's no opportunity to make such drastic changes as modifying the list of implemented interfaces.
In the case above, MyListener is already created as a pure Ruby class by the time we encounter the implement line...we can't then change it into a Ruby/Java proxy class. Even if we had a way to mark it ahead of time as a Java proxy, that proxy would have to be created already by the time we're in the class body. Ruby's unusual way of instantiating classes is to blame: all classes start out "blank" and the class body is basically eval'ed within that blank instance. With Java types, we do not have such flexibility.
So a new option comes into the debate today. It's not as clean, and it's not as Rubyish, but it should support Java typing and Java interface implementation very well:
include_class "java.util.AbstractList"
include_class "java.util.Map"
MapList = AbstractList.implement(Map)
class MyMapList < MapList
...
end
Or the shortcut version:
class MyMapList < AbstractList.implement(Map)
We will probably also continue to allow the single-inheritance shortcuts as well, since they're nice and clean:
class MyListener < ActionListener
end
...which is roughly synonymous with:
include_class("java.lang.Object") { |p, n| "J" + n }
class MyListener < JObject.implement(ActionListener)
end
The logic behind this approach (very similar to that being taken by RubyCLR and IronPython) is that a concrete class plus multiple interfaces as a whole represents a very rigid, specific type in the Java world. We do not have the flexibility to juggle the internals of those types after they're created, so having a very clear-cut way of specifying that combination of concrete + interfaces allows us to satisfy Java's typing requirements. We can then extend that with Ruby code, implement whatever we want, alter behavior, reopen classes, and so on. We're essentially creating a rigid top-level Java type with a "back door" for implementing its behavior with Ruby code under the covers.
We'll certainly want to try to coordinate with other projects addressing this same issue (Ruby.NET, RubyCLR, IronRuby, IronPython, Jython?), since we don't want multiple incompatible syntaxes for this stuff. You out there guys?
I think this one would work either:
ReplyDeleteclass MyListener < JObject(ActionListener)
end