From 7a693a43a1f432f9363541972095a4deb700b532 Mon Sep 17 00:00:00 2001 From: kares Date: Fri, 15 Apr 2016 19:52:52 +0200 Subject: [PATCH 1/3] [ji] fix potential interface method conflicts from Ruby hierarchy on MyIface.impl by having all iface required methods setup in the implementation Ruby class to always dispatch to impl logic. also improves performance due dispatching directly to method_missing --- .../java/proxies/JavaInterfaceTemplate.java | 96 +++++++++++++++++-- .../interfaces/implementation_spec.rb | 22 ++++- 2 files changed, 109 insertions(+), 9 deletions(-) diff --git a/core/src/main/java/org/jruby/java/proxies/JavaInterfaceTemplate.java b/core/src/main/java/org/jruby/java/proxies/JavaInterfaceTemplate.java index b452f25b932..73817bf6c5e 100644 --- a/core/src/main/java/org/jruby/java/proxies/JavaInterfaceTemplate.java +++ b/core/src/main/java/org/jruby/java/proxies/JavaInterfaceTemplate.java @@ -11,6 +11,7 @@ import org.jruby.anno.JRubyMethod; import org.jruby.exceptions.RaiseException; import org.jruby.internal.runtime.methods.DynamicMethod; +import org.jruby.internal.runtime.methods.JavaMethod; import org.jruby.internal.runtime.methods.JavaMethod.JavaMethodN; import org.jruby.internal.runtime.methods.JavaMethod.JavaMethodOne; import org.jruby.internal.runtime.methods.JavaMethod.JavaMethodOneBlock; @@ -258,6 +259,7 @@ public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule claz public static void addRealImplClassNew(final RubyClass clazz) { clazz.setAllocator(new ObjectAllocator() { private Constructor proxyConstructor; + public IRubyObject allocate(Ruby runtime, RubyClass klazz) { // if we haven't been here before, reify the class Class reifiedClass = klazz.getReifiedClass(); @@ -358,17 +360,39 @@ public static IRubyObject impl(ThreadContext context, IRubyObject self, IRubyObj // RubySymbol implements a Java compareTo thus will always work } - RubyClass implClass = RubyClass.newClass(runtime, runtime.getObject()); - implClass.include(context, self); + RubyClass implClass = RubyClass.newClass(runtime, runtime.getObject()); // ImplClass = Class.new + implClass.include(context, self); // ImplClass.include Interface - final IRubyObject implObject = implClass.callMethod(context, "new"); + final BlockInterfaceImpl ifaceImpl = new BlockInterfaceImpl(implClass, implBlock, methodNames); + implClass.addMethod("method_missing", ifaceImpl); // def ImplClass.method_missing ... - implClass.addMethod("method_missing", new BlockInterfaceImpl(implClass, implBlock, methodNames)); + // + final Class ifaceClass = JavaClass.getJavaClass(context, ((RubyModule) self)); + if ( methodNames == null ) { + final BlockInterfaceImpl.ConcreteMethod implMethod = ifaceImpl.getConcreteMethod(); + for ( Method method : ifaceClass.getMethods() ) { + implClass.addMethodInternal(method.getName(), implMethod); // might add twice - its fine + } + } + else { + final BlockInterfaceImpl.ConcreteMethod implMethod = ifaceImpl.getConcreteMethod(); + final Method[] allMethods = ifaceClass.getDeclaredMethods(); + for ( IRubyObject methodName : methodNames ) { + final String name = methodName.toString(); + for ( int i = 0; i < allMethods.length; i++ ) { + // add if its a declared method of the interface or its super-interfaces + if ( name.equals( allMethods[i].getName() ) ) { + implClass.addMethodInternal(name, implMethod); + break; + } + } + } + } - return implObject; + return implClass.callMethod(context, "new"); // ImplClass.new } - private static final class BlockInterfaceImpl extends org.jruby.internal.runtime.methods.JavaMethod { + private static final class BlockInterfaceImpl extends JavaMethod { private final IRubyObject[] methodNames; // RubySymbol[] private final Block implBlock; @@ -398,6 +422,11 @@ else if ( Arrays.binarySearch(methodNames, args[0]) >= 0 ) { return clazz.getSuperClass().callMethod(context, "method_missing", args, block); } + @Override + public final IRubyObject call(ThreadContext context, IRubyObject self, RubyModule klazz, String name, Block block) { + return callImpl(context, klazz, block); // avoids checkArgumentCount + } + @Override public final IRubyObject call(ThreadContext context, IRubyObject self, RubyModule klazz, String name, IRubyObject arg0, Block block) { return callImpl(context, klazz, block, arg0); // avoids checkArgumentCount @@ -413,7 +442,60 @@ public final IRubyObject call(ThreadContext context, IRubyObject self, RubyModul return callImpl(context, klazz, block, arg0, arg1, arg2); // avoids checkArgumentCount } - //public DynamicMethod dup() { return this; } + public DynamicMethod dup() { return this; } + + final ConcreteMethod getConcreteMethod() { return new ConcreteMethod(); } + + private final class ConcreteMethod extends JavaMethod { + + ConcreteMethod() { + super(BlockInterfaceImpl.this.implementationClass, Visibility.PUBLIC); + } + + @Override + public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule klazz, String name, Block block) { + final IRubyObject[] nargs = new IRubyObject[] { context.runtime.newSymbol(name) }; + return BlockInterfaceImpl.this.callImpl(context, klazz, block, nargs); + } + + @Override + public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule klazz, String name, IRubyObject arg0, Block block) { + final IRubyObject[] nargs = new IRubyObject[] { context.runtime.newSymbol(name), arg0 }; + return BlockInterfaceImpl.this.callImpl(context, klazz, block, nargs); + } + + @Override + public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule klazz, String name, IRubyObject arg0, IRubyObject arg1, Block block) { + final IRubyObject[] nargs = new IRubyObject[] { context.runtime.newSymbol(name), arg0, arg1 }; + return BlockInterfaceImpl.this.callImpl(context, klazz, block, nargs); + } + + @Override + public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule klazz, String name, IRubyObject arg0, IRubyObject arg1, IRubyObject arg2, Block block) { + final IRubyObject[] nargs = new IRubyObject[] { context.runtime.newSymbol(name), arg0, arg1, arg2 }; + return BlockInterfaceImpl.this.callImpl(context, klazz, block, nargs); + } + + @Override + public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule klazz, String name, IRubyObject[] args, Block block) { + switch (args.length) { + case 0: + return call(context, self, klazz, name, block); + case 1: + return call(context, self, klazz, name, args[0], block); + case 2: + return call(context, self, klazz, name, args[0], args[1], block); + case 3: + return call(context, self, klazz, name, args[0], args[1], args[2], block); + default: + final IRubyObject[] nargs = new IRubyObject[args.length + 1]; + nargs[0] = context.runtime.newSymbol(name); + System.arraycopy(args, 0, nargs, 1, args.length); + return BlockInterfaceImpl.this.callImpl(context, klazz, block, nargs); + } + } + + } } diff --git a/spec/java_integration/interfaces/implementation_spec.rb b/spec/java_integration/interfaces/implementation_spec.rb index 41ed9c6d1d4..a8830d9ff09 100644 --- a/spec/java_integration/interfaces/implementation_spec.rb +++ b/spec/java_integration/interfaces/implementation_spec.rb @@ -233,7 +233,25 @@ def callIt expect(UsesSingleMethodInterface.new.callIt2(impl)).to eq(:callIt) end - it "passes correct arguments to proc .impl" do + it '.impl should always dispatch interface method (even when it conflicts from method in Ruby hierarchy)' do + begin + Kernel.module_eval { def callIt; raise RuntimeError.new('Kernel#callIt') end } + expect { callIt }.to raise_error(RuntimeError) + + impl = SingleMethodInterface.impl { |name| name ? 'CALL-IT' : 'FALSY!' } + + # NOTE: prior to 9.1 impls would fail with RuntimeError 'Kernel#callIt' + + expect(UsesSingleMethodInterface.new.callIt2(impl)).to eq 'CALL-IT' + + impl = DescendantOfSingleMethodInterface.impl { |name| name.to_s.upcase } + #expect(UsesSingleMethodInterface.new.callIt2(impl)).to eq 'CALLIT' + ensure + Kernel.send :remove_method, :callIt if defined? Kernel.callIt + end + end + + it "passes correct arguments to proc implementation" do Java::java.io.File.new('.').list do |dir, name| # FilenameFilter expect(dir).to be_kind_of(java.io.File) expect(name).to be_kind_of(String) @@ -254,7 +272,7 @@ def callIt end end - it "resolves 'ambiguous' method by proc argument count (with .impl)" do + it "resolves 'ambiguous' method by proc argument count (with proc-implementation)" do java.io.File.new('.').listFiles do |pathname| # FileFilter#accept(File) expect(pathname).to be_kind_of(java.io.File) end From cf11355a64e6d8c2e9cd21a7479d2113da3aab9a Mon Sep 17 00:00:00 2001 From: kares Date: Mon, 18 Apr 2016 11:32:15 +0200 Subject: [PATCH 2/3] [ji] only override abstract iface methods (by default on proc-impl) unless present we're keeping Iface.impl backwards compatible to override all instance methods by default (unless methods are specified or we introduced impl(false) to keep defaults) for proc-to-iface (functionak) impls implementing default methods makes no sense ... use cases such as Function#compose with a Ruby impl did not work previously --- .../java/codegen/RealClassGenerator.java | 27 ++++++- .../java/proxies/JavaInterfaceTemplate.java | 30 +++++-- .../interfaces/java8_methods_spec.rb | 78 ++++++++++++++++++- 3 files changed, 122 insertions(+), 13 deletions(-) diff --git a/core/src/main/java/org/jruby/java/codegen/RealClassGenerator.java b/core/src/main/java/org/jruby/java/codegen/RealClassGenerator.java index 9f5d12d6b1b..bfae122822f 100644 --- a/core/src/main/java/org/jruby/java/codegen/RealClassGenerator.java +++ b/core/src/main/java/org/jruby/java/codegen/RealClassGenerator.java @@ -74,8 +74,14 @@ public abstract class RealClassGenerator { private static final int V_BC = V1_6; // version used for generated byte-code - public static Map> buildSimpleToAllMap(Class[] interfaces, String[] superTypeNames) throws SecurityException { - Map> simpleToAll = new LinkedHashMap>(); + //public static Map> buildSimpleToAllMap(Class[] interfaces, String[] superTypeNames) + // throws SecurityException { + // return buildSimpleToAllMap(interfaces, superTypeNames, null); + //} + + static Map> buildSimpleToAllMap(Class[] interfaces, String[] superTypeNames, RubyClass implClass) + throws SecurityException { + final LinkedHashMap> simpleToAll = new LinkedHashMap<>(); // we're use the map's order to work-around bug when there's too getters for a property : // getFoo and isFoo in which case we make sure getFoo will come after isFoo in the map // so that the installed "foo" alias always triggers getFoo regardless of getMethods order @@ -84,6 +90,11 @@ public static Map> buildSimpleToAllMap(Class[] interfaces, for ( Method method : interfaces[i].getMethods() ) { final String name = method.getName(); if ( Modifier.isStatic(method.getModifiers()) ) continue; + if ( implClass != null ) { // only override default methods if present in implementing class + if ( ! Modifier.isAbstract(method.getModifiers()) && ! implClass.getMethods().containsKey(name) ) { + continue; + } + } List methods = simpleToAll.get(name); if (methods == null) { simpleToAll.put(name, methods = new ArrayList(6)); @@ -103,18 +114,26 @@ public static Map> buildSimpleToAllMap(Class[] interfaces, return simpleToAll; } + // NOTE: assuming this is only used for interface-impl generation from: Java.newInterfaceImpl public static Class createOldStyleImplClass(Class[] superTypes, RubyClass rubyClass, Ruby ruby, String name, ClassDefiningClassLoader classLoader) { String[] superTypeNames = new String[superTypes.length]; - Map> simpleToAll = buildSimpleToAllMap(superTypes, superTypeNames); + + // interfaces now do have a convention that they only override an interface default method + // if a Ruby method (stub) is present in the implementing Ruby class : + Map> simpleToAll = buildSimpleToAllMap(superTypes, superTypeNames, rubyClass); Class newClass = defineOldStyleImplClass(ruby, name, superTypeNames, simpleToAll, classLoader); return newClass; } + // NOTE: only used for interface class generation from ... Java.generateRealClass public static Class createRealImplClass(Class superClass, Class[] interfaces, RubyClass rubyClass, Ruby ruby, String name) { String[] superTypeNames = new String[interfaces.length]; - Map> simpleToAll = buildSimpleToAllMap(interfaces, superTypeNames); + + // interfaces now do have a convention that they only override an interface default method + // if a Ruby method (stub) is present in the implementing Ruby class : + Map> simpleToAll = buildSimpleToAllMap(interfaces, superTypeNames, rubyClass); Class newClass = defineRealImplClass(ruby, name, superClass, superTypeNames, simpleToAll); if (!newClass.isAssignableFrom(interfaces[0])) { diff --git a/core/src/main/java/org/jruby/java/proxies/JavaInterfaceTemplate.java b/core/src/main/java/org/jruby/java/proxies/JavaInterfaceTemplate.java index 73817bf6c5e..ee9dd39d3d9 100644 --- a/core/src/main/java/org/jruby/java/proxies/JavaInterfaceTemplate.java +++ b/core/src/main/java/org/jruby/java/proxies/JavaInterfaceTemplate.java @@ -2,9 +2,12 @@ import java.lang.reflect.Constructor; import java.lang.reflect.Method; +import java.lang.reflect.Modifier; import java.util.Arrays; + import org.jruby.Ruby; import org.jruby.RubyArray; +import org.jruby.RubyBoolean; import org.jruby.RubyClass; import org.jruby.RubyInstanceConfig; import org.jruby.RubyModule; @@ -344,7 +347,7 @@ public static IRubyObject op_aref(ThreadContext context, IRubyObject self, IRuby return JavaProxy.op_aref(context, self, args); } - @JRubyMethod(name = "impl", rest = true) + @JRubyMethod(name = "impl", rest = true) // impl(methods = true) public static IRubyObject impl(ThreadContext context, IRubyObject self, IRubyObject[] args, final Block implBlock) { final Ruby runtime = context.runtime; @@ -352,8 +355,13 @@ public static IRubyObject impl(ThreadContext context, IRubyObject self, IRubyObj throw runtime.newArgumentError("block required to call #impl on a Java interface"); } + boolean allMethods = true; final IRubyObject[] methodNames; if ( args.length == 0 ) methodNames = null; + else if ( args.length == 1 && args[0] instanceof RubyBoolean ) { + allMethods = args[0].isTrue(); // impl(false) ... allMethods = false + methodNames = null; + } else { methodNames = args.clone(); Arrays.sort(methodNames); // binarySearch needs a sorted array @@ -366,26 +374,34 @@ public static IRubyObject impl(ThreadContext context, IRubyObject self, IRubyObj final BlockInterfaceImpl ifaceImpl = new BlockInterfaceImpl(implClass, implBlock, methodNames); implClass.addMethod("method_missing", ifaceImpl); // def ImplClass.method_missing ... - // final Class ifaceClass = JavaClass.getJavaClass(context, ((RubyModule) self)); if ( methodNames == null ) { final BlockInterfaceImpl.ConcreteMethod implMethod = ifaceImpl.getConcreteMethod(); for ( Method method : ifaceClass.getMethods() ) { + if ( method.isBridge() || method.isSynthetic() ) continue; + if ( Modifier.isStatic( method.getModifiers() ) ) continue; + // override default methods (by default) - users should pass down method names or impl(false) { ... } + if ( ! allMethods && ! Modifier.isAbstract( method.getModifiers() ) ) continue; implClass.addMethodInternal(method.getName(), implMethod); // might add twice - its fine } } else { final BlockInterfaceImpl.ConcreteMethod implMethod = ifaceImpl.getConcreteMethod(); - final Method[] allMethods = ifaceClass.getDeclaredMethods(); - for ( IRubyObject methodName : methodNames ) { + final Method[] decMethods = ifaceClass.getDeclaredMethods(); + loop: for ( IRubyObject methodName : methodNames ) { final String name = methodName.toString(); - for ( int i = 0; i < allMethods.length; i++ ) { + for ( int i = 0; i < decMethods.length; i++ ) { + final Method method = decMethods[i]; + if ( method.isBridge() || method.isSynthetic() ) continue; + if ( Modifier.isStatic( method.getModifiers() ) ) continue; // add if its a declared method of the interface or its super-interfaces - if ( name.equals( allMethods[i].getName() ) ) { + if ( name.equals(decMethods[i].getName()) ) { implClass.addMethodInternal(name, implMethod); - break; + continue loop; } } + // did not continue (main) loop - passed method name not found in interface + runtime.getWarnings().warn("`" + name + "' is not a declared method in interface " + ifaceClass.getName()); } } diff --git a/spec/java_integration/interfaces/java8_methods_spec.rb b/spec/java_integration/interfaces/java8_methods_spec.rb index cd3ac7542f6..2797c2e6b04 100644 --- a/spec/java_integration/interfaces/java8_methods_spec.rb +++ b/spec/java_integration/interfaces/java8_methods_spec.rb @@ -13,11 +13,24 @@ abstract String bar() ; default CharSequence foo(Object prefix) { return prefix + "foo" + ' ' + bar(); } + + public static CharSequence foo(Java8Interface impl) { return impl.foo(""); } + public static CharSequence bar(Java8Interface impl) { return impl.bar(); } + + public static CharSequence subFoo(SubInterface impl) { return impl.foo(""); } + public static CharSequence subBar(SubInterface impl) { return impl.bar(); } + public static CharSequence subBaz(SubInterface impl) { return impl.baz(); } + + interface SubInterface extends Java8Interface { abstract String baz() ; } } JAVA files << (file = "#{@tmpdir}/Java8Interface.java"); File.open(file, 'w') { |f| f.print(src) } src = <<-JAVA + import java.util.*; + import java.util.stream.*; + import java.util.function.*; + public class Java8Implemtor implements Java8Interface { public String bar() { return getClass().getSimpleName(); } @@ -61,6 +74,14 @@ return "ambiguousWithBinaryOperator"; } + public static Function composepp(Function fx) { + return fx.compose(i -> i + 1); + } + + public static Function compose10pp() { + return composepp(i -> i + 10); + } + } JAVA files << (file = "#{@tmpdir}/Java8Implemtor.java"); File.open(file, 'w') { |f| f.print(src) } @@ -102,13 +123,13 @@ expect( method.bind(Java::Java8Implemtor.new).call '' ).to eql 'foo Java8Implemtor' end - it "java_send works on impl (default method)" do + it "java_send works on implemented interface (default method)" do impl = Java::Java8Implemtor.new expect(impl.java_send(:bar)).to eq("Java8Implemtor") expect(impl.java_send(:foo, [ java.lang.Object ], 11)).to eq("11foo Java8Implemtor") end - it "works with java.util.function-al interface with proc impl" do + it "works with java.util.function-al interface using proc implementation" do expect( Java::Java8Implemtor.withConsumerCall(1) do |i| expect(i).to eql 10 end ).to eql 2 @@ -121,6 +142,59 @@ expect( ret ).to be false end + it "does not override default methods using proc implementation" do + expect( Java::Java8Interface.bar { 'BAR' } ).to eq 'BAR' + expect( Java::Java8Interface.foo { 'BAR' } ).to eq 'foo BAR' # 'BAR' prior to 9.1 + + res = Java::Java8Implemtor.compose10pp # Java + 10 impl + expect( res.apply(1.to_java(:int)) ).to eql 12 + # NOTE: has been failing prior to 9.1 as { ... } override #apply as well as #compose default method! + res = Java::Java8Implemtor.composepp { |i| i + 10 } # Ruby + 10 impl + expect( res.apply(1.to_java(:int)) ).to eql 12 + end + + it "does not override default methods using proc implementation (non-functional interface)" do + expect( Java::Java8Interface.subBar { |*args| "#{args.inspect}-SUB-BAR" } ).to eq '[]-SUB-BAR' + expect( Java::Java8Interface.subBaz { |*args| "#{args.inspect}-SUB-BAR" } ).to eq '[]-SUB-BAR' + expect( Java::Java8Interface.subFoo { |*args| "#{args.inspect}-SUB-BAR" } ).to eq 'foo []-SUB-BAR' # [""]-SUB-BAR prior to 9.1 + end + + it 'interface .impl() overrides all methods (by default)' do + impl = Java::Java8Interface.impl { |*args| "#{args.inspect}-IMPL" } + # NOTE: (historically) override every method (including default methods) + expect( Java::Java8Interface.bar(impl) ).to eq '[:bar]-IMPL' + expect( Java::Java8Interface.foo(impl) ).to eq '[:foo, ""]-IMPL' + + impl = Java::Java8Interface.impl(true) { |*args| "#{args.inspect}-IMPL" } + expect( Java::Java8Interface.bar(impl) ).to eq '[:bar]-IMPL' + expect( Java::Java8Interface.foo(impl) ).to eq '[:foo, ""]-IMPL' + end + + it 'interface .impl(false) only overrides abstract methods' do + impl = Java::Java8Interface.impl(false) { |*args| "#{args.inspect}-IMPL" } + expect( Java::Java8Interface.bar(impl) ).to eq '[:bar]-IMPL' + expect( Java::Java8Interface.foo(impl) ).to eq 'foo [:bar]-IMPL' + + impl = Java::Java8Interface::SubInterface.impl(false) { |*args| "#{args.inspect}-IMPL" } + expect( Java::Java8Interface.bar(impl) ).to eq '[:bar]-IMPL' + expect( Java::Java8Interface.foo(impl) ).to eq 'foo [:bar]-IMPL' + end + + it 'interface .impl(method_names) only generates those methods' do + impl = Java::Java8Interface.impl(:bar) { |*args| "#{args.inspect}-IMPL" } + expect( Java::Java8Interface.bar(impl) ).to eq '[:bar]-IMPL' + # NOTE: previously - NoMethodError: undefined method `foo' for Java::Default::Java8Interface:Module + expect( Java::Java8Interface.foo(impl) ).to eq 'foo [:bar]-IMPL' + end + + it 'interface .impl with unknown methods passed in warns' do + output = with_stderr_captured do # prints a warning (since 9.1) + impl = Java::Java8Interface.impl(:baz) { |*args| "#{args.inspect}-IMPL" } + expect{ Java::Java8Interface.bar(impl) }.to raise_error(NoMethodError) + end + expect( output.index("`baz' is not a declared method in interface") ).to_not be nil + end + it "does not consider Map vs func-type Consumer ambiguous" do output = with_stderr_captured do # exact match should not warn : ret = Java::Java8Implemtor.ambiguousCall1('') do |c| From 79bb8d118043d9e0c472f02498845da03962f810 Mon Sep 17 00:00:00 2001 From: kares Date: Mon, 18 Apr 2016 12:03:41 +0200 Subject: [PATCH 3/3] [ji] at last - deal with Ruby - Java method conflicts with interface impl using a block we're now add an internal "impl" method for each prescribed abstract interface method this is expected to resolve conflicting issues (e.g using Java 8 streams) such as #3475 --- .../main/java/org/jruby/javasupport/Java.java | 53 ++++++++++++++++--- .../java/org/jruby/javasupport/JavaUtil.java | 16 +++++- 2 files changed, 60 insertions(+), 9 deletions(-) diff --git a/core/src/main/java/org/jruby/javasupport/Java.java b/core/src/main/java/org/jruby/javasupport/Java.java index 8be61a0ea69..8f9d7eb586e 100644 --- a/core/src/main/java/org/jruby/javasupport/Java.java +++ b/core/src/main/java/org/jruby/javasupport/Java.java @@ -65,9 +65,10 @@ import org.jruby.javasupport.binding.Initializer; import org.jruby.javasupport.proxy.JavaProxyClass; import org.jruby.javasupport.proxy.JavaProxyConstructor; -import org.jruby.runtime.Helpers; import org.jruby.runtime.Arity; import org.jruby.runtime.Block; +import org.jruby.runtime.Helpers; +import org.jruby.runtime.Visibility; import org.jruby.runtime.ThreadContext; import org.jruby.runtime.builtin.IRubyObject; import org.jruby.runtime.load.Library; @@ -995,7 +996,7 @@ public final IRubyObject call(ThreadContext context, IRubyObject self, RubyModul } - final static class ProcToInterface extends org.jruby.internal.runtime.methods.DynamicMethod { + static final class ProcToInterface extends org.jruby.internal.runtime.methods.DynamicMethod { ProcToInterface(final RubyClass singletonClass) { super(singletonClass, PUBLIC); @@ -1003,10 +1004,6 @@ final static class ProcToInterface extends org.jruby.internal.runtime.methods.Dy @Override // method_missing impl : public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject[] args, Block block) { - if ( ! ( self instanceof RubyProc ) ) { - throw context.runtime.newTypeError("interface impl method_missing for block used with non-Proc object"); - } - final RubyProc proc = (RubyProc) self; final IRubyObject[] newArgs; switch( args.length ) { case 1 : newArgs = IRubyObject.NULL_ARRAY; break; @@ -1015,7 +1012,14 @@ public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule claz default : newArgs = new IRubyObject[ args.length - 1 ]; System.arraycopy(args, 1, newArgs, 0, newArgs.length); } - return proc.call(context, newArgs); + return callProc(context, self, newArgs); + } + + private IRubyObject callProc(ThreadContext context, IRubyObject self, IRubyObject[] procArgs) { + if ( ! ( self instanceof RubyProc ) ) { + throw context.runtime.newTypeError("interface impl method_missing for block used with non-Proc object"); + } + return ((RubyProc) self).call(context, procArgs); } @Override @@ -1023,6 +1027,41 @@ public DynamicMethod dup() { return this; } + final ConcreteMethod getConcreteMethod() { return new ConcreteMethod(); } + + final class ConcreteMethod extends org.jruby.internal.runtime.methods.JavaMethod { + + ConcreteMethod() { + super(ProcToInterface.this.implementationClass, Visibility.PUBLIC); + } + + @Override + public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule klazz, String name, Block block) { + return ProcToInterface.this.callProc(context, self, IRubyObject.NULL_ARRAY); + } + + @Override + public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule klazz, String name, IRubyObject arg0, Block block) { + return ProcToInterface.this.callProc(context, self, new IRubyObject[]{arg0}); + } + + @Override + public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule klazz, String name, IRubyObject arg0, IRubyObject arg1, Block block) { + return ProcToInterface.this.callProc(context, self, new IRubyObject[]{arg0, arg1}); + } + + @Override + public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule klazz, String name, IRubyObject arg0, IRubyObject arg1, IRubyObject arg2, Block block) { + return ProcToInterface.this.callProc(context, self, new IRubyObject[]{arg0, arg1, arg2}); + } + + @Override + public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule klazz, String name, IRubyObject[] args, Block block) { + return ProcToInterface.this.callProc(context, self, args); + } + + } + } private static RubyModule getProxyUnderClass(final ThreadContext context, diff --git a/core/src/main/java/org/jruby/javasupport/JavaUtil.java b/core/src/main/java/org/jruby/javasupport/JavaUtil.java index d2b42969482..3f8d8969514 100644 --- a/core/src/main/java/org/jruby/javasupport/JavaUtil.java +++ b/core/src/main/java/org/jruby/javasupport/JavaUtil.java @@ -37,13 +37,14 @@ import java.io.UnsupportedEncodingException; import java.lang.reflect.Array; import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.ReflectPermission; import static java.lang.Character.isLetter; import static java.lang.Character.isLowerCase; import static java.lang.Character.isUpperCase; import static java.lang.Character.isDigit; import static java.lang.Character.toLowerCase; -import java.lang.reflect.ReflectPermission; import java.math.BigDecimal; import java.math.BigInteger; import java.security.AccessController; @@ -242,7 +243,18 @@ public static T convertProcToInterface(ThreadContext context, RubyBasicObjec // Proc implementing an interface, pull in the catch-all code that lets the proc get invoked // no matter what method is called on the interface final RubyClass singletonClass = rubyObject.getSingletonClass(); - singletonClass.addMethod("method_missing", new Java.ProcToInterface(singletonClass)); + final Java.ProcToInterface procToIface = new Java.ProcToInterface(singletonClass); + singletonClass.addMethod("method_missing", procToIface); + // similar to Iface.impl { ... } - bind interface method(s) to avoid Java-Ruby conflicts + // ... e.g. calling a Ruby implemented Predicate#test should not dispatch to Kernel#test + final Java.ProcToInterface.ConcreteMethod implMethod = procToIface.getConcreteMethod(); + // getMethods for interface returns all methods (including ones from super-interfaces) + for ( Method method : targetType.getMethods() ) { + if ( Modifier.isAbstract(method.getModifiers()) ) { + singletonClass.addMethodInternal(method.getName(), implMethod); + } + } + } JavaObject javaObject = (JavaObject) Helpers.invoke(context, rubyObject, "__jcreate_meta!"); return (T) javaObject.getValue();