Skip to content

Commit

Permalink
[ji] support java_alias-ing a contructor (using "<init>")
Browse files Browse the repository at this point in the history
  • Loading branch information
kares committed Oct 22, 2023
1 parent 3ca8d00 commit 89709aa
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule claz
return call(context, self, clazz, name, arg0, arg1, arg2);
}

private void setAndCacheProxyObject(ThreadContext context, RubyModule clazz, JavaProxy proxy, Object object) {
private static void setAndCacheProxyObject(ThreadContext context, RubyModule clazz, JavaProxy proxy, Object object) {
proxy.setObject(object);

if (Java.OBJECT_PROXY_CACHE || clazz.getCacheProxy()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ void initialize() {
}

if (varArgs != null /* && varargsMethods.size() > 0 */) {
varargsCallables = (T[]) varArgs.toArray(createCallableArray(varArgs.size()));
varargsCallables = varArgs.toArray(createCallableArray(varArgs.size()));
}
// NOTE: tested (4, false); with opt_for_space: false but does not
// seem to give the promised ~10% improvement in map's speed ...
Expand Down Expand Up @@ -341,7 +341,7 @@ private static Object convertVarArgumentsOnly(final Class<?> varArrayType,
}

static JavaProxy castJavaProxy(final IRubyObject self) {
assert self instanceof JavaProxy : "Java methods can only be invoked on Java objects";
assert self instanceof JavaProxy : "Java methods can only be invoked on Java objects; got: " + self;
return (JavaProxy) self;
}

Expand Down
85 changes: 56 additions & 29 deletions core/src/main/java/org/jruby/java/proxies/JavaProxy.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
Expand All @@ -29,10 +31,12 @@
import org.jruby.anno.JRubyMethod;
import org.jruby.common.IRubyWarnings;
import org.jruby.exceptions.RaiseException;
import org.jruby.java.invokers.ConstructorInvoker;
import org.jruby.java.invokers.InstanceFieldGetter;
import org.jruby.java.invokers.InstanceFieldSetter;
import org.jruby.java.invokers.InstanceMethodInvoker;
import org.jruby.java.invokers.MethodInvoker;
import org.jruby.java.invokers.RubyToJavaInvoker;
import org.jruby.java.invokers.StaticFieldGetter;
import org.jruby.java.invokers.StaticFieldSetter;
import org.jruby.java.invokers.StaticMethodInvoker;
Expand Down Expand Up @@ -655,7 +659,7 @@ public static IRubyObject java_send(ThreadContext context, IRubyObject recv, IRu

checkArgSizeMismatch(runtime, 1, argTypesAry);

Class argTypeClass = (Class) argTypesAry.eltInternal(0).toJava(Class.class);
Class argTypeClass = argTypesAry.eltInternal(0).toJava(Class.class);

JavaMethod method = new JavaMethod(runtime, getMethodFromClass(context, recv, name, argTypeClass));
return method.invokeStaticDirect(context, arg0.toJava(argTypeClass));
Expand Down Expand Up @@ -696,29 +700,37 @@ public static IRubyObject java_alias(ThreadContext context, IRubyObject clazz, I
}

@JRubyMethod(meta = true, visibility = Visibility.PRIVATE)
public static IRubyObject java_alias(ThreadContext context, IRubyObject clazz, IRubyObject newName, IRubyObject rubyName, IRubyObject argTypes) {
public static IRubyObject java_alias(ThreadContext context, IRubyObject klass, IRubyObject newName, IRubyObject rubyName, IRubyObject argTypes) {
final Ruby runtime = context.runtime;
if ( ! ( clazz instanceof RubyModule ) ) {
throw runtime.newTypeError(clazz, runtime.getModule());
if ( ! ( klass instanceof RubyModule ) ) {
throw runtime.newTypeError(klass, runtime.getModule());
}
final RubyModule proxyClass = (RubyModule) clazz;
final RubyModule proxyClass = (RubyModule) klass;

String name = rubyName.asJavaString();
String newNameStr = newName.asJavaString();
RubyArray argTypesAry = argTypes.convertToArray();
Class<?>[] argTypesClasses = (Class[]) argTypesAry.toArray(new Class[argTypesAry.size()]);

final Method method = getMethodFromClass(context, clazz, name, argTypesClasses);
final MethodInvoker invoker;

if ( Modifier.isStatic( method.getModifiers() ) ) {
invoker = new StaticMethodInvoker(proxyClass.getMetaClass(), () -> arrayOf(method), newNameStr);
// add alias to meta
proxyClass.getSingletonClass().addMethod(newNameStr, invoker);
}
else {
invoker = new InstanceMethodInvoker(proxyClass, () -> arrayOf(method), newNameStr);
proxyClass.addMethod(newNameStr, invoker);
Class<?>[] argTypesClasses = (Class[]) argTypes.convertToArray().toArray(ClassUtils.EMPTY_CLASS_ARRAY);

final Class<?> clazz = JavaUtil.getJavaClass(proxyClass);
final RubyToJavaInvoker invoker;
switch (name) {
case "<init>" :
final Constructor constructor = getConstructorFromClass(context, clazz, name, argTypesClasses);
invoker = new ConstructorInvoker(proxyClass, () -> arrayOf(constructor), newNameStr);
proxyClass.addMethod(newNameStr, invoker);

break;

default :
final Method method = getMethodFromClass(context, clazz, name, argTypesClasses);
if ( Modifier.isStatic( method.getModifiers() ) ) {
invoker = new StaticMethodInvoker(proxyClass.getMetaClass(), () -> arrayOf(method), newNameStr);
proxyClass.getSingletonClass().addMethod(newNameStr, invoker); // add alias to meta
}
else {
invoker = new InstanceMethodInvoker(proxyClass, () -> arrayOf(method), newNameStr);
proxyClass.addMethod(newNameStr, invoker);
}
}

return context.nil;
Expand All @@ -731,7 +743,7 @@ private static AbstractRubyMethod getRubyMethod(ThreadContext context, IRubyObje
}
final RubyModule proxyClass = (RubyModule) clazz;

final Method method = getMethodFromClass(context, clazz, name, argTypesClasses);
final Method method = getMethodFromClass(context, JavaUtil.getJavaClass(proxyClass), name, argTypesClasses);
final String prettyName = name + CodegenUtils.prettyParams(argTypesClasses);

if ( Modifier.isStatic( method.getModifiers() ) ) {
Expand All @@ -743,21 +755,36 @@ private static AbstractRubyMethod getRubyMethod(ThreadContext context, IRubyObje
return RubyUnboundMethod.newUnboundMethod(proxyClass, prettyName, proxyClass, name, new CacheEntry(invoker, proxyClass, proxyClass.getGeneration()));
}

private static Method getMethodFromClass(final ThreadContext context, final IRubyObject proxyClass,
final String name, final Class... argTypes) {
final Class<?> clazz = JavaUtil.getJavaClass((RubyModule) proxyClass);
private static Method getMethodFromClass(final ThreadContext context, final IRubyObject klass, final String name, final Class... argTypes) {
return getMethodFromClass(context, JavaUtil.getJavaClass((RubyModule) klass), name, argTypes);
}

private static Method getMethodFromClass(final ThreadContext context, final Class<?> clazz, final String name, final Class... argTypes) {
try {
return clazz.getMethod(name, argTypes);
} catch (NoSuchMethodException e) {
throw newNameError(context.runtime, "Java method not found: " + format(clazz, name, argTypes), name, e);
}
catch (NoSuchMethodException e) {
String prettyName = name + CodegenUtils.prettyParams(argTypes);
String errorName = clazz.getName() + '.' + prettyName;
RaiseException ex = context.runtime.newNameError("Java method not found: " + errorName, name);
ex.initCause(e);
throw ex;
}

private static Constructor getConstructorFromClass(final ThreadContext context, final Class<?> clazz, final String name, final Class... argTypes) {
try {
return clazz.getConstructor(argTypes);
} catch (NoSuchMethodException e) {
throw newNameError(context.runtime, "Java initializer not found: " + format(clazz, name, argTypes), name, e);
}
}

private static String format(final Class<?> clazz, final String name, final Class... argTypes) {
return clazz.getName() + '.' + name + CodegenUtils.prettyParams(argTypes); // e.g. SomeClass.someMethod(java.lang.String)
}

private static RaiseException newNameError(final Ruby runtime, final String msg, final String name, final ReflectiveOperationException cause) {
RaiseException ex = runtime.newNameError(msg, name);
ex.initCause(cause);
return ex;
}

}

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

public class AnArrayList extends java.util.ArrayList {
public static final java.util.Vector CREATED_INSTANCES = new java.util.Vector();

public AnArrayList() {
super();
CREATED_INSTANCES.add(this);
}

public AnArrayList(int c) {
super(c);
for (int i=0; i<c; i++) this.add(null);
CREATED_INSTANCES.add(this);
}

}
45 changes: 44 additions & 1 deletion spec/java_integration/methods/java_alias_spec.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
require 'java'

describe "A Java object's java_send method" do
describe "A Java object's java_alias method" do
before :all do
class java::util::ArrayList
java_alias :add_int_object, :add, [Java::int, java.lang.Object]
Expand Down Expand Up @@ -46,6 +46,49 @@ class java::util::ArrayList
end.to raise_error(ArgumentError)
end

context 'constructor' do

before(:all) do
Java::java_integration.fixtures::AnArrayList.class_eval do
java_alias :init, :'<init>', [Java::int] # AnArrayList(int)

def initialize(num)
init(num) if num
@@last_instance = self
end

@@last_instance = nil
def self.last_instance; @@last_instance end
end
end

before(:each) { klass::CREATED_INSTANCES.clear }

let(:klass) { Java::java_integration.fixtures::AnArrayList }

it 'is supported' do
a_list = klass.new(3)
expect( a_list ).to be klass::last_instance
expect( klass::CREATED_INSTANCES.size ).to be 1
expect( klass::CREATED_INSTANCES[0].size ).to eql(3)

a_list = klass.allocate
a_list.init(10) # useful to bypass :initialize
expect( klass::CREATED_INSTANCES.size ).to be 2
expect( a_list ).to_not be klass::last_instance
end

it 'raises on invalid arguments' do
expect { klass.new(:foo) }.to raise_error(TypeError)
end

it 'raises on unexpected arguments' do
a_list = klass.new(nil)
expect { a_list.init(1, 2) }.to raise_error(ArgumentError)
end

end

context 'interface' do

before(:all) do
Expand Down

0 comments on commit 89709aa

Please sign in to comment.