Very timely, I was just asking about the new interface syntax on
#jruby. I was under the (mistaken) assumption this was already in for
0.9.9. Thanks for explaining so thoroughly!
--Chris
On 4/29/07, Bill Dortch (JIRA) <[EMAIL PROTECTED]> wrote:
Java interface modules
----------------------
Key: JRUBY-903
URL: http://jira.codehaus.org/browse/JRUBY-903
Project: JRuby
Issue Type: Improvement
Components: Java Integration
Affects Versions: JRuby 1.0.0
Reporter: Bill Dortch
Attachments: interface_modules.patch
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:
{code}
Java.set_deprecated_interface_syntax true
{code}
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:
{code}
class Runner
include java.lang.Runnable
def run
...
end
end
runner = Runner.new
runner.run
{code}
The impl syntax is still supported:
{code}
runner = java.lang.Runnable.impl do
puts 'running'
end
runner.run
{code}
You can implement multiple interfaces:
{code}
import java.lang.Runnable
import java.lang.Comparable
class RunComp
include Runnable
include Comparable
def run
...
end
def compareTo(other)
...
end
end
{code}
You can extend a class and implement interfaces:
{code}
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
{code}
You can group interfaces in modules and include them together:
{code}
module RunComp
include Runnable
include Comparable
end
...
class RunAndComp
include RunComp
def run
...
end
def compareTo(other)
...
end
end
{code}
You can automatically implement interface methods (useful for event listeners
when you only care about one method):
{code}
class Listener
include MenuListener
implement MenuListener #=> creates empty methods menuCanceled,
menuDeselected, menuSelected
def menuSelected(event) #=> the one we care about
...
end
end
{code}
You can automatically implement all interface methods
{code}
class MegaListener
include KeyListener
include MouseListener
include WindowListener
implement_all
...
end
{code}
You can modify an interface, and have the change reflected in all implementing
classes (Charlie's example):
{code}
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://localhost/mephisto_development",
"mephisto", "mephisto")
connection.execute("select * from users") do |results|
puts results.get_string("email") while results.next
end
ensure
connection.close if connection
end
{code}
You can *not* open an existing Java class and add an interface:
{code}
class JFrame
include ActionListener
end
ArgumentError: can't add Java interface to existing Java class!
{code}
You can *not* open a generated subclass and add an interface once it's been
instantiated:
{code}
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!
{code}
Classes and interfaces are associated with package modules, which you'll see
when classes are displayed (note that interfaces are now there as well):
{noformat}
irb(main):012:0> JFrame.ancestors
=> [Java::JavaxSwing::JFrame, Java::JavaLang::Runnable,
Java::JavaxSwing::WindowConstants,
Java::JavaxSwing::RootPaneContainer, #<Module:01xb9b618>, Java::JavaAwt::Frame,
Java::JavaAwt::Window,
Java::JavaxAccessibility::Accessible, Java::JavaAwt::Container,
Java::JavaAwt::Component,
Java::JavaAwtImage::ImageObserver, Java::JavaAwt::MenuContainer,
Java::JavaIo::Serializable,
Java::JavaLang::Object, ConcreteJavaProxy, JavaProxy, JavaProxyMethods, Object,
Java, Kernel]
{noformat}
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:
{code}
class Java::JavaLang::System
...
end
-- or --
class JavaLang::System
...
end
module JavaUtil::List
...
end
JavaLang::System.nano_time
{code}
The kind_of?, is_a?, < and > methods now work for interfaces as well as classes:
{code}
class MyClass
include Comparable
...
end
MyClass < Comparable #=> true
MyClass.new.kind_of?Comparable #=> true
{code}
So, now for the bad news.
This change breaks two types of interface syntax, though only one is commonly
used:
{code}
class MyClass < java.lang.Runnable; end #=> breaks
anonymous = Class.new(java.lang.Runnable) #=> breaks
{code}
The easy (lazy!) workaround, as noted above:
{code}
Java.set_deprecated_interface_syntax true
{code}
The next-easiest (but still lazy!) workaround:
{code}
# we have *not* set_deprecated_interface_syntax true
class Runner < java.lang.Runnable.deprecated #=> a constant reminder...
...
end
{code}
The workaround for the anonymous class syntax:
{code}
# one way to do this
anonymous = Class.new(Object)
anonymous.send :include, java.lang.Runnable
{code}
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.)
{code}
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
{code}
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:
{code}
class MyFrame < JFrame
end
MyFrame.new.visible = true
# debug code just spews as methods go in and out of JRuby
{code}
Now only methods overridden in the subclass (and any modules it includes) are
intercepted:
{code}
class MyFrame < JFrame
...
def getBounds
puts "getBounds called"
super
end
end
frame = MyFrame.new
frame.visible = true
# only getBounds calls are overriden
{code}
I'll get more tests and documentation done for all this over the next couple of
days.
-Bill
--
This message is automatically generated by JIRA.
-
If you think it was sent incorrectly contact one of the administrators:
http://jira.codehaus.org/secure/Administrators.jspa
-
For more information on JIRA, see: http://www.atlassian.com/software/jira
---------------------------------------------------------------------
To unsubscribe from this list please visit:
http://xircles.codehaus.org/manage_email
---------------------------------------------------------------------
To unsubscribe from this list please visit:
http://xircles.codehaus.org/manage_email