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

Reply via email to