The attached patch implements Java interfaces as modules. It also improves the performance of generated JavaProxyClass instances. Affected unit tests have been updated to handle interface modules. All tests run. (More tests to come.)
I intended to include support for protected methods / fields with this patch, but encountered a number of issues. I don't believe that support can make it for 1.0. I'll post a separate JIRA issue detailing what I ran up against. (I do have a lot of the code done, so once a few ideas get kicked around sufficiently, it won't take too long to get it implemented.)
This patch enables interfaces as modules by default. This will break existing code. However, the old syntax can be enabled easily:
Java.set_deprecated_interface_syntax true
If used, this should be set first thing upon starting up a runtime (or at least before running any Java applications). Though it is possible to change this while running, many problems will likely ensue, as interface classes will be bound to some constants, and interface modules to others. (Changing in mid-stream won't hurt applications loaded from Java, only user Ruby/Java applications.)
We could enable the old syntax by default, but I think it makes sense to go with our best solution for 1.0. I'll discuss working in deprecated mode farther down, but first, the new syntax.
Implementing an interface:
class Runner
include java.lang.Runnable
def run
...
end
end
runner = Runner.new
runner.run
The impl syntax is still supported:
runner = java.lang.Runnable.impl do
puts 'running'
end
runner.run
You can implement multiple interfaces:
import java.lang.Runnable
import java.lang.Comparable
class RunComp
include Runnable
include Comparable
def run
...
end
def compareTo(other)
...
end
end
You can extend a class and implement interfaces:
class MyApp < javax.swing.JFrame
include java.awt.event.ActionListener
include java.lang.Runnable
def initialize(*args)
super
button = javax.swing.JButton.new 'Howdy'
button.add_action_listener self
add button
pack
end
def actionPerformed(event)
puts "Howdy back"
end
def run
...
end
end
You can group interfaces in modules and include them together:
module RunComp
include Runnable
include Comparable
end
...
class RunAndComp
include RunComp
def run
...
end
def compareTo(other)
...
end
end
You can automatically implement interface methods (useful for event listeners when you only care about one method):
class Listener
include MenuListener
implement MenuListener #=> creates empty methods menuCanceled, menuDeselected, menuSelected
def menuSelected(event) #=> the one we care about
...
end
end
You can automatically implement all interface methods
class MegaListener
include KeyListener
include MouseListener
include WindowListener
implement_all
...
end
You can modify an interface, and have the change reflected in all implementing classes (Charlie's example):
include Java
import java.sql.Connection
import java.sql.DriverManager
import java.sql.ResultSet
java.lang.Class.for_name("com.mysql.jdbc.Driver")
module Connection
def execute(query)
statement = create_statement
results = statement.execute_query(query)
yield results
ensure
results.close if results
statement.close if statement
end
end
begin
connection =
DriverManager.get_connection("jdbc:mysql:,
"mephisto", "mephisto")
connection.execute("select * from users") do |results|
puts results.get_string("email") while results.next
end
ensure
connection.close if connection
end
You can not open an existing Java class and add an interface:
class JFrame
include ActionListener
end
ArgumentError: can't add Java interface to existing Java class!
You can not open a generated subclass and add an interface once it's been instantiated:
class MyFrame < JFrame
include ActionListener
end
# oops, forgot one
class MyFrame
include WindowListener #=> OK, no class generated yet
end
frame = MyFrame.new
class MyFrame
include MouseListener #=># too late now
end
ArgumentError: can't add Java interface to existing Java class!
Classes and interfaces are associated with package modules, which you'll see when classes are displayed (note that interfaces are now there as well):
You can optionally use the package name syntax when referring to classes/interfaces. The Java, Javax, Com, Org, and Edu roots are recognized at the top level:
class Java::JavaLang::System
...
end
-- or --
class JavaLang::System
...
end
module JavaUtil::List
...
end
JavaLang::System.nano_time
The kind_of?, is_a?, < and > methods now work for interfaces as well as classes:
class MyClass
include Comparable
...
end
MyClass < Comparable #=> true
MyClass.new.kind_of?Comparable #=> true
So, now for the bad news.
This change breaks two types of interface syntax, though only one is commonly used:
class MyClass < java.lang.Runnable; end #=> breaks
anonymous = Class.new(java.lang.Runnable) #=> breaks
The easy (lazy!) workaround, as noted above:
Java.set_deprecated_interface_syntax true
The next-easiest (but still lazy!) workaround:
# we have *not* set_deprecated_interface_syntax true
class Runner < java.lang.Runnable.deprecated #=> a constant reminder...
...
end
The workaround for the anonymous class syntax:
# one way to do this
anonymous = Class.new(Object)
anonymous.send :include, java.lang.Runnable
If you must run in the deprecated mode, you can still get most of the benefits of the new syntax by referring to the Includable constant on old-style interface classes. However, this will break if you then go to the new mode (I tried to make this transition smoother; you should see how fast JRuby hangs when you assign a constant that refers to the assignee.)
Java.set_deprecated_interface_syntax true #=> in deprecated mode
class MyApp < JFrame
include java.lang.Runnable::Includable
include java.awt.event.ActionListener::Includable
end
module JavaSql::Connection::Includable
...
end
There's probably more to say about interfaces; ask and I shall answer.
The changes (fixes) to JavaProxyClass generation will greatly improve the performance of derived classes. As I've noted elsewhere, every call (even/especially internal ones) in generated classes was being passed in and out of JRuby, operands/results being converted in both directions:
class MyFrame < JFrame
end
MyFrame.new.visible = true
# debug code just spews as methods go in and out of JRuby
Now only methods overridden in the subclass (and any modules it includes) are intercepted:
class MyFrame < JFrame
...
def getBounds
puts "getBounds called"
super
end
end
frame = MyFrame.new
frame.visible = true
# only getBounds calls are overriden
I'll get more tests and documentation done for all this over the next couple of days.
-Bill
I will try playing with your current patch and we'll look forward to including it ASAP for 1.0Beta/RC.