Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: jruby/jruby
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 106cdebc0b9b
Choose a base ref
...
head repository: jruby/jruby
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: aedbf262bf18
Choose a head ref
  • 9 commits
  • 7 files changed
  • 1 contributor

Commits on Jul 16, 2015

  1. Copy the full SHA
    3264692 View commit details
  2. Copy the full SHA
    e893bb1 View commit details
  3. Copy the full SHA
    6c7320b View commit details
  4. avoid reflection in getFunctionalInterface

    ... while checking whether method.isDefault()
    kares committed Jul 16, 2015
    Copy the full SHA
    ddbed4b View commit details
  5. improve resolving functional-interfaces

    if there's an abstract method implemented by the Object.class
    (e.g. equals) continue looking for the "real" single method
    kares committed Jul 16, 2015
    Copy the full SHA
    f1353e3 View commit details
  6. support for matching proc-to-iface methods by arity

    ... of the block (see #3136) when there are 2 method with different
    interface type signature we can improve current (unpredictable) logic
    
    state prior to this commit is printing a ambiguous warning, the picked
    method depends on returned getMethods order
    kares committed Jul 16, 2015
    Copy the full SHA
    c054d50 View commit details
  7. method cache hash calc needs to acount for Proc

    so that we cache correctly cases with proc-to-iface dispatch
    (per Proc's arity)
    
    as a side effect redundant null checks were removed
    and the calculation should now compute the same when using args[]
    or the overloaded version (with args splatted)
    
    (closing #3136)
    kares committed Jul 16, 2015
    Copy the full SHA
    bd50c26 View commit details
  8. Copy the full SHA
    950cf3f View commit details
  9. no need for "" + ...

    kares committed Jul 16, 2015
    Copy the full SHA
    aedbf26 View commit details
97 changes: 76 additions & 21 deletions core/src/main/java/org/jruby/java/dispatch/CallableSelector.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.jruby.java.dispatch;

import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
@@ -13,6 +14,7 @@
import org.jruby.RubyFixnum;
import org.jruby.RubyFloat;
import org.jruby.RubyInteger;
import org.jruby.RubyProc;
import org.jruby.RubyString;
import org.jruby.javasupport.JavaCallable;
import org.jruby.javasupport.JavaClass;
@@ -22,6 +24,7 @@
import org.jruby.util.collections.IntHashMap;
import static org.jruby.util.CodegenUtils.getBoxType;
import static org.jruby.util.CodegenUtils.prettyParams;
import static org.jruby.javasupport.Java.getFunctionalInterfaceMethod;

/**
* Method selection logic for calling from Ruby to Java.
@@ -188,18 +191,27 @@ else if ( cType.isPrimitive() && msType.isAssignableFrom(getBoxType(cType)) ) {
ambiguous = true;
}
}

// somehow we can still decide e.g. if we got a RubyFixnum
// then (int) constructor shoudl be preffered over (float)
// then (int) constructor should be preferred over (float)
if ( ambiguous ) {
int msPref = 0, cPref = 0;
for ( int i = 0; i < msTypes.length; i++ ) {
final Class<?> msType = msTypes[i], cType = cTypes[i];
msPref += calcTypePreference(msType, args[i]);
cPref += calcTypePreference(cType, args[i]);
// special handling if we're dealing with Proc#impl :
final IRubyObject lastArg = args.length > 0 ? args[ args.length - 1 ] : null;
final T procToIfaceMatch = matchProcToInterfaceCandidate(lastArg, candidates);
if ( procToIfaceMatch != null ) {
mostSpecific = procToIfaceMatch; ambiguous = false;
}
else {
int msPref = 0, cPref = 0;
for ( int i = 0; i < msTypes.length; i++ ) {
final Class<?> msType = msTypes[i], cType = cTypes[i];
msPref += calcTypePreference(msType, args[i]);
cPref += calcTypePreference(cType, args[i]);
}
// for backwards compatibility we do not switch to cType as
// the better fit - we seem to lack tests on this front ...
if ( msPref > cPref ) ambiguous = false; // continue OUTER;
}
// for backwards compatibility we do not switch to cType as
// the better fit - we seem to lack tests on this front ...
if ( msPref > cPref ) ambiguous = false; // continue OUTER;
}
}
method = mostSpecific;
@@ -230,6 +242,39 @@ else if ( cType.isPrimitive() && msType.isAssignableFrom(getBoxType(cType)) ) {
return method;
}

private static <T extends ParameterTypes> T matchProcToInterfaceCandidate(
final IRubyObject lastArg, final List<T> candidates) {
if ( lastArg instanceof RubyProc ) {
// cases such as (both ifaces - differ in arg count) :
// java.io.File#listFiles(java.io.FileFilter) ... accept(File)
// java.io.File#listFiles(java.io.FilenameFilter) ... accept(File, String)
final int arity = ((RubyProc) lastArg).getBlock().arity().getValue();
T match = null;
for ( int i = 0; i < candidates.size(); i++ ) {
final T method = candidates.get(i);

final Class<?>[] params = method.getParameterTypes();

if ( params.length == 0 ) return null; // can not match (no args)
final Class<?> lastParam = params[ params.length - 1 ];

if ( ! lastParam.isInterface() ) return null; // can not match

final Method implMethod = getFunctionalInterfaceMethod(lastParam);
if ( implMethod != null ) {
// we're sure to have an interface in the end - match arg count :
// NOTE: implMethod.getParameterCount() on Java 8 would do ...
if ( implMethod.getParameterTypes().length == arity ) {
if ( match != null ) return null; // 2 with same arity (can not match)
match = method; // do not break here we want to check all
}
}
}
return match;
}
return null;
}

private static <T extends ParameterTypes> T findCallable(T[] callables, CallableAcceptor acceptor, IRubyObject[] args) {
T bestCallable = null;
int bestScore = -1;
@@ -551,35 +596,45 @@ private static boolean duckable(final Class<?> type, final IRubyObject arg) {
}

private static int argsHashCode(IRubyObject a0) {
return 31 + javaClassHashCode(a0);
return 31 + javaClassOrProcHashCode(a0);
}

private static int argsHashCode(IRubyObject a0, IRubyObject a1) {
return 31 * argsHashCode(a0) + javaClassHashCode(a1);
return 17 * ( 31 + javaClassHashCode(a0) ) +
javaClassOrProcHashCode(a1);
}

private static int argsHashCode(IRubyObject a0, IRubyObject a1, IRubyObject a2) {
return 31 * argsHashCode(a0, a1) + javaClassHashCode(a2);
return 17 * ( 17 * ( 31 + javaClassHashCode(a0) ) + javaClassHashCode(a1) ) +
javaClassOrProcHashCode(a2);
}

private static int argsHashCode(IRubyObject a0, IRubyObject a1, IRubyObject a2, IRubyObject a3) {
return 31 * argsHashCode(a0, a1, a2) + javaClassHashCode(a3);
return 17 * ( 17 * ( 17 * ( 31 + javaClassHashCode(a0) ) + javaClassHashCode(a1) ) + javaClassHashCode(a2) ) +
javaClassOrProcHashCode(a3);
}

private static int argsHashCode(IRubyObject[] args) {
if ( args == null ) return 0;
private static int argsHashCode(final IRubyObject[] args) {
final int last = args.length - 1;
if ( last == -1 ) return 0;

int result = 1;

for ( int i = 0; i < args.length; i++ ) {
result = 31 * result + javaClassHashCode(args[i]);
int result = 31;
for ( int i = 0; i < last; i++ ) {
result = 17 * ( result + javaClassHashCode( args[i] ) );
}

return result;
return result + javaClassOrProcHashCode( args[last] );
}

private static int javaClassHashCode(final IRubyObject arg) {
return arg == null ? 0 : arg.getJavaClass().hashCode();
// if ( arg == null ) return 0;
return arg.getJavaClass().hashCode();
}

private static int javaClassOrProcHashCode(final IRubyObject arg) {
// if ( arg == null ) return 0;
final Class<?> javaClass = arg.getJavaClass();
return javaClass == RubyProc.class ? arg.hashCode() : javaClass.hashCode();
}

private static Class<?> getJavaClass(final IRubyObject arg) {
62 changes: 61 additions & 1 deletion core/src/main/java/org/jruby/javasupport/Java.java
Original file line number Diff line number Diff line change
@@ -72,7 +72,6 @@
import org.jruby.runtime.Arity;
import org.jruby.runtime.Block;
import org.jruby.runtime.ThreadContext;
import static org.jruby.runtime.Visibility.*;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.runtime.load.Library;
import org.jruby.anno.JRubyMethod;
@@ -107,10 +106,13 @@
import org.jruby.util.ClassProvider;
import org.jruby.util.CodegenUtils;
import org.jruby.util.IdUtil;
import org.jruby.util.SafePropertyAccessor;
import org.jruby.util.cli.Options;
import org.jruby.util.collections.IntHashMap;

import static org.jruby.java.dispatch.CallableSelector.newCallableCache;
import static org.jruby.java.invokers.RubyToJavaInvoker.convertArguments;
import static org.jruby.runtime.Visibility.*;

@JRubyModule(name = "Java")
public class Java implements Library {
@@ -1474,4 +1476,62 @@ private static RubyClass createProxyClass(final Ruby runtime,
final RubyClass baseType, final JavaClass javaClass, boolean invokeInherited) {
return createProxyClass(runtime, RubyClass.newClass(runtime, baseType), javaClass, invokeInherited);
}

/**
* @param iface
* @return the sole un-implemented method for a functional-style interface or null
* @note This method is internal and might be subject to change, do not assume its part of JRuby's API!
*/
public static Method getFunctionalInterfaceMethod(final Class<?> iface) {
assert iface.isInterface();
Method single = null;
for ( final Method method : iface.getMethods() ) {
final int mod = method.getModifiers();
if ( Modifier.isStatic(mod) ) continue;
if ( Modifier.isAbstract(mod) ) {
try { // check if it's equals, hashCode etc. :
Object.class.getMethod(method.getName(), method.getParameterTypes());
continue; // abstract but implemented by java.lang.Object
}
catch (NoSuchMethodException e) { /* fall-thorough */ }
catch (SecurityException e) {
// NOTE: we could try check for FunctionalInterface on Java 8
}
}
else continue; // not-abstract ... default method
if ( single == null ) single = method;
else return null; // not a functional iface
}
return single;
}

static final boolean JAVA8;
static {
boolean java8 = false;
final String version = SafePropertyAccessor.getProperty("java.version", "0.0");
if ( version.length() > 2 ) {
int v = Character.getNumericValue( version.charAt(0) );
if ( v > 8 ) java8 = true; // 9.0
else if ( v == 1 ) {
v = Character.getNumericValue( version.charAt(2) ); // 1.8
if ( v < 10 && v >= 8 ) java8 = true;
}
// seems as no Java 10 support ... yet :)
}
JAVA8 = java8;
}

// TODO if about to compile against Java 8 this does not need to be reflective
static boolean isDefaultMethod(final Method method) {
if ( JAVA8 ) {
try {
return (Boolean) Method.class.getMethod("isDefault").invoke(method);
}
catch (NoSuchMethodException ex) { throw new RuntimeException(ex); }
catch (IllegalAccessException ex) { throw new RuntimeException(ex); }
catch (Exception ex) { /* noop */ }
}
return false;
}

}
24 changes: 12 additions & 12 deletions core/src/main/java/org/jruby/javasupport/JavaUtil.java
Original file line number Diff line number Diff line change
@@ -219,21 +219,21 @@ public static Class<?> primitiveToWrapper(final Class<?> type) {
return type.isPrimitive() ? CodegenUtils.getBoxType(type) : type;
}

@SuppressWarnings("unchecked")
public static boolean isDuckTypeConvertable(Class providedArgumentType, Class parameterType) {
return parameterType.isInterface() &&
! parameterType.isAssignableFrom(providedArgumentType) &&
RubyObject.class.isAssignableFrom(providedArgumentType);
public static boolean isDuckTypeConvertable(final Class<?> argumentType, final Class<?> targetType) {
return targetType.isInterface() &&
! targetType.isAssignableFrom(argumentType) &&
RubyObject.class.isAssignableFrom(argumentType);
}

public static Object convertProcToInterface(ThreadContext context, RubyObject rubyObject, Class target) {
return convertProcToInterface(context, (RubyBasicObject) rubyObject, target);
public static <T> T convertProcToInterface(ThreadContext context, RubyObject rubyObject, Class<T> targetType) {
return convertProcToInterface(context, (RubyBasicObject) rubyObject, targetType);
}

public static Object convertProcToInterface(ThreadContext context, RubyBasicObject rubyObject, Class target) {
@SuppressWarnings("unchecked")
public static <T> T convertProcToInterface(ThreadContext context, RubyBasicObject rubyObject, Class<T> targetType) {
final Ruby runtime = context.runtime;

final RubyModule ifaceModule = Java.getInterfaceModule(runtime, JavaClass.get(runtime, target));
final RubyModule ifaceModule = Java.getInterfaceModule(runtime, JavaClass.get(runtime, targetType));
if ( ! ifaceModule.isInstance(rubyObject) ) {
ifaceModule.callMethod(context, "extend_object", rubyObject);
ifaceModule.callMethod(context, "extended", rubyObject);
@@ -246,7 +246,7 @@ public static Object convertProcToInterface(ThreadContext context, RubyBasicObje
singletonClass.addMethod("method_missing", new Java.ProcToInterface(singletonClass));
}
JavaObject javaObject = (JavaObject) Helpers.invoke(context, rubyObject, "__jcreate_meta!");
return javaObject.getValue();
return (T) javaObject.getValue();
}

public static NumericConverter getNumericConverter(Class target) {
@@ -325,13 +325,13 @@ public static String getJavaPropertyName(final String beanMethodName) {
if ( maybeGetOrSet && ( beanMethodName.startsWith("get") || beanMethodName.startsWith("set") ) ) {
if (isUpperDigit(ch = beanMethodName.charAt(3))) {
if ( length == 4 ) return Character.toString(toLowerCase(ch));
return "" + toLowerCase(ch) + beanMethodName.substring(4);
return toLowerCase(ch) + beanMethodName.substring(4);
}
}
else if ( beanMethodName.startsWith("is") && length > 2 ) {
if (isUpperDigit(ch = beanMethodName.charAt(2))) {
if ( length == 3 ) return Character.toString( toLowerCase(ch) );
return "" + toLowerCase(ch) + beanMethodName.substring(3);
return toLowerCase(ch) + beanMethodName.substring(3);
}
}
return null;
58 changes: 54 additions & 4 deletions core/src/test/java/org/jruby/javasupport/TestJava.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.jruby.javasupport;

import junit.framework.TestCase;
import java.lang.reflect.Method;
import org.junit.Test;

import org.jruby.Ruby;

@@ -10,11 +11,11 @@ public static class C extends B {}

class B extends A {}

public class TestJava extends junit.framework.TestCase {

public class TestJava extends TestCase {

@Test
public void testProxyCreation() {
Ruby runtime = Ruby.newInstance();
final Ruby runtime = Ruby.newInstance();
try {
Java.getProxyClass(runtime, B.class);
assert(true);
@@ -23,4 +24,53 @@ public void testProxyCreation() {
fail(ae.toString());
}
}

@Test
public void testGetFunctionInterface() {
Method method;
method = Java.getFunctionalInterfaceMethod(java.lang.Runnable.class);
assertNotNull(method);
assertEquals("run", method.getName());

method = Java.getFunctionalInterfaceMethod(java.io.Serializable.class);
assertNull(method);

//if ( Java.JAVA8 ) { // compare and equals both abstract
method = Java.getFunctionalInterfaceMethod(java.util.Comparator.class);
assertNotNull(method);
assertEquals("compare", method.getName());
//}

method = Java.getFunctionalInterfaceMethod(java.lang.Comparable.class);
assertNotNull(method);
assertEquals("compareTo", method.getName());

method = Java.getFunctionalInterfaceMethod(java.lang.Iterable.class);
assertNotNull(method);
assertEquals("iterator", method.getName());

method = Java.getFunctionalInterfaceMethod(java.util.concurrent.ThreadFactory.class);
assertNotNull(method);

method = Java.getFunctionalInterfaceMethod(java.util.Enumeration.class);
assertNull(method);

method = Java.getFunctionalInterfaceMethod(FxRunnable1.class);
assertNotNull(method);
assertEquals("run", method.getName());

method = Java.getFunctionalInterfaceMethod(FxRunnable2.class);
assertNotNull(method);
assertEquals("run", method.getName());

method = Java.getFunctionalInterfaceMethod(NonFxRunnable.class);
assertNull(method);
}

private static interface FxRunnable1 extends Runnable { abstract void run() ; }

private static interface FxRunnable2 extends Runnable { /* inherited run() */ }

private static interface NonFxRunnable extends Runnable { public void doRun() ; }

}
Loading