Index: test/org/jruby/test/TestAdoptedThreading.java =================================================================== --- test/org/jruby/test/TestAdoptedThreading.java (revision 3594) +++ test/org/jruby/test/TestAdoptedThreading.java (working copy) @@ -44,17 +44,28 @@ // Uncomment the "puts" lines if you want to see more detail private static final String SCRIPT = "require 'java'\n" + "include_class 'org.jruby.test.ITest'\n" - + "class TestImpl < ITest\n" + " def exec(_value)\n" + + "if ITest.instance_of?(Module)\n" + + " class TestImpl; include ITest; end\n" + + "else\n" + + " class TestImpl < ITest; end\n" + + "end\n" + + "class TestImpl\n" + + " def exec(_value)\n" + " #puts \"start executing!\"\n" + " 100.times do | item |\n" - + " value = \"#{item}\"\n" + " end\n" - + " #puts \"end executing1!\"\n" + " exec2(_value)\n" + + " value = \"#{item}\"\n" + + " end\n" + + " #puts \"end executing1!\"\n" + + " exec2(_value)\n" + " end\n" + " def exec2(_value)\n" + " #puts \"start executing2!\"\n" + " 500.times do | item |\n" - + " value = \"#{item}\"\n" + " end\n" + + " value = \"#{item}\"\n" + + " end\n" + " #puts \"end executing2!\"\n" - + " \"VALUE: #{_value}\"\n" + " end\n" + "end"; + + " \"VALUE: #{_value}\"\n" + + " end\n" + + "end"; public TestAdoptedThreading(String _name) { super(_name); Index: test/org/jruby/javasupport/test/SimpleInterfaceImpl.rb =================================================================== --- test/org/jruby/javasupport/test/SimpleInterfaceImpl.rb (revision 3594) +++ test/org/jruby/javasupport/test/SimpleInterfaceImpl.rb (working copy) @@ -2,7 +2,16 @@ include_class 'org.jruby.javasupport.test.SimpleInterface' -class SimpleInterfaceImpl < SimpleInterface +if SimpleInterface.instance_of?(Module) + class SimpleInterfaceImpl + include SimpleInterface + end +else + class SimpleInterfaceImpl < SimpleInterface + end +end + +class SimpleInterfaceImpl def initialize super @list = [1,2,3] Index: test/test_higher_javasupport.rb =================================================================== --- test/test_higher_javasupport.rb (revision 3594) +++ test/test_higher_javasupport.rb (working copy) @@ -148,7 +148,17 @@ end def test_extending_java_interfaces - assert Class.new(java.lang.Comparable) + if java.lang.Comparable.instance_of?(Module) + anonymous = Class.new(Object) + anonymous.send :include, java.lang.Comparable + anonymous.send :include, java.lang.Runnable + assert anonymous < java.lang.Comparable + assert anonymous < java.lang.Runnable + assert anonymous.new.kind_of?(java.lang.Runnable) + assert anonymous.new.kind_of?(java.lang.Comparable) + else + assert Class.new(java.lang.Comparable) + end end def test_support_of_other_class_loaders @@ -301,7 +311,13 @@ assert_equal("b", p.getProperty("a")) end - class MyBadActionListener < java.awt.event.ActionListener + if java.awt.event.ActionListener.instance_of?(Module) + class MyBadActionListener + include java.awt.event.ActionListener + end + else + class MyBadActionListener < java.awt.event.ActionListener + end end def test_expected_missing_interface_method @@ -366,15 +382,26 @@ end unless (java.lang.System.getProperty("java.specification.version") == "1.4") - class NSCT < javax.xml.namespace.NamespaceContext - # JRUBY-66: No super here...make sure we still work. - def initialize(arg) + if javax.xml.namespace.NamespaceContext.instance_of?(Module) + class NSCT + include javax.xml.namespace.NamespaceContext + # JRUBY-66: No super here...make sure we still work. + def initialize(arg) + end + def getNamespaceURI(prefix) + 'ape:sex' + end end - def getNamespaceURI(prefix) - 'ape:sex' + else + class NSCT < javax.xml.namespace.NamespaceContext + # JRUBY-66: No super here...make sure we still work. + def initialize(arg) + end + def getNamespaceURI(prefix) + 'ape:sex' + end end end - def test_no_need_to_call_super_in_initialize_when_implementing_java_interfaces # No error is a pass here for JRUBY-66 assert_nothing_raised do @@ -394,12 +421,23 @@ assert(java.lang.System.respond_to?("current_time_millis")) end - class TestInitBlock < Java::java.lang.Runnable - def initialize(&block) - raise if !block - @bar = block.call + if Java::java.lang.Runnable.instance_of?(Module) + class TestInitBlock + include Java::java.lang.Runnable + def initialize(&block) + raise if !block + @bar = block.call + end + def bar; @bar; end end - def bar; @bar; end + else + class TestInitBlock < Java::java.lang.Runnable + def initialize(&block) + raise if !block + @bar = block.call + end + def bar; @bar; end + end end def test_that_blocks_are_passed_through_to_the_constructor_for_an_interface_impl Index: test/testInstantiatingInterfaces.rb =================================================================== --- test/testInstantiatingInterfaces.rb (revision 3594) +++ test/testInstantiatingInterfaces.rb (working copy) @@ -5,7 +5,13 @@ require 'java' # Tests unimplemented interface methods - class A < java.lang.Runnable + if java.lang.Runnable.instance_of?(Module) + class A + include java.lang.Runnable + end + else + class A < java.lang.Runnable + end end test_exception(NoMethodError) do A.new.run Index: src/builtin/javasupport/proxy/interface.rb =================================================================== --- src/builtin/javasupport/proxy/interface.rb (revision 3594) +++ src/builtin/javasupport/proxy/interface.rb (working copy) @@ -8,6 +8,7 @@ proxy_class.class_eval &@block if @java_class.assignable_from? proxy_class.java_class end end + class InterfaceJavaProxy < JavaProxy class << self alias_method :new_proxy, :new @@ -21,7 +22,7 @@ proxy.send(:initialize,*args,&block) proxy end - + def +(other) MultipleInterfaceJavaProxy.new(lambda{|*args| new_proxy(*args)}, self, other) end @@ -49,6 +50,7 @@ end end +# TODO: I think we can drop this now class MultipleInterfaceJavaProxy attr_reader :interfaces @@ -90,4 +92,156 @@ else [other.java_class] end end -end \ No newline at end of file +end + +# template for Java interface modules, not used directly +module JavaInterfaceTemplate + class << self + attr :java_class + +private # not intended to be called directly by users + # TODO: this should be implemented in JavaClass.java, where we can + # check for reserved Ruby names, conflicting methods, etc. + def implement(clazz) + @java_class.java_instance_methods.each do |meth| + name = meth.name + clazz.module_eval <<-EOM + def #{name}(*args); end unless method_defined?(:#{name}) + EOM + end + end + +public + def included(clazz) + if clazz.instance_of?(Class) + java_class = @java_class + clazz.module_eval do + # not allowed for original (non-generated) Java classes + # note: not allowing for any previously created class right now; + # this restriction might be loosened later (post-1.0.0) for generated classes + if (@java_class && !(class< 0 - # array creation should use this variant - ArrayJavaProxyCreator.new(java_class,*args) - else - # keep this variant for kind_of? testing - JavaUtilities.get_proxy_class(java_class.array_class) - end - end - end - +module JavaProxyMethods attr :java_object, true def java_class @@ -60,3 +33,35 @@ java_object.synchronized { yield } end end + +# JavaProxy is a base class for all Java Proxies. A Java proxy is a high-level abstraction +# that wraps a low-level JavaObject with ruby methods capable of dispatching to the JavaObjects +# native java methods. +class JavaProxy + class << self + attr :java_class, true + # Carry the Java class as a class variable on the derived classes too, otherwise + # JavaProxy.java_class won't work. + def inherited(subclass) + subclass.java_class = self.java_class unless subclass.java_class + super + end + + def singleton_class + class << self; self; end + end + + def [](*args) + if args.length > 0 + # array creation should use this variant + ArrayJavaProxyCreator.new(java_class,*args) + else + # keep this variant for kind_of? testing + JavaUtilities.get_proxy_class(java_class.array_class) + end + end + end + + include JavaProxyMethods + +end Index: src/builtin/javasupport/java.rb =================================================================== --- src/builtin/javasupport/java.rb (revision 3594) +++ src/builtin/javasupport/java.rb (working copy) @@ -1,7 +1,11 @@ module Java class << self def const_missing(sym) - JavaUtilities.get_proxy_class "#{sym}" + begin + JavaUtilities.get_proxy_class "#{sym}" + rescue + JavaUtilities.get_package_module "#{sym}" + end end def method_missing(sym, *args) @@ -17,7 +21,11 @@ class Package # this class should be a blank slate - + keep_names = /^(__|class|inspect|object_id|to_s|equal|respond_to)/ + instance_methods.each do |m| + undef_method m unless m =~ keep_names + end + def initialize(name) @name = name end @@ -37,9 +45,21 @@ class << self def create_package(sym, package_name, cls) package = Java::Package.new package_name - cls.send(:define_method, sym) { package } + cls.__send__(:define_method, sym) { package } package end end end -end \ No newline at end of file +end + +module JavaPackageModuleTemplate + class << self + attr :package_name + + def const_missing(const) + JavaUtilities.get_proxy_class(package_name + const.to_s) + end + end +end +# pull in the default package +JavaUtilities.get_package_module("Default") Index: src/builtin/javasupport/core_ext/object.rb =================================================================== --- src/builtin/javasupport/core_ext/object.rb (revision 3594) +++ src/builtin/javasupport/core_ext/object.rb (working copy) @@ -53,9 +53,19 @@ end end end + + class << self + alias_method :__jredef_const_missing, :const_missing + def const_missing(const) + if const.to_s =~ /^((Java|Javax|Com|Org|Edu)[A-Z])/ + JavaUtilities.get_package_module(const) + else + __jredef_const_missing const + end + end + end - # sneaking this in with the array support, getting - # tired of having to define it in all my code -BD + # TODO: this can go away now, but people may be using it def java_kind_of?(other) return true if self.kind_of?(other) return false unless self.respond_to?(:java_class) && other.respond_to?(:java_class) && Index: src/builtin/javasupport/utilities/base.rb =================================================================== --- src/builtin/javasupport/utilities/base.rb (revision 3594) +++ src/builtin/javasupport/utilities/base.rb (working copy) @@ -5,25 +5,27 @@ def JavaUtilities.setup_java_subclass(subclass, java_class) # add new class-variable to hold the JavaProxyClass instance - subclass.class.send :attr, :java_proxy_class, true - + subclass.module_eval do + class << self + attr :java_proxy_class, true + def java_interfaces + @java_interfaces.dup if @java_interfaces + end + end #self + end + subclass.send(:define_method, "__jcreate!") {|*args| + self.class.java_proxy_class ||= Java::JavaProxyClass.get_with_class(self.java_class, + self.class.java_interfaces,self.class) constructors = self.class.java_proxy_class.constructors.select {|c| c.arity == args.length } raise NameError.new("wrong # of arguments for constructor") if constructors.empty? args.collect! { |v| Java.ruby_to_java(v) } self.java_object = JavaUtilities.matching_method(constructors, args).new_instance(args) { |proxy, method, *args| - args.collect! { |arg| Java.java_to_ruby(arg) } + args.collect! { |arg| Java.java_to_ruby(arg) } result = __jsend!(method.name, *args) Java.ruby_to_java(result) } } - - subclass.send(:define_method, "setup_instance_methods") { - puts "subclass #{self} calling JPC.dimfp" # never called? - self.java_proxy_class.define_instance_methods_for_proxy(subclass) - } - - subclass.java_proxy_class = Java::JavaProxyClass.get(java_class) end def JavaUtilities.get_java_class(name) Index: src/org/jruby/javasupport/proxy/JavaProxyClassFactory.java =================================================================== --- src/org/jruby/javasupport/proxy/JavaProxyClassFactory.java (revision 3594) +++ src/org/jruby/javasupport/proxy/JavaProxyClassFactory.java (working copy) @@ -102,7 +102,7 @@ private static Method defineClass_method; static JavaProxyClass newProxyClass(ClassLoader loader, - String targetClassName, Class superClass, Class[] interfaces) + String targetClassName, Class superClass, Class[] interfaces, Set names) throws InvocationTargetException { if (loader == null) { loader = JavaProxyClassFactory.class.getClassLoader(); @@ -147,7 +147,7 @@ validateArgs(targetClassName, superClass); Map methods = new HashMap(); - collectMethods(superClass, interfaces, methods); + collectMethods(superClass, interfaces, methods, names); Type selfType = Type.getType("L" + toInternalClassName(targetClassName) + ";"); @@ -160,6 +160,12 @@ return proxyClass; } + static JavaProxyClass newProxyClass(ClassLoader loader, + String targetClassName, Class superClass, Class[] interfaces) + throws InvocationTargetException { + return newProxyClass(loader,targetClassName,superClass,interfaces,null); + } + private static JavaProxyClass generate(final ClassLoader loader, final String targetClassName, final Class superClass, final Class[] interfaces, final Map methods, final Type selfType) { @@ -526,10 +532,10 @@ } private static void collectMethods(Class superClass, Class[] interfaces, - Map methods) { + Map methods, Set names) { HashSet allClasses = new HashSet(); - addClass(allClasses, methods, superClass); - addInterfaces(allClasses, methods, interfaces); + addClass(allClasses, methods, superClass, names); + addInterfaces(allClasses, methods, interfaces, names); } static class MethodData { @@ -684,29 +690,30 @@ } private static void addInterfaces(Set allClasses, Map methods, - Class[] interfaces) { + Class[] interfaces, Set names) { for (int i = 0; i < interfaces.length; i++) { - addInterface(allClasses, methods, interfaces[i]); + addInterface(allClasses, methods, interfaces[i], names); } } private static void addInterface(Set allClasses, Map methods, - Class interfaze) { + Class interfaze, Set names) { if (allClasses.add(interfaze)) { - addMethods(methods, interfaze); - addInterfaces(allClasses, methods, interfaze.getInterfaces()); + addMethods(methods, interfaze, names); + addInterfaces(allClasses, methods, interfaze.getInterfaces(), names); } } - private static void addMethods(Map methods, Class classOrInterface) { + private static void addMethods(Map methods, Class classOrInterface, Set names) { Method[] mths = classOrInterface.getDeclaredMethods(); for (int i = 0; i < mths.length; i++) { - addMethod(methods, mths[i]); + if (names.contains(mths[i].getName())) { + addMethod(methods, mths[i]); + } } } private static void addMethod(Map methods, Method method) { - int acc = method.getModifiers(); if (Modifier.isStatic(acc) || Modifier.isPrivate(acc)) { return; @@ -721,15 +728,15 @@ md.add(method); } - private static void addClass(Set allClasses, Map methods, Class clazz) { + private static void addClass(Set allClasses, Map methods, Class clazz, Set names) { if (allClasses.add(clazz)) { - addMethods(methods, clazz); + addMethods(methods, clazz, names); Class superClass = clazz.getSuperclass(); if (superClass != null) { - addClass(allClasses, methods, superClass); + addClass(allClasses, methods, superClass, names); } - addInterfaces(allClasses, methods, clazz.getInterfaces()); + addInterfaces(allClasses, methods, clazz.getInterfaces(), names); } } Index: src/org/jruby/javasupport/proxy/JavaProxyClass.java =================================================================== --- src/org/jruby/javasupport/proxy/JavaProxyClass.java (revision 3594) +++ src/org/jruby/javasupport/proxy/JavaProxyClass.java (working copy) @@ -38,8 +38,11 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Set; import org.jruby.Ruby; import org.jruby.RubyArray; @@ -94,18 +97,23 @@ } public static JavaProxyClass getProxyClass(Ruby runtime, Class superClass, - Class[] interfaces) throws InvocationTargetException { + Class[] interfaces, Set names) throws InvocationTargetException { Object save = runtimeTLS.get(); runtimeTLS.set(runtime); try { ClassLoader loader = runtime.getJavaSupport().getJavaClassLoader(); - return JavaProxyClassFactory.newProxyClass(loader, null, superClass, interfaces); + return JavaProxyClassFactory.newProxyClass(loader, null, superClass, interfaces, names); } finally { runtimeTLS.set(save); } } + public static JavaProxyClass getProxyClass(Ruby runtime, Class superClass, + Class[] interfaces) throws InvocationTargetException { + return getProxyClass(runtime,superClass,interfaces,null); + } + public static Object newProxyInstance(Ruby runtime, Class superClass, Class[] interfaces, Class[] constructorParameters, Object[] constructorArgs, JavaProxyInvocationHandler handler) throws IllegalArgumentException, @@ -159,8 +167,7 @@ return (JavaProxyMethod[]) methods.toArray(new JavaProxyMethod[methods.size()]); } - public JavaProxyMethod getMethod(String name, Class[] parameterTypes) - throws NoSuchMethodException { + public JavaProxyMethod getMethod(String name, Class[] parameterTypes) { List methods = (List)methodMap.get(name); if (methods != null) { for (int i = methods.size(); --i >= 0; ) { @@ -168,7 +175,7 @@ if (jpm.matches(name, parameterTypes)) return jpm; } } - throw new NoSuchMethodException(); + return null; } /** return the class of instances of this proxy class */ @@ -474,6 +481,8 @@ result.getMetaClass().defineFastMethod("get", callbackFactory.getFastSingletonMethod("get", JavaClass.class)); + result.getMetaClass().defineFastMethod("get_with_class", + callbackFactory.getFastSingletonMethod("get_with_class", JavaClass.class, IRubyObject.class, RubyClass.class)); return result; } @@ -491,7 +500,80 @@ throw ex; } } + + private static final HashSet EXCLUDE_MODULES = new HashSet(); + static { + EXCLUDE_MODULES.add("Kernel"); + EXCLUDE_MODULES.add("Java"); + EXCLUDE_MODULES.add("JavaProxyMethods"); + EXCLUDE_MODULES.add("Enumerable"); + } + public static RubyObject get_with_class(IRubyObject recv, JavaClass type, IRubyObject ifcArray, RubyClass clazz) { + Ruby runtime = recv.getRuntime(); + Map methods; + Set names = new HashSet(); + Class[] interfaces; + + // Let's only generate methods for those the user may actually + // intend to override. That includes any defined in the current + // class, but none from any ancestor class. Methods defined in + // mixins will be considered intentionally overridden, except those + // from Kernel, Java, and JavaProxyMethods. + // TODO: may want to exclude Enumerable, others? + synchronized(methods = clazz.getMethods()) { + for (Iterator iter = methods.keySet().iterator(); iter.hasNext(); ) { + names.add(iter.next()); + } + } + List ancestors = clazz.getAncestorList(); + for (Iterator iter = ancestors.iterator(); iter.hasNext(); ) { + RubyModule ancestor = (RubyModule)iter.next(); + if (ancestor instanceof RubyClass || + EXCLUDE_MODULES.contains(ancestor.getName())) { + continue; + } + synchronized(methods = ancestor.getMethods()) { + for (Iterator meths = methods.keySet().iterator(); meths.hasNext(); ) { + names.add(meths.next()); + } + } + } + + if (ifcArray instanceof RubyArray) { + RubyArray ifcs = (RubyArray)ifcArray; + int size = ifcs.size(); + interfaces = new Class[size]; + for (int i = size; --i >= 0; ) { + IRubyObject ifc = ifcs.eltInternal(i); + if (!(ifc instanceof JavaClass)) { + throw runtime.newArgumentError("unable to create proxy class for " + type.getValue() + " - invalid interface"); + } + Class ifcClass = ((JavaClass)ifc).javaClass(); + if (!ifcClass.isInterface()) { + throw runtime.newArgumentError("unable to create proxy class for " + type.getValue() + " - invalid interface"); + } + interfaces[i] = ifcClass; + } + } else { + interfaces = new Class[0]; + } + + try { + return getProxyClass(recv.getRuntime(), (Class) type.getValue(), interfaces, names); + } catch (Error e) { + RaiseException ex = recv.getRuntime().newArgumentError("unable to create proxy class for " + type.getValue() + " : " + e.getMessage()); + //e.printStackTrace(); + ex.initCause(e); + throw ex; + } catch (InvocationTargetException e) { + RaiseException ex = recv.getRuntime().newArgumentError("unable to create proxy class for " + type.getValue() + " : " + e.getMessage()); + //e.printStackTrace(); + ex.initCause(e); + throw ex; + } + } + public RubyObject superclass() { return JavaClass.get(getRuntime(), getSuperclass()); } Index: src/org/jruby/javasupport/JavaClass.java =================================================================== --- src/org/jruby/javasupport/JavaClass.java (revision 3594) +++ src/org/jruby/javasupport/JavaClass.java (working copy) @@ -70,6 +70,7 @@ import org.jruby.runtime.CallType; import org.jruby.runtime.CallbackFactory; import org.jruby.runtime.ObjectAllocator; +import org.jruby.runtime.Visibility; import org.jruby.runtime.builtin.IRubyObject; import org.jruby.runtime.callback.Callback; import org.jruby.util.IdUtil; @@ -86,8 +87,13 @@ static final int RESERVED = 0; static final int METHOD = 1; static final int FIELD = 2; - static final int WEAKLY_RESERVED = 3; // we'll be peeved, but not devastated, if you override - static final int ALIAS = 4; + static final int PROTECTED_METHOD = 3; + static final int WEAKLY_RESERVED = 4; // we'll be peeved, but not devastated, if you override + static final int ALIAS = 5; + // yes, protected fields are weaker than aliases. many conflicts + // in the old AWT code, for example, where you really want 'size' + // to mean the public method getSize, not the protected field 'size'. + static final int PROTECTED_FIELD = 6; String name; int type; AssignedName () {} @@ -100,9 +106,16 @@ // TODO: other reserved names? private static final Map RESERVED_NAMES = new HashMap(); static { + RESERVED_NAMES.put("__id__", new AssignedName("__id__", AssignedName.RESERVED)); + RESERVED_NAMES.put("__send__", new AssignedName("__send__", AssignedName.RESERVED)); RESERVED_NAMES.put("class", new AssignedName("class", AssignedName.RESERVED)); - RESERVED_NAMES.put("__id__", new AssignedName("__id__", AssignedName.RESERVED)); + RESERVED_NAMES.put("initialize", new AssignedName("initialize", AssignedName.RESERVED)); RESERVED_NAMES.put("object_id", new AssignedName("object_id", AssignedName.RESERVED)); + RESERVED_NAMES.put("private", new AssignedName("private", AssignedName.RESERVED)); + RESERVED_NAMES.put("protected", new AssignedName("protected", AssignedName.RESERVED)); + RESERVED_NAMES.put("public", new AssignedName("public", AssignedName.RESERVED)); + + // weakly reserved names RESERVED_NAMES.put("id", new AssignedName("id", AssignedName.WEAKLY_RESERVED)); } private static final Map STATIC_RESERVED_NAMES = new HashMap(RESERVED_NAMES); @@ -118,6 +131,8 @@ static final int INSTANCE_METHOD = 4; String name; int type; + Visibility visibility = Visibility.PUBLIC; + boolean isProtected; NamedCallback () {} NamedCallback (String name, int type) { this.name = name; @@ -128,6 +143,12 @@ boolean hasLocalMethod() { return true; } + boolean isPublic() { + return visibility == Visibility.PUBLIC; + } + boolean isProtected() { + return visibility == Visibility.PROTECTED; + } void logMessage(IRubyObject self, IRubyObject[] args) { if (!DEBUG) { return; @@ -156,6 +177,10 @@ FieldCallback(String name, int type, Field field) { super(name,type); this.field = field; +// if (Modifier.isProtected(field.getModifiers())) { +// field.setAccessible(true); +// this.visibility = Visibility.PROTECTED; +// } } } @@ -165,7 +190,7 @@ super(name,STATIC_FIELD,field); } void install(RubyClass proxy) { - proxy.getSingletonClass().defineFastMethod(this.name,this); + proxy.getSingletonClass().defineFastMethod(this.name,this,this.visibility); } public IRubyObject execute(IRubyObject self, IRubyObject[] args, Block block) { logMessage(self,args); @@ -185,7 +210,7 @@ super(name,STATIC_FIELD,field); } void install(RubyClass proxy) { - proxy.getSingletonClass().defineFastMethod(this.name,this); + proxy.getSingletonClass().defineFastMethod(this.name,this,this.visibility); } public IRubyObject execute(IRubyObject self, IRubyObject[] args, Block block) { logMessage(self,args); @@ -207,7 +232,7 @@ super(name,INSTANCE_FIELD,field); } void install(RubyClass proxy) { - proxy.defineFastMethod(this.name,this); + proxy.defineFastMethod(this.name,this,this.visibility); } public IRubyObject execute(IRubyObject self, IRubyObject[] args, Block block) { logMessage(self,args); @@ -229,7 +254,7 @@ super(name,INSTANCE_FIELD,field); } void install(RubyClass proxy) { - proxy.defineFastMethod(this.name,this); + proxy.defineFastMethod(this.name,this,this.visibility); } public IRubyObject execute(IRubyObject self, IRubyObject[] args, Block block) { logMessage(self,args); @@ -262,7 +287,10 @@ methods = new ArrayList(); } methods.add(method); - haveLocalMethod = (haveLocalMethod || javaClass == method.getDeclaringClass()); +// if (Modifier.isProtected(method.getModifiers())) { +// visibility = Visibility.PROTECTED; +// } + haveLocalMethod |= javaClass == method.getDeclaringClass(); } void addAlias(String alias) { if (aliases == null) { @@ -319,8 +347,8 @@ void install(RubyClass proxy) { if (haveLocalMethod) { RubyClass singleton = proxy.getSingletonClass(); - singleton.defineFastMethod(this.name,this); - if (aliases != null) { + singleton.defineFastMethod(this.name,this,this.visibility); + if (aliases != null && isPublic() ) { for (Iterator iter = aliases.iterator(); iter.hasNext(); ) { singleton.defineAlias((String)iter.next(), this.name); } @@ -370,8 +398,8 @@ } void install(RubyClass proxy) { if (haveLocalMethod) { - proxy.defineFastMethod(this.name,this); - if (aliases != null) { + proxy.defineFastMethod(this.name,this,this.visibility); + if (aliases != null && isPublic()) { for (Iterator iter = aliases.iterator(); iter.hasNext(); ) { proxy.defineAlias((String)iter.next(), this.name); } @@ -422,7 +450,7 @@ ConstantField(Field field) { this.field = field; } - void install(RubyClass proxy) { + void install(RubyModule proxy) { if (proxy.getConstantAt(field.getName()) == null) { JavaField javaField = new JavaField(proxy.getRuntime(),field); RubyString name = javaField.name(); @@ -435,7 +463,6 @@ } } - private final RubyModule JAVA_UTILITIES = getRuntime().getModule("JavaUtilities"); private Map staticAssignedNames; @@ -464,23 +491,17 @@ } private void initializeInterface(Class javaClass) { - Class superclass = javaClass.getSuperclass(); - Map staticNames; - if (superclass == null) { - staticNames = new HashMap(); - } else { - JavaClass superJavaClass = get(getRuntime(),superclass); - staticNames = new HashMap(superJavaClass.getStaticAssignedNames()); + Map staticNames = new HashMap(STATIC_RESERVED_NAMES); + List constantFields = new ArrayList(); + Field[] fields; + try { + fields = javaClass.getDeclaredFields(); + } catch (SecurityException e) { + fields = javaClass.getFields(); } - staticNames.putAll(STATIC_RESERVED_NAMES); - List constantFields = new ArrayList(); - Field[] fields = javaClass.getFields(); for (int i = fields.length; --i >= 0; ) { Field field = fields[i]; - // treating constants specially until interface modules - // are implemented. we'll define any as-yet undefined - // constant, regardless of declaring class. this will - // slow us down a bit in setupProxy. + if (javaClass != field.getDeclaringClass()) continue; if (ConstantField.isConstant(field)) { constantFields.add(new ConstantField(field)); } @@ -488,6 +509,7 @@ this.staticAssignedNames = staticNames; this.constantFields = constantFields; } + private void initializeClass(Class javaClass) { Class superclass = javaClass.getSuperclass(); Map staticNames; @@ -508,25 +530,21 @@ Field[] fields = javaClass.getFields(); for (int i = fields.length; --i >= 0; ) { Field field = fields[i]; - // treating constants specially until interface modules - // are implemented. we'll define any as-yet undefined - // constant, regardless of declaring class. this will - // slow us down a bit in setupProxy. + if (javaClass != field.getDeclaringClass()) continue; + if (ConstantField.isConstant(field)) { constantFields.add(new ConstantField(field)); continue; } - // for everything else, must be declared in this class - if (!javaClass.equals(field.getDeclaringClass())) - continue; String name = field.getName(); - if (Modifier.isStatic(field.getModifiers())) { + int modifiers = field.getModifiers(); + if (Modifier.isStatic(modifiers)) { AssignedName assignedName = (AssignedName)staticNames.get(name); if (assignedName != null && assignedName.type < AssignedName.FIELD) continue; staticNames.put(name,new AssignedName(name,AssignedName.FIELD)); staticCallbacks.put(name,new StaticFieldGetter(name,field)); - if (!Modifier.isFinal(field.getModifiers())) { + if (!Modifier.isFinal(modifiers)) { String setName = name + '='; staticCallbacks.put(setName,new StaticFieldSetter(setName,field)); } @@ -536,12 +554,14 @@ continue; instanceNames.put(name,new AssignedName(name,AssignedName.FIELD)); instanceCallbacks.put(name,new InstanceFieldGetter(name,field)); - if (!Modifier.isFinal(field.getModifiers())) { + if (!Modifier.isFinal(modifiers)) { String setName = name + '='; instanceCallbacks.put(setName,new InstanceFieldSetter(setName,field)); } } } + // TODO: protected methods. this is going to require a rework + // of some of the mechanism. Method[] methods = javaClass.getMethods(); for (int i = methods.length; --i >= 0; ) { // we need to collect all methods, though we'll only @@ -692,14 +712,20 @@ } } - private static void addUnassignedAlias(String name, Map assignedNames, MethodCallback callback ) { + private static void addUnassignedAlias(String name, Map assignedNames, + MethodCallback callback) { if (name != null) { AssignedName assignedName = (AssignedName)assignedNames.get(name); - if (assignedName == null || assignedName.type >= AssignedName.ALIAS) { + if (assignedName == null) { callback.addAlias(name); - if (assignedName == null || assignedName.type != AssignedName.ALIAS) { - assignedNames.put(name,new AssignedName(name,AssignedName.ALIAS)); - } + assignedNames.put(name,new AssignedName(name,AssignedName.ALIAS)); + } else if (assignedName.type == AssignedName.ALIAS) { + callback.addAlias(name); + } else if (assignedName.type > AssignedName.ALIAS) { + // TODO: there will be some additional logic in this branch + // dealing with conflicting protected fields. + callback.addAlias(name); + assignedNames.put(name,new AssignedName(name,AssignedName.ALIAS)); } } } @@ -745,6 +771,28 @@ } } } + + public void setupInterfaceModule(RubyModule module) { + Class javaClass = javaClass(); + for (Iterator iter = constantFields.iterator(); iter.hasNext(); ){ + ((ConstantField)iter.next()).install(module); + } + // setup constants for public inner classes + Class[] classes = javaClass.getClasses(); + for (int i = classes.length; --i >= 0; ) { + if (javaClass == classes[i].getDeclaringClass()) { + Class clazz = classes[i]; + String simpleName = getSimpleName(clazz); + if (simpleName.length() == 0) continue; + + // Ignore bad constant named inner classes pending JRUBY-697 + if (IdUtil.isConstant(simpleName) && module.getConstantAt(simpleName) == null) { + module.const_set(getRuntime().newString(simpleName), + Java.get_proxy_class(JAVA_UTILITIES,get(getRuntime(),clazz))); + } + } + } + } // unsynchronized, so create won't hold up get by other threads public static JavaClass get(Ruby runtime, Class klass) { @@ -1103,7 +1151,7 @@ return getRuntime().newBoolean(Modifier.isPrivate(javaClass().getModifiers())); } - Class javaClass() { + public Class javaClass() { return (Class) getValue(); } Index: src/org/jruby/javasupport/Java.java =================================================================== --- src/org/jruby/javasupport/Java.java (revision 3594) +++ src/org/jruby/javasupport/Java.java (working copy) @@ -39,6 +39,8 @@ import java.util.Iterator; import java.util.HashMap; import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Modifier; @@ -78,6 +80,7 @@ javaModule.defineModuleFunction("java_to_ruby", callbackFactory.getSingletonMethod("java_to_ruby", IRubyObject.class)); javaModule.defineModuleFunction("ruby_to_java", callbackFactory.getSingletonMethod("ruby_to_java", IRubyObject.class)); javaModule.defineModuleFunction("new_proxy_instance", callbackFactory.getOptSingletonMethod("new_proxy_instance")); + javaModule.defineModuleFunction("set_deprecated_interface_syntax", callbackFactory.getSingletonMethod("set_deprecated_interface_syntax", IRubyObject.class)); JavaObject.createJavaObjectClass(runtime, javaModule); JavaArray.createJavaArrayClass(runtime, javaModule); @@ -85,7 +88,7 @@ JavaMethod.createJavaMethodClass(runtime, javaModule); JavaConstructor.createJavaConstructorClass(runtime, javaModule); JavaField.createJavaFieldClass(runtime, javaModule); - + // also create the JavaProxy* classes JavaProxyClass.createJavaProxyModule(runtime); @@ -95,6 +98,9 @@ javaUtils.defineFastModuleFunction("primitive_match", callbackFactory.getFastSingletonMethod("primitive_match",IRubyObject.class,IRubyObject.class)); javaUtils.defineFastModuleFunction("access", callbackFactory.getFastSingletonMethod("access",IRubyObject.class)); javaUtils.defineFastModuleFunction("matching_method", callbackFactory.getFastSingletonMethod("matching_method", IRubyObject.class, IRubyObject.class)); + javaUtils.defineFastModuleFunction("get_deprecated_interface_proxy", callbackFactory.getFastSingletonMethod("get_deprecated_interface_proxy", IRubyObject.class)); + javaUtils.defineFastModuleFunction("get_interface_module", callbackFactory.getFastSingletonMethod("get_interface_module", IRubyObject.class)); + javaUtils.defineFastModuleFunction("get_package_module", callbackFactory.getFastSingletonMethod("get_package_module", IRubyObject.class)); javaUtils.defineFastModuleFunction("get_proxy_class", callbackFactory.getFastSingletonMethod("get_proxy_class", IRubyObject.class)); javaUtils.defineFastModuleFunction("add_proxy_extender", callbackFactory.getFastSingletonMethod("add_proxy_extender", IRubyObject.class)); @@ -121,6 +127,7 @@ private final static class ProxyData { public final IntHashMap classes = new IntHashMap(); + public final IntHashMap interfaces = new IntHashMap(); public final List extenders = new ArrayList(); public final Map matchCache = new HashMap(); public final Callback callback; @@ -140,56 +147,191 @@ for(Iterator iter = pdata.classes.values().iterator(); iter.hasNext(); ) { extender.callMethod(tc, "extend_proxy", (IRubyObject)iter.next()); } + for(Iterator iter = pdata.interfaces.values().iterator(); iter.hasNext(); ) { + extender.callMethod(tc, "extend_proxy", (IRubyObject)iter.next()); + } return recv.getRuntime().getNil(); } - public static IRubyObject get_proxy_class(IRubyObject recv, IRubyObject java_class) { - if(java_class instanceof RubyString) { - java_class = JavaClass.for_name(recv, java_class); + private static boolean supportExtendableInterfaces = false; + private static boolean supportExtendableInterfaces() { + // TODO: some kind of user mechanism to enable this + return supportExtendableInterfaces; + } + public static IRubyObject set_deprecated_interface_syntax(IRubyObject recv, IRubyObject object, Block unusedBlock) { + supportExtendableInterfaces = object.isTrue(); + return object; + } + + public static IRubyObject get_interface_module(IRubyObject recv, IRubyObject javaClassObject) { + Ruby runtime = recv.getRuntime(); + JavaClass javaClass; + if (javaClassObject instanceof RubyString) { + javaClass = JavaClass.for_name(recv, javaClassObject); + } else if (javaClassObject instanceof JavaClass) { + javaClass = (JavaClass)javaClassObject; + } else { + throw runtime.newArgumentError("expected JavaClass, got " + javaClassObject); } + if ( !javaClass.javaClass().isInterface()) { + throw runtime.newArgumentError(javaClass.toString() + " is not an interface"); + } + int class_id = RubyNumeric.fix2int(javaClass.id()); + ProxyData pdata = ((ProxyData)recv.dataGetStruct()); + IntHashMap interfaces = pdata.interfaces; + RubyModule interfaceModule; + synchronized(javaClass) { + if ((interfaceModule = (RubyModule)interfaces.get(class_id)) == null) { + interfaceModule = (RubyModule)runtime.getModule("JavaInterfaceTemplate").dup(); + interfaceModule.setInstanceVariable("@java_class",javaClass); + addToJavaPackageModule2(interfaceModule,javaClass); + interfaces.put(class_id,interfaceModule); + javaClass.setupInterfaceModule(interfaceModule); + // include any interfaces we extend + Class[] extended = javaClass.javaClass().getInterfaces(); + for (int i = extended.length; --i >= 0; ) { + JavaClass extendedClass = JavaClass.get(runtime,extended[i]); + RubyModule extModule; + if ((extModule = + (RubyModule)interfaces.get(RubyNumeric.fix2int(extendedClass.id()))) == null) { + extModule = (RubyModule)get_interface_module(recv,extendedClass); + } + interfaceModule.includeModule(extModule); + } + for(Iterator iter = pdata.extenders.iterator(); iter.hasNext(); ) { + ((IRubyObject)iter.next()).callMethod( + runtime.getCurrentContext(), "extend_proxy", interfaceModule); + } + } + } + return interfaceModule; + } + + // Note: this isn't really all that deprecated, as it is used for + // internal purposes, at least for now. But users should be discouraged + // from calling this directly; eventually it will go away. + public static IRubyObject get_deprecated_interface_proxy(IRubyObject recv, IRubyObject java_class_object) { + Ruby runtime = recv.getRuntime(); + JavaClass java_class; + if (java_class_object instanceof RubyString) { + java_class = JavaClass.for_name(recv, java_class_object); + } else if (java_class_object instanceof JavaClass) { + java_class = (JavaClass)java_class_object; + } else { + throw runtime.newArgumentError("expected JavaClass, got " + java_class_object); + } + if (!java_class.javaClass().isInterface()) { + throw runtime.newArgumentError("expected Java interface class, got " + java_class_object); + } int class_id = RubyNumeric.fix2int(java_class.id()); + ProxyData pdata = ((ProxyData)recv.dataGetStruct()); + IntHashMap proxy_classes = pdata.classes; + RubyClass proxy_class; + synchronized(java_class) { + if((proxy_class = (RubyClass)proxy_classes.get(class_id)) == null) { + RubyModule interfaceModule = (RubyModule)get_interface_module(recv,java_class); + proxy_class = createProxyClass(recv,runtime.getClass("InterfaceJavaProxy"), + java_class,true,proxy_classes,class_id); + // including interface module so old-style interface "subclasses" will + // respond correctly to #kind_of?, etc. + proxy_class.includeModule(interfaceModule); + // add reference to interface module + if (proxy_class.getConstantAt("Includable") == null) { + proxy_class.const_set(runtime.newSymbol("Includable"),interfaceModule); + } + + for(Iterator iter = pdata.extenders.iterator(); iter.hasNext(); ) { + ((IRubyObject)iter.next()).callMethod( + runtime.getCurrentContext(), "extend_proxy", proxy_class); + } + } + } + return proxy_class; + } + + public static IRubyObject get_proxy_class(IRubyObject recv, IRubyObject java_class_object) { Ruby runtime = recv.getRuntime(); + JavaClass java_class; + if (java_class_object instanceof RubyString) { + java_class = JavaClass.for_name(recv, java_class_object); + } else if (java_class_object instanceof JavaClass) { + java_class = (JavaClass)java_class_object; + } else { + throw runtime.newArgumentError("expected JavaClass, got " + java_class_object); + } + Class c; + if ((c = java_class.javaClass()).isInterface() && !supportExtendableInterfaces()) { + return get_interface_module(recv,java_class); + } + int class_id = RubyNumeric.fix2int(java_class.id()); ProxyData pdata = ((ProxyData)recv.dataGetStruct()); IntHashMap proxy_classes = pdata.classes; + RubyClass proxy_class; synchronized(java_class) { - if(proxy_classes.get(class_id) == null) { - Class c = ((JavaClass)java_class).javaClass(); - RubyClass base_type; - boolean concrete = false; - boolean invokeInherited = true; + if((proxy_class = (RubyClass)proxy_classes.get(class_id)) == null) { + if(c.isInterface()) { - base_type = runtime.getClass("InterfaceJavaProxy"); + RubyModule interfaceModule = (RubyModule)get_interface_module(recv,java_class); + // backwards compatibility mode + proxy_class = createProxyClass(recv,runtime.getClass("InterfaceJavaProxy"), + java_class,true,proxy_classes,class_id); + // including interface module so old-style interface "subclasses" will + // respond correctly to #kind_of?, etc. + proxy_class.includeModule(interfaceModule); + // add reference to interface module + if (proxy_class.getConstantAt("Includable") == null) { + proxy_class.const_set(runtime.newSymbol("Includable"),interfaceModule); + } + } else if(c.isArray()) { - base_type = runtime.getClass("ArrayJavaProxy"); + proxy_class = createProxyClass(recv,runtime.getClass("ArrayJavaProxy"), + java_class,true,proxy_classes,class_id); + + } else if (c.isPrimitive()) { + proxy_class = createProxyClass(recv,runtime.getClass("ConcreteJavaProxy"), + java_class,true,proxy_classes,class_id); + + } else if (c == Object.class) { + // java.lang.Object is added at root of java proxy classes + proxy_class = createProxyClass(recv,runtime.getClass("ConcreteJavaProxy"), + java_class,true,proxy_classes,class_id); + proxy_class.getMetaClass().defineFastMethod("inherited", pdata.callback); + addToJavaPackageModule2(proxy_class,java_class); + } else { - concrete = true; - if (Object.class.equals(c) || c.isPrimitive()) { - base_type = runtime.getClass("ConcreteJavaProxy"); - } else { - base_type = (RubyClass)get_proxy_class(recv,runtime.newString(c.getSuperclass().getName())); - invokeInherited = false; + // other java proxy classes added under their superclass' java proxy + proxy_class = createProxyClass(recv, + get_proxy_class(recv,runtime.newString(c.getSuperclass().getName())), + java_class,false,proxy_classes,class_id); + + // include interface modules into the proxy class + Class[] interfaces = c.getInterfaces(); + for (int i = interfaces.length; --i >= 0; ) { + JavaClass ifc = JavaClass.get(runtime,interfaces[i]); + proxy_class.includeModule(get_interface_module(recv,ifc)); } + if (Modifier.isPublic(c.getModifiers())) addToJavaPackageModule2(proxy_class,java_class); } - RubyClass proxy_class = RubyClass.newClass(recv, new IRubyObject[]{base_type}, Block.NULL_BLOCK,invokeInherited); - proxy_class.callMethod(runtime.getCurrentContext(), "java_class=", java_class); - if(concrete && invokeInherited) { - proxy_class.getMetaClass().defineFastMethod("inherited", pdata.callback); - } - proxy_classes.put(class_id, proxy_class); - // We do not setup the proxy before we register it so that same-typed constants do - // not try and create a fresh proxy class and go into an infinite loop - ((JavaClass)java_class).setupProxy(proxy_class); for(Iterator iter = pdata.extenders.iterator(); iter.hasNext(); ) { - ((IRubyObject)iter.next()).callMethod(runtime.getCurrentContext(), "extend_proxy", proxy_class); + ((IRubyObject)iter.next()).callMethod( + runtime.getCurrentContext(), "extend_proxy", proxy_class); } -// if (concrete && Modifier.isPublic(c.getModifiers()) && -// !c.isPrimitive() && useJavaPackageModules(runtime)) { -// addToJavaPackageModule(proxy_class,(JavaClass)java_class); -// } } } - return (IRubyObject)proxy_classes.get(class_id); + return proxy_class; } + + private static RubyClass createProxyClass(IRubyObject recv, IRubyObject baseType, + JavaClass javaClass, boolean invokeInherited, IntHashMap proxyClasses, int classId ) { + RubyClass proxyClass = RubyClass.newClass(recv, new IRubyObject[]{ baseType }, + Block.NULL_BLOCK, invokeInherited); + proxyClass.callMethod(recv.getRuntime().getCurrentContext(), "java_class=", javaClass); + proxyClasses.put(classId, proxyClass); + // We do not setup the proxy before we register it so that same-typed constants do + // not try and create a fresh proxy class and go into an infinite loop + javaClass.setupProxy(proxyClass); + return proxyClass; + } public static IRubyObject concrete_proxy_inherited(IRubyObject recv, IRubyObject subclass) { Ruby runtime = recv.getRuntime(); @@ -200,7 +342,7 @@ return runtime.getModule("JavaUtilities").callMethod(tc, "setup_java_subclass", new IRubyObject[]{subclass, recv.callMethod(tc,"java_class")}); } - public static boolean useJavaPackageModules (Ruby runtime) { + private static boolean useJavaPackageModules (Ruby runtime) { final RubyString javaModuleVar = runtime.newString("JRUBY_JAVA_MODULES"); RubyHash h = ((RubyHash)runtime.getObject().getConstant("ENV")); IRubyObject useMods = h.aref(javaModuleVar); @@ -211,7 +353,8 @@ ! "false".equals(useMods.toString())); } - private static void addToJavaPackageModule(RubyClass proxyClass, JavaClass javaClass) { + // package scheme 1: module per package segment, all caps: Java::JAVA::LANG::Object + private static void addToJavaPackageModule(RubyModule proxyClass, JavaClass javaClass) { Class clazz = javaClass.javaClass(); String fullName = clazz.getName(); int endPackage; @@ -229,7 +372,7 @@ String moduleName = fullName.substring(start,offset).toUpperCase(); IRubyObject child = parent.getConstantAt(moduleName); if (child == null) { - child = parent.defineModuleUnder(moduleName); + child = createPackageModule(parent,moduleName,fullName.substring(0,offset)); } else if (!(child instanceof RubyModule)) { return; } @@ -241,6 +384,72 @@ } } + // package scheme 2: separate module for each full package name, constructed + // from the camel-cased package segments: Java::JavaLang::Object, + private static void addToJavaPackageModule2(RubyModule proxyClass, JavaClass javaClass) { + Class clazz = javaClass.javaClass(); + String fullName; + if ((fullName = clazz.getName()) == null) return; + int endPackage = fullName.lastIndexOf('.'); + // we'll only map conventional class names to modules + if (fullName.indexOf('$') != -1 || !Character.isUpperCase(fullName.charAt(endPackage + 1))) { + return; + } + String packageName; + if (endPackage < 0) { + packageName = "Default"; + } else { + StringBuffer buf = new StringBuffer(); + for (int start = 0, offset = 0; start < endPackage; start = offset + 1) { + offset = fullName.indexOf('.',start); + buf.append(Character.toUpperCase(fullName.charAt(start))) + .append(fullName.substring(start+1,offset)); + } + packageName = buf.toString(); + } + Ruby runtime = proxyClass.getRuntime(); + RubyModule javaModule = runtime.getModule("Java"); + IRubyObject packageModule = javaModule.getConstantAt(packageName); + if (packageModule == null) { + String packageString = endPackage < 0 ? "" : fullName.substring(0,endPackage); + packageModule = createPackageModule(javaModule,packageName,packageString); + } else if (!(packageModule instanceof RubyModule)) { + return; + } + String className = fullName.substring(endPackage + 1); + if (((RubyModule)packageModule).getConstantAt(className) == null) { + ((RubyModule)packageModule).const_set(runtime.newSymbol(className),proxyClass); + } + } + + private static RubyModule createPackageModule(RubyModule parent, String name, String packageString) { + Ruby runtime = parent.getRuntime(); + RubyModule packageModule = (RubyModule)runtime.getModule("JavaPackageModuleTemplate").dup(); + packageModule.setInstanceVariable("@package_name",runtime.newString( + packageString.length() > 0 ? packageString + '.' : packageString)); + parent.const_set(runtime.newSymbol(name),packageModule); + return packageModule; + } + + private static final Pattern CAMEL_CASE_PACKAGE_SPLITTER = Pattern.compile("([a-z])([A-Z])"); + + public static IRubyObject get_package_module(IRubyObject recv, IRubyObject symObject) { + String sym = symObject.asSymbol(); + RubyModule javaModule = recv.getRuntime().getModule("Java"); + IRubyObject value; + if ((value = javaModule.getConstantAt(sym)) != null) { + return value; + } + String packageName; + if ("Default".equals(sym)) { + packageName = ""; + } else { + Matcher m = CAMEL_CASE_PACKAGE_SPLITTER.matcher(sym); + packageName = m.replaceAll("$1.$2").toLowerCase(); + } + return createPackageModule(javaModule,sym,packageName); + } + public static IRubyObject matching_method(IRubyObject recv, IRubyObject methods, IRubyObject args) { Map matchCache = ((ProxyData)recv.dataGetStruct()).matchCache; @@ -562,7 +771,7 @@ Class[] interfaces = new Class[size]; for (int i = 0; i < size; i++) { if (!(args[i] instanceof JavaClass) || !((JavaClass)args[i]).interface_p().isTrue()) { - throw recv.getRuntime().newArgumentError("Java interface expected."); + throw recv.getRuntime().newArgumentError("Java interface expected. got: " + args[i]); } interfaces[i] = ((JavaClass) args[i]).javaClass(); } Index: src/org/jruby/javasupport/JavaMethod.java =================================================================== --- src/org/jruby/javasupport/JavaMethod.java (revision 3594) +++ src/org/jruby/javasupport/JavaMethod.java (working copy) @@ -158,23 +158,16 @@ // this test really means, that this is a ruby-defined subclass of a java class // if (javaInvokee instanceof InternalJavaProxy && - // don't bother to check if final method, it won't be there + // don't bother to check if final method, it won't + // be there (not generated, can't be!) !Modifier.isFinal(method.getModifiers())) { JavaProxyClass jpc = ((InternalJavaProxy) javaInvokee) .___getProxyClass(); JavaProxyMethod jpm; - try { - jpm = jpc.getMethod(method.getName(), parameterTypes); - } catch (NoSuchMethodException e) { - // ok, this just means there's no generated proxy method (which - // there wouldn't be for final methods, possibly other cases?). - // Try to invoke anyway. - return invokeWithExceptionHandling(method, javaInvokee, arguments); + if ((jpm = jpc.getMethod(method.getName(), parameterTypes)) != null && + jpm.hasSuperImplementation()) { + return invokeWithExceptionHandling(jpm.getSuperMethod(), javaInvokee, arguments); } - if (jpm.hasSuperImplementation()) { - return invokeWithExceptionHandling(jpm.getSuperMethod(), - javaInvokee, arguments); - } } return invokeWithExceptionHandling(method, javaInvokee, arguments); } Index: src/org/jruby/RubyModule.java =================================================================== --- src/org/jruby/RubyModule.java (revision 3594) +++ src/org/jruby/RubyModule.java (working copy) @@ -544,6 +544,10 @@ addMethod(name, new SimpleCallbackMethod(this, method, visibility)); } + public void defineFastMethod(String name, Callback method, Visibility visibility) { + addMethod(name, new SimpleCallbackMethod(this, method, visibility)); + } + public void definePrivateMethod(String name, Callback method) { addMethod(name, new FullFunctionCallbackMethod(this, method, Visibility.PRIVATE)); } @@ -552,6 +556,10 @@ addMethod(name, new SimpleCallbackMethod(this, method, Visibility.PRIVATE)); } + public void defineFastProtectedMethod(String name, Callback method) { + addMethod(name, new SimpleCallbackMethod(this, method, Visibility.PROTECTED)); + } + public void undefineMethod(String name) { addMethod(name, UndefinedMethod.getInstance()); }