Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 23 additions & 4 deletions core/src/main/java/org/jruby/java/codegen/RealClassGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, List<Method>> buildSimpleToAllMap(Class[] interfaces, String[] superTypeNames) throws SecurityException {
Map<String, List<Method>> simpleToAll = new LinkedHashMap<String, List<Method>>();
//public static Map<String, List<Method>> buildSimpleToAllMap(Class[] interfaces, String[] superTypeNames)
// throws SecurityException {
// return buildSimpleToAllMap(interfaces, superTypeNames, null);
//}

static Map<String, List<Method>> buildSimpleToAllMap(Class[] interfaces, String[] superTypeNames, RubyClass implClass)
throws SecurityException {
final LinkedHashMap<String, List<Method>> 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
Expand All @@ -84,6 +90,11 @@ public static Map<String, List<Method>> 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<Method> methods = simpleToAll.get(name);
if (methods == null) {
simpleToAll.put(name, methods = new ArrayList<Method>(6));
Expand All @@ -103,18 +114,26 @@ public static Map<String, List<Method>> 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<String, List<Method>> 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<String, List<Method>> 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<String, List<Method>> 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<String, List<Method>> simpleToAll = buildSimpleToAllMap(interfaces, superTypeNames, rubyClass);

Class newClass = defineRealImplClass(ruby, name, superClass, superTypeNames, simpleToAll);
if (!newClass.isAssignableFrom(interfaces[0])) {
Expand Down
118 changes: 108 additions & 10 deletions core/src/main/java/org/jruby/java/proxies/JavaInterfaceTemplate.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,19 @@

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;
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;
Expand Down Expand Up @@ -258,6 +262,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();
Expand Down Expand Up @@ -342,33 +347,68 @@ 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;

if ( ! implBlock.isGiven() ) {
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
// RubySymbol implements a Java compareTo thus will always work
}

RubyClass implClass = RubyClass.newClass(runtime, runtime.getObject());
implClass.include(context, self);

final IRubyObject implObject = implClass.callMethod(context, "new");

implClass.addMethod("method_missing", new BlockInterfaceImpl(implClass, implBlock, methodNames));
RubyClass implClass = RubyClass.newClass(runtime, runtime.getObject()); // ImplClass = Class.new
implClass.include(context, self); // ImplClass.include Interface

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[] decMethods = ifaceClass.getDeclaredMethods();
loop: for ( IRubyObject methodName : methodNames ) {
final String name = methodName.toString();
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(decMethods[i].getName()) ) {
implClass.addMethodInternal(name, implMethod);
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());
}
}

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;
Expand Down Expand Up @@ -398,6 +438,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
Expand All @@ -413,7 +458,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);
}
}

}

}

Expand Down
53 changes: 46 additions & 7 deletions core/src/main/java/org/jruby/javasupport/Java.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -995,18 +996,14 @@ 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);
}

@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;
Expand All @@ -1015,14 +1012,56 @@ 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
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,
Expand Down
16 changes: 14 additions & 2 deletions core/src/main/java/org/jruby/javasupport/JavaUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -242,7 +243,18 @@ public static <T> 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();
Expand Down
Loading