Skip to content

Commit

Permalink
Showing 7 changed files with 540 additions and 317 deletions.
8 changes: 6 additions & 2 deletions core/src/main/java/org/jruby/RubyBasicObject.java
Original file line number Diff line number Diff line change
@@ -825,6 +825,10 @@ public IRubyObject checkArrayType() {
*/
@Override
public Object toJava(Class target) {
return defaultToJava(target);
}

final <T> T defaultToJava(Class<T> target) {
// for callers that unconditionally pass null retval type (JRUBY-4737)
if (target == void.class) return null;

@@ -838,7 +842,7 @@ public Object toJava(Class target) {
if (target.isAssignableFrom(value.getClass())) {
getRuntime().getJavaSupport().getObjectProxyCache().put(value, this);

return value;
return (T) value;
}
}
else if (JavaUtil.isDuckTypeConvertable(getClass(), target)) {
@@ -847,7 +851,7 @@ else if (JavaUtil.isDuckTypeConvertable(getClass(), target)) {
}
}
else if (target.isAssignableFrom(getClass())) {
return this;
return (T) this;
}

throw getRuntime().newTypeError("cannot convert instance of " + getClass() + " to " + target);
598 changes: 321 additions & 277 deletions core/src/main/java/org/jruby/RubyClass.java

Large diffs are not rendered by default.

30 changes: 14 additions & 16 deletions core/src/main/java/org/jruby/java/codegen/RealClassGenerator.java
Original file line number Diff line number Diff line change
@@ -192,14 +192,7 @@ public static Class defineOldStyleImplClass(final Ruby ruby, final String name,
implementedNames.add(fullName);

// indices for temp values
int baseIndex = 1;
for (Class paramType : paramTypes) {
if (paramType == double.class || paramType == long.class) {
baseIndex += 2;
} else {
baseIndex += 1;
}
}
final int baseIndex = calcBaseIndex(paramTypes, 1);

SkinnyMethodAdapter mv = new SkinnyMethodAdapter(
cw, ACC_PUBLIC, simpleName, sig(returnType, paramTypes), null, null);
@@ -419,14 +412,7 @@ public static Class defineRealImplClass(final Ruby runtime, final String name,
implementedNames.add(fullName);

// indices for temp values
int baseIndex = 1;
for (Class paramType : paramTypes) {
if (paramType == double.class || paramType == long.class) {
baseIndex += 2;
} else {
baseIndex += 1;
}
}
final int baseIndex = calcBaseIndex(paramTypes, 1);

SkinnyMethodAdapter mv = new SkinnyMethodAdapter(
cw, ACC_PUBLIC, simpleName, sig(returnType, paramTypes), null, null);
@@ -813,4 +799,16 @@ public static void coerceResultAndReturn(SkinnyMethodAdapter mv, Class returnTyp
public static boolean isCacheOk(CacheEntry entry, IRubyObject self) {
return CacheEntry.typeOk(entry, self.getMetaClass()) && entry.method != UndefinedMethod.INSTANCE;
}

public static int calcBaseIndex(final Class[] params, int baseIndex) {
for (Class paramType : params) {
if (paramType == double.class || paramType == long.class) {
baseIndex += 2;
} else {
baseIndex += 1;
}
}
return baseIndex;
}

}
92 changes: 92 additions & 0 deletions spec/java_integration/fixtures/Reflector.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package java_integration.fixtures;

import java.lang.reflect.Method;
import java.lang.reflect.Array;
import java.util.Collection;
import java.util.ArrayList;
import java.util.Arrays;

public abstract class Reflector {

public static Object invoke(final Object obj, final Method method) throws Exception {
if ( method.isVarArgs() ) {
final Class type = method.getParameterTypes()[0].getComponentType();
Object array = Array.newInstance(type, 0);
return method.invoke(obj, array);
}
return method.invoke(obj);
}

public static Object invoke(final Object obj, final Method method, Object arg) throws Exception {
if ( method.isVarArgs() && ! arg.getClass().isArray() ) {
final Class type = method.getParameterTypes()[0].getComponentType();
Object[] array = (Object[]) Array.newInstance(type, 1);
array[0] = arg;
return method.invoke(obj, (Object) array);
}
return method.invoke(obj, arg);
}

public static Object invoke(final Object obj, final Class klass, final String method) throws Exception {
Method instanceMethod = klass.getMethod(method, (Class[]) null);
return instanceMethod.invoke(obj, (Object[]) null);
}

public static Object invoke(final Object obj, final String method) throws Exception {
return invoke(obj, obj.getClass(), method);
}

public static Object invokeMatch(final Object obj, final String method, final Object... args) throws Exception {
final Method[] methods = obj.getClass().getMethods();
ArrayList<Method> matchedMethods = new ArrayList<Method>();
for ( Method m : methods ) {
if ( method.equals(m.getName()) ) {
if ( m.isVarArgs() ) matchedMethods.add(m);
else {
if (m.getParameterTypes().length == args.length) {
matchedMethods.add(m);
}
}
}
}
if ( matchedMethods.isEmpty() ) {
throw new IllegalArgumentException("no methods of name " + method + " matched in " + obj.getClass());
}
if ( matchedMethods.size() > 1 ) {
throw new IllegalArgumentException("multiple methods matched for name " + method + " and arguments " + Arrays.toString(args));
}
return matchedMethods.get(0).invoke(obj, args);
}

public static Method resolveMethod(final Object obj, final String method, final Class... types) throws Exception {
final Method[] methods = obj.getClass().getMethods();
ArrayList<Method> matchedMethods = new ArrayList<Method>();
for ( Method m : methods ) {
if ( method.equals(m.getName()) ) {
if ( Arrays.equals(m.getParameterTypes(), types) ) {
matchedMethods.add(m);
}
}
}
if ( matchedMethods.isEmpty() ) {
throw new IllegalArgumentException("no methods of name " + method + " matched in " + obj.getClass());
}
if ( matchedMethods.size() > 1 ) {
throw new IllegalArgumentException("multiple methods matched for name " + method + " and param types " + Arrays.toString(types));
}
return matchedMethods.get(0);
}

public static Collection<Method> findMethods(final Object obj, final String method) throws Exception {
final Method[] methods = obj.getClass().getMethods();
ArrayList<Method> matchedMethods = new ArrayList<Method>();
for ( Method m : methods ) {
if ( method.equals(m.getName()) ) {
matchedMethods.add(m);
}
}
return matchedMethods;
}

}

102 changes: 85 additions & 17 deletions spec/java_integration/reify/become_java_spec.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
require File.dirname(__FILE__) + "/../spec_helper"
require 'jruby/core_ext'

java_import 'java_integration.fixtures.Reflector'

describe "JRuby class reification" do
jclass = java.lang.Class

class RubyRunnable
include java.lang.Runnable
@@ -26,6 +27,8 @@ class ReifyInterfacesClass2
ReifyInterfacesClass1.become_java!
ReifyInterfacesClass1::ReifyInterfacesClass2.become_java!

jclass = java.lang.Class

expect(ReifyInterfacesClass1.to_java(jclass).name).to eq("rubyobj.ReifyInterfacesClass1")
expect(ReifyInterfacesClass1::ReifyInterfacesClass2.to_java(jclass).name).to eq("rubyobj.ReifyInterfacesClass1.ReifyInterfacesClass2")

@@ -35,32 +38,46 @@ class ReifyInterfacesClass2
ReifyInterfacesClass1::ReifyInterfacesClass2.new
end.not_to raise_error
end
it "creates static methods for Ruby class methods" do
cls = Class.new do

it "creates static methods for reified Ruby class methods" do
klass = Class.new do
class << self
def blah
end
def foo; 'FOO' end
end
def self.bar(arg = 'baz'); "BAR-#{arg.upcase rescue arg.inspect}" end
end

java_class = cls.become_java!

method = java_class.declared_methods.select {|m| m.name == "blah"}[0]
expect(method.name).to eq("blah")
expect(method.return_type).to eq(org.jruby.runtime.builtin.IRubyObject.java_class)
expect(method.parameter_types.length).to eq(0)

java_class = klass.become_java!

method = java_class.declared_methods.find { |m| m.name == 'foo' }
expect(method.return_type).to eql org.jruby.runtime.builtin.IRubyObject.java_class
expect(method.parameter_types.length).to eql 0 # foo()

expect( Reflector.invoke(nil, method) ).to eql 'FOO'

method = java_class.declared_methods.find { |m| m.name == 'bar' }

expect( method.return_type ).to eql org.jruby.runtime.builtin.IRubyObject.java_class
expect( method.parameter_types.length ).to eql 1 # bar(org.jruby.runtime.builtin.IRubyObject[])
expect( method.parameter_types[0] ).to eql org.jruby.runtime.builtin.IRubyObject[].java_class
expect( method.isVarArgs ).to be true

expect( Reflector.invoke(nil, method) ).to eql 'BAR-BAZ'
args = org.jruby.runtime.builtin.IRubyObject[1].new
expect( Reflector.invoke(nil, method, args) ).to eql 'BAR-nil'
args = org.jruby.runtime.builtin.IRubyObject[1].new; args[0] = 'zZz'.to_java(org.jruby.runtime.builtin.IRubyObject)
expect( Reflector.invoke(nil, method, args) ).to eql 'BAR-ZZZ'
end

it "supports fully reifying a deep class hierarchy" do
class BottomOfTheStack ; end
class MiddleOfTheStack < BottomOfTheStack ; end
class TopLeftOfTheStack < MiddleOfTheStack ; end
class TopRightOfTheStack < MiddleOfTheStack ; end

java_class = TopLeftOfTheStack.become_java!
expect(java_class).not_to be_nil

java_class = TopRightOfTheStack.become_java!
expect(java_class).not_to be_nil
end
@@ -113,8 +130,59 @@ class JRUBY5564; end
a_class = JRUBY5564.become_java!(false)

# load the java class from the classloader
cl = java.lang.Thread.current_thread.getContextClassLoader
expect(cl.load_class(a_class.get_name)).to eq(a_class)
klass = java.lang.Thread.current_thread.getContextClassLoader
expect(klass.load_class(a_class.get_name)).to eq(a_class)
end

class ReifiedSample
def hello; 'Sayonara from Ruby' end
java_signature "java.lang.String ahoy()"
def ahoy; 'Ahoy There!' end

def hoja(arg); 'Hoja ' + arg.to_s end
def szia(arg1, arg2, arg3); return "Szia #{arg1} #{arg2} #{arg3}" end

def greet(*args); "Greetings #{args.inspect}!" end

java_signature "java.lang.String ola(java.lang.String[] args)"
def ola(*args); "OLA #{args.join(' ')}" end
end

it "handles argument count for reified class methods" do
j_class = ReifiedSample.become_java!; sample = ReifiedSample.new

methods = Reflector.findMethods(sample, 'hello')
expect( methods.size ).to eql 1; method = methods[0]
expect( method.isVarArgs ).to be false
expect( Reflector.invoke(sample, 'hello') ).to eql 'Sayonara from Ruby'
expect( Reflector.invoke(sample, j_class, 'hello') ).to eql 'Sayonara from Ruby'

methods = Reflector.findMethods(sample, 'ahoy')
expect( methods.size ).to eql 1; method = methods[0]
expect( method.isVarArgs ).to be false
expect( j_class.newInstance.ahoy ).to eql 'Ahoy There!'

methods = Reflector.findMethods(sample, 'hoja')
expect( methods.size ).to eql 1; method = methods[0]
expect( method.isVarArgs ).to be false
expect( j_class.newInstance.hoja('Ferko') ).to eql 'Hoja Ferko'
expect { j_class.newInstance.szia('Ferko', 'Janko') }.to raise_error(ArgumentError)

methods = Reflector.findMethods(sample, 'szia')
expect( methods.size ).to eql 1; method = methods[0]
expect( method.isVarArgs ).to be false
expect( j_class.newInstance.szia('Jozko', 'Janko', 'Ferko') ).to eql 'Szia Jozko Janko Ferko'
expect { j_class.newInstance.szia('Jozko', 'Janko') }.to raise_error(ArgumentError)

method = Reflector.resolveMethod(sample, 'greet', org.jruby.runtime.builtin.IRubyObject[])
expect( method.isVarArgs ).to be true
expect( Reflector.invoke(sample, method) ).to eql 'Greetings []!'
expect( j_class.newInstance.greet ).to eql 'Greetings []!'
expect( j_class.newInstance.greet('Janko') ).to eql 'Greetings ["Janko"]!'

method = Reflector.resolveMethod(sample, 'ola', java.lang.String[])
expect( method.isVarArgs ).to be true
expect( j_class.newInstance.ola('Jozko') ).to eql 'OLA Jozko'
end

describe "java fields" do
10 changes: 6 additions & 4 deletions spec/java_integration/types/coercion_spec.rb
Original file line number Diff line number Diff line change
@@ -728,15 +728,17 @@ def receive_primitive_box(obj)
end
end

it "provides nearest reified class for unreified user classes" do
rubycls = Class.new
expect(rubycls.to_java(cls)).to eq(cls.forName('org.jruby.RubyObject'));
class UserKlass < Object; end

it "reifies user class on-demand" do
expect(klass = UserKlass.to_java(cls)).to be_a java.lang.Class
expect( klass.getSuperclass ).to be cls.forName('org.jruby.RubyObject')
end

it "returns reified class for reified used classes" do
rubycls = Class.new; require 'jruby/core_ext'
rubycls.become_java!
expect(rubycls.to_java(cls)).to eq(JRuby.reference(rubycls).reified_class)
expect(rubycls.to_java(cls)).to be JRuby.reference(rubycls).getReifiedClass
end

it "converts Java proxy classes to their JavaClass/java.lang.Class equivalent" do
17 changes: 16 additions & 1 deletion test/jruby/test_higher_javasupport.rb
Original file line number Diff line number Diff line change
@@ -2,7 +2,6 @@
require 'rbconfig'
require 'test/unit'
require 'test/jruby/test_helper'
require 'jruby/core_ext'

TopLevelConstantExistsProc = Proc.new do
java_import 'java.lang.String'
@@ -28,6 +27,7 @@ def test_java_int_primitive_assignment

class JRUBY5564; end
def test_reified_class_in_jruby_class_loader
require 'jruby/core_ext'
a_class = JRUBY5564.become_java!(false)

# load the java class from the classloader
@@ -39,6 +39,21 @@ def test_reified_class_in_jruby_class_loader
end
end

class Klass1 < Object
def method1(arg); arg end
end
class Klass2 < Klass1
def self.method2; end
end

def test_passing_a_java_class_auto_reifies
assert_nil Klass2.to_java.getReifiedClass
# previously TestHelper.getClassName(Klass2) returned 'org.jruby.RubyObject'
assert_equal 'rubyobj.TestHigherJavasupport.Klass2', TestHelper.getClassName(Klass2)
assert_not_nil Klass2.to_java.getReifiedClass
assert_not_nil Klass1.to_java.getReifiedClass
end

def test_java_passing_class
assert_equal("java.util.ArrayList", TestHelper.getClassName(ArrayList))
end

0 comments on commit 2876388

Please sign in to comment.