Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ji] bind interface default methods #6484

Merged
merged 5 commits into from Dec 7, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -19,56 +19,56 @@ public InstanceMethodInvoker(RubyModule host, Supplier<Method[]> methods, String

@Override
public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject[] args) {
JavaProxy proxy = castJavaProxy(self);
Object target = unwrapIfJavaProxy(self);
JavaMethod method = (JavaMethod) findCallable(self, name, args, args.length);
return method.invokeDirect( context, proxy.getObject(), convertArguments(method, args) );
return method.invokeDirect( context, target, convertArguments(method, args) );
}

@Override
public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name) {
if (javaVarargsCallables != null) return call(context, self, clazz, name, IRubyObject.NULL_ARRAY);
JavaProxy proxy = castJavaProxy(self);
Object target = unwrapIfJavaProxy(self);
JavaMethod method = (JavaMethod) findCallableArityZero(self, name);
return method.invokeDirect(context, proxy.getObject());
return method.invokeDirect(context, target);
}

@Override
public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject arg0) {
if (javaVarargsCallables != null) return call(context, self, clazz, name, new IRubyObject[] {arg0});
JavaProxy proxy = castJavaProxy(self);
Object target = unwrapIfJavaProxy(self);
JavaMethod method = (JavaMethod) findCallableArityOne(self, name, arg0);
final Class<?>[] paramTypes = method.getParameterTypes();
Object cArg0 = arg0.toJava(paramTypes[0]);
return method.invokeDirect(context, proxy.getObject(), cArg0);
return method.invokeDirect(context, target, cArg0);
}

@Override
public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject arg0, IRubyObject arg1) {
if (javaVarargsCallables != null) return call(context, self, clazz, name, new IRubyObject[] {arg0, arg1});
JavaProxy proxy = castJavaProxy(self);
Object target = unwrapIfJavaProxy(self);
JavaMethod method = (JavaMethod) findCallableArityTwo(self, name, arg0, arg1);
final Class<?>[] paramTypes = method.getParameterTypes();
Object cArg0 = arg0.toJava(paramTypes[0]);
Object cArg1 = arg1.toJava(paramTypes[1]);
return method.invokeDirect(context, proxy.getObject(), cArg0, cArg1);
return method.invokeDirect(context, target, cArg0, cArg1);
}

@Override
public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject arg0, IRubyObject arg1, IRubyObject arg2) {
if (javaVarargsCallables != null) return call(context, self, clazz, name, new IRubyObject[] {arg0, arg1, arg2});
JavaProxy proxy = castJavaProxy(self);
Object target = unwrapIfJavaProxy(self);
JavaMethod method = (JavaMethod) findCallableArityThree(self, name, arg0, arg1, arg2);
final Class<?>[] paramTypes = method.getParameterTypes();
Object cArg0 = arg0.toJava(paramTypes[0]);
Object cArg1 = arg1.toJava(paramTypes[1]);
Object cArg2 = arg2.toJava(paramTypes[2]);
return method.invokeDirect(context, proxy.getObject(), cArg0, cArg1, cArg2);
return method.invokeDirect(context, target, cArg0, cArg1, cArg2);
}

@Override
public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject[] args, Block block) {
if ( block.isGiven() ) {
JavaProxy proxy = castJavaProxy(self);
Object target = unwrapIfJavaProxy(self);
final int len = args.length;
// these extra arrays are really unfortunate; split some of these paths out to eliminate?
IRubyObject[] newArgs = ArraySupport.newCopy(args, RubyProc.newProc(context.runtime, block, block.type));
Expand All @@ -81,65 +81,65 @@ public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule claz
convertedArgs[i] = newArgs[i].toJava(paramTypes[i]);
}

return method.invokeDirect(context, proxy.getObject(), convertedArgs);
return method.invokeDirect(context, target, convertedArgs);
}
return call(context, self, clazz, name, args);
}

@Override
public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, Block block) {
if (block.isGiven()) {
JavaProxy proxy = castJavaProxy(self);
Object target = unwrapIfJavaProxy(self);
RubyProc proc = RubyProc.newProc(context.runtime, block, block.type);
JavaMethod method = (JavaMethod) findCallableArityOne(self, name, proc);
final Class<?>[] paramTypes = method.getParameterTypes();
Object cArg0 = proc.toJava(paramTypes[0]);
return method.invokeDirect(context, proxy.getObject(), cArg0);
return method.invokeDirect(context, target, cArg0);
}
return call(context, self, clazz, name);
}

@Override
public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject arg0, Block block) {
if (block.isGiven()) {
JavaProxy proxy = castJavaProxy(self);
Object target = unwrapIfJavaProxy(self);
RubyProc proc = RubyProc.newProc(context.runtime, block, block.type);
JavaMethod method = (JavaMethod) findCallableArityTwo(self, name, arg0, proc);
final Class<?>[] paramTypes = method.getParameterTypes();
Object cArg0 = arg0.toJava(paramTypes[0]);
Object cArg1 = proc.toJava(paramTypes[1]);
return method.invokeDirect(context, proxy.getObject(), cArg0, cArg1);
return method.invokeDirect(context, target, cArg0, cArg1);
}
return call(context, self, clazz, name, arg0);
}

@Override
public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject arg0, IRubyObject arg1, Block block) {
if (block.isGiven()) {
JavaProxy proxy = castJavaProxy(self);
Object target = unwrapIfJavaProxy(self);
RubyProc proc = RubyProc.newProc(context.runtime, block, block.type);
JavaMethod method = (JavaMethod) findCallableArityThree(self, name, arg0, arg1, proc);
final Class<?>[] paramTypes = method.getParameterTypes();
Object cArg0 = arg0.toJava(paramTypes[0]);
Object cArg1 = arg1.toJava(paramTypes[1]);
Object cArg2 = proc.toJava(paramTypes[2]);
return method.invokeDirect(context, proxy.getObject(), cArg0, cArg1, cArg2);
return method.invokeDirect(context, target, cArg0, cArg1, cArg2);
}
return call(context, self, clazz, name, arg0, arg1);
}

@Override
public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject arg0, IRubyObject arg1, IRubyObject arg2, Block block) {
if (block.isGiven()) {
JavaProxy proxy = castJavaProxy(self);
Object target = unwrapIfJavaProxy(self);
RubyProc proc = RubyProc.newProc(context.runtime, block, block.type);
JavaMethod method = (JavaMethod)findCallableArityFour(self, name, arg0, arg1, arg2, proc);
final Class<?>[] paramTypes = method.getParameterTypes();
Object cArg0 = arg0.toJava(paramTypes[0]);
Object cArg1 = arg1.toJava(paramTypes[1]);
Object cArg2 = arg2.toJava(paramTypes[2]);
Object cArg3 = proc.toJava(paramTypes[3]);
return method.invokeDirect(context, proxy.getObject(), cArg0, cArg1, cArg2, cArg3);
return method.invokeDirect(context, target, cArg0, cArg1, cArg2, cArg3);
}
return call(context, self, clazz, name, arg0, arg1, arg2);
}
Expand Down
10 changes: 10 additions & 0 deletions core/src/main/java/org/jruby/java/invokers/RubyToJavaInvoker.java
Expand Up @@ -336,6 +336,16 @@ static JavaProxy castJavaProxy(final IRubyObject self) {
return (JavaProxy) self;
}

static Object unwrapIfJavaProxy(final IRubyObject object) {
if (object instanceof JavaProxy) {
return ((JavaProxy) object).getObject();
}
// special case when target is a plain-old Ruby object
// e.g. in case of a `class Ruby; include java.some.Interface end`
// Interface's default methods will be set up as InstanceMethodInvokers
return object;
}

static <T extends AccessibleObject & Member> T setAccessible(T accessible) {
// TODO: Replace flag that's false on 9 with proper module checks
if (!Java.isAccessible(accessible) &&
Expand Down
Expand Up @@ -61,7 +61,8 @@ public static RubyModule setupProxyModule(Ruby runtime, final Class<?> javaClass
assert javaClass.isInterface();

proxy = new InterfaceInitializer(runtime, javaClass).initialize(proxy);
flagAsJavaProxy(proxy); return proxy;
flagAsJavaProxy(proxy);
return proxy;
}

private static void flagAsJavaProxy(final RubyModule proxy) {
Expand Down
Expand Up @@ -135,6 +135,7 @@ void initialize(Class<?> javaClass, RubyModule proxy) {
Map<String, AssignedName> instanceAssignedNames = javaSupport.getInstanceAssignedNames().get(javaClass);
if (javaClass.isInterface()) {
instanceAssignedNames.clear();
installInstanceMethods(proxy);
} else {
assignInstanceAliases();

Expand Down Expand Up @@ -288,8 +289,6 @@ public PartitionedMethods computeValue(Class cls) {
private static final ClassValue<Class<?>[]> INTERFACES = new ClassValue<Class<?>[]>() {
@Override
public Class<?>[] computeValue(Class cls) {
Class<?>[] baseInterfaces = cls.getInterfaces();

// Expand each interface's parent interfaces using a set
Set<Class<?>> interfaceSet = new HashSet<>();

Expand Down Expand Up @@ -582,7 +581,7 @@ void setupMethods(Class<?> javaClass) {

if (Modifier.isStatic(method.getModifiers())) {
prepareStaticMethod(javaClass, method, name);
} else if (!isInterface) {
} else if (!isInterface || method.isDefault()) {
prepareInstanceMethod(javaClass, method, name);
}
}
Expand Down
25 changes: 22 additions & 3 deletions spec/java_integration/interfaces/java8_methods_spec.rb
@@ -1,6 +1,6 @@
require File.dirname(__FILE__) + "/../spec_helper"

describe "an interface (Java 8+)" do
describe "an interface" do

before :all do
require 'tmpdir'; @tmpdir = Dir.mktmpdir
Expand Down Expand Up @@ -118,11 +118,30 @@
expect(method.name).to eq(:"bar()") # abstract
end if RUBY_VERSION > '1.9'

it "(default) java_method is callable" do
it "default java_method is callable" do
method = Java::Java8Interface.java_method(:foo, [ java.lang.Object ])
expect( method.bind(Java::Java8Implementor.new).call '' ).to eql 'foo Java8Implementor'
end

it "binds default method as instance method" do
expect( Java::Java8Interface.instance_methods(false) ).to include :foo
expect( Java::Java8Implementor.new.foo(42) ).to eq("42foo Java8Implementor")
end

it "binds default method as instance method (Ruby receiver)" do
klass = Class.new do
include java.util.Iterator
def hasNext; false end
def next; nil end
end
expect( java.util.Iterator.instance_methods(false) ).to include :remove
begin
klass.new.remove
rescue java.lang.UnsupportedOperationException
# pass
end
end

it "java_send works on implemented interface (default method)" do
impl = Java::Java8Implementor.new
expect(impl.java_send(:bar)).to eq("Java8Implementor")
Expand Down Expand Up @@ -228,4 +247,4 @@ def javac_compile(files)
compilation_task.call # returns boolean
end

end if ENV_JAVA['java.specification.version'] >= '1.8'
end