Skip to content

Commit

Permalink
Merge pull request jruby#7974 from kares/ji-new-instance
Browse files Browse the repository at this point in the history
[ji] support java_alias with constructor
  • Loading branch information
kares authored Nov 8, 2023
2 parents 8eb9ef6 + 89709aa commit 353808f
Show file tree
Hide file tree
Showing 7 changed files with 187 additions and 83 deletions.
111 changes: 65 additions & 46 deletions core/src/main/java/org/jruby/RubyClass.java
Original file line number Diff line number Diff line change
Expand Up @@ -144,23 +144,32 @@ public void setAllocator(ObjectAllocator allocator) {
* Set a reflective allocator that calls a no-arg constructor on the given
* class.
*
* @param cls The class on which to call the default constructor to allocate
* @param clazz The class on which to call the default constructor to allocate
*/
@SuppressWarnings("unchecked")
public void setClassAllocator(final Class<?> cls) {
public void setClassAllocator(final Class<?> clazz) {
final Constructor<?> constructor;
try {
constructor = clazz.getConstructor();
} catch (NoSuchMethodException nsme) {
throw new RuntimeException(nsme);
}

this.allocator = (runtime, klazz) -> {
try {
RubyBasicObject object = (RubyBasicObject)cls.getConstructor().newInstance();
RubyBasicObject object = (RubyBasicObject) constructor.newInstance();
object.setMetaClass(klazz);
return object;
} catch (InstantiationException | InvocationTargetException ie) {
throw runtime.newTypeError("could not allocate " + cls + " with default constructor:\n" + ie);
} catch (IllegalAccessException | NoSuchMethodException iae) {
throw runtime.newSecurityError("could not allocate " + cls + " due to inaccessible default constructor:\n" + iae);
} catch (InvocationTargetException e) {
throw newTypeError(runtime, "could not allocate " + clazz + " with default constructor:\n" + e.getTargetException(), e);
} catch (InstantiationException e) {
throw newTypeError(runtime, "could not allocate " + clazz + " with default constructor:\n" + e, e);
} catch (IllegalAccessException e) {
throw runtime.newSecurityError("could not allocate " + clazz + " due to inaccessible default constructor:\n" + e);
}
};

this.reifiedClass = (Class<? extends Reified>) cls;
this.reifiedClass = (Class<? extends Reified>) clazz;
}

/**
Expand All @@ -171,25 +180,26 @@ public void setClassAllocator(final Class<?> cls) {
*/
@SuppressWarnings("unchecked")
public void setRubyClassAllocator(final Class<? extends IRubyObject> clazz) {
final Constructor<? extends IRubyObject> constructor;
try {
final Constructor<? extends IRubyObject> constructor = clazz.getConstructor(Ruby.class, RubyClass.class);

this.allocator = (runtime, klazz) -> {
try {
return constructor.newInstance(runtime, klazz);
} catch (InvocationTargetException ite) {
throw runtime.newTypeError("could not allocate " + clazz + " with (Ruby, RubyClass) constructor:\n" + ite);
} catch (InstantiationException ie) {
throw runtime.newTypeError("could not allocate " + clazz + " with (Ruby, RubyClass) constructor:\n" + ie);
} catch (IllegalAccessException iae) {
throw runtime.newSecurityError("could not allocate " + clazz + " due to inaccessible (Ruby, RubyClass) constructor:\n" + iae);
}
};

this.reifiedClass = (Class<? extends Reified>) clazz;
constructor = clazz.getConstructor(Ruby.class, RubyClass.class);
} catch (NoSuchMethodException nsme) {
throw new RuntimeException(nsme);
}

this.allocator = (runtime, klazz) -> {
try {
return constructor.newInstance(runtime, klazz);
} catch (InvocationTargetException e) {
throw newTypeError(runtime, "could not allocate " + clazz + " with (Ruby, RubyClass) constructor:\n" + e.getTargetException(), e);
} catch (InstantiationException e) {
throw newTypeError(runtime, "could not allocate " + clazz + " with (Ruby, RubyClass) constructor:\n" + e, e);
} catch (IllegalAccessException e) {
throw runtime.newSecurityError("could not allocate " + clazz + " due to inaccessible (Ruby, RubyClass) constructor:\n" + e);
}
};

this.reifiedClass = (Class<? extends Reified>) clazz;
}

/**
Expand All @@ -203,23 +213,30 @@ public void setRubyClassAllocator(final Class<? extends IRubyObject> clazz) {
* <p>Note: Used with new concrete extension.</p>
*/
public void setRubyStaticAllocator(final Class<?> clazz) {
final Method method;
try {
final Method method = clazz.getDeclaredMethod("__allocate__", Ruby.class, RubyClass.class);

this.allocator = (runtime, klazz) -> {
try {
return (IRubyObject) method.invoke(null, runtime, klazz);
} catch (InvocationTargetException ite) {
throw runtime.newTypeError("could not allocate " + clazz + " with (Ruby, RubyClass) constructor:\n" + ite);
} catch (IllegalAccessException iae) {
throw runtime.newSecurityError("could not allocate " + clazz + " due to inaccessible (Ruby, RubyClass) constructor:\n" + iae);
}
};

this.reifiedClass = (Class<? extends Reified>) clazz;
method = clazz.getDeclaredMethod("__allocate__", Ruby.class, RubyClass.class);
} catch (NoSuchMethodException nsme) {
throw new RuntimeException(nsme);
}

this.allocator = (runtime, klazz) -> {
try {
return (IRubyObject) method.invoke(null, runtime, klazz);
} catch (InvocationTargetException e) {
throw newTypeError(runtime, "could not allocate " + clazz + " with (Ruby, RubyClass) method:\n" + e.getTargetException(), e);
} catch (IllegalAccessException e) {
throw runtime.newSecurityError("could not allocate " + clazz + " due to inaccessible (Ruby, RubyClass) method:\n" + e);
}
};

this.reifiedClass = (Class<? extends Reified>) clazz;
}

private static RaiseException newTypeError(final Ruby runtime, final String msg, final Exception e) {
RaiseException error = runtime.newTypeError(msg);
error.initCause(e);
return error;
}

@JRubyMethod(name = "allocate")
Expand Down Expand Up @@ -936,22 +953,14 @@ public IRubyObject newInstance(ThreadContext context, IRubyObject[] args, Block
*
*/
@Override
public IRubyObject initialize(ThreadContext context, Block block) {
return initialize19(context, block);
}

public IRubyObject initialize(ThreadContext context, IRubyObject superObject, Block block) {
return initialize19(context, superObject, block);
}

@JRubyMethod(name = "initialize", visibility = PRIVATE)
public IRubyObject initialize19(ThreadContext context, Block block) {
public IRubyObject initialize(ThreadContext context, Block block) {
checkNotInitialized();
return initializeCommon(context, runtime.getObject(), block);
}

@JRubyMethod(name = "initialize", visibility = PRIVATE)
public IRubyObject initialize19(ThreadContext context, IRubyObject superObject, Block block) {
public IRubyObject initialize(ThreadContext context, IRubyObject superObject, Block block) {
checkNotInitialized();
checkInheritable(superObject);
return initializeCommon(context, (RubyClass) superObject, block);
Expand Down Expand Up @@ -2899,6 +2908,16 @@ public VariableAccessorField getObjectGroupAccessorField() {
return variableTableManager.getObjectGroupAccessorField();
}

@Deprecated
public IRubyObject initialize19(ThreadContext context, Block block) {
return initialize(context, block);
}

@Deprecated
public IRubyObject initialize19(ThreadContext context, IRubyObject superObject, Block block) {
return initialize(context, superObject, block);
}

@Deprecated
public IRubyObject invokeFrom(ThreadContext context, CallType callType, IRubyObject caller, IRubyObject self, String name,
Block block) {
Expand Down
6 changes: 2 additions & 4 deletions core/src/main/java/org/jruby/RubyDir.java
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,6 @@ public class RubyDir extends RubyObject implements Closeable {
private boolean isOpen = true;
private Encoding encoding;

private static final Pattern PROTOCOL_PATTERN = Pattern.compile("^(uri|jar|file|classpath):([^:]*:)?//?.*");

public RubyDir(Ruby runtime, RubyClass type) {
super(runtime, type);
}
Expand All @@ -103,13 +101,13 @@ public static RubyClass createDirClass(Ruby runtime) {
return dirClass;
}

private final void checkDir() {
private void checkDir() {
checkDirIgnoreClosed();

if (!isOpen) throw getRuntime().newIOError("closed directory");
}

private final void checkDirIgnoreClosed() {
private void checkDirIgnoreClosed() {
testFrozen("Dir");
// update snapshot (if changed) :
if (snapshot == null || dir.exists() && dir.lastModified() > lastModified) {
Expand Down
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);
}

}
Loading

0 comments on commit 353808f

Please sign in to comment.