package org.jruby.internal.runtime.methods;
import java.lang.invoke.MethodHandle;
import java.util.concurrent.Callable;
import org.jruby.RubyModule;
import org.jruby.runtime.Arity;
import org.jruby.runtime.Block;
import org.jruby.runtime.Helpers;
import org.jruby.runtime.Signature;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.Visibility;
import org.jruby.runtime.builtin.IRubyObject;
import static org.jruby.util.StringSupport.EMPTY_STRING_ARRAY;
import static org.jruby.util.StringSupport.split;
public class HandleMethod extends DynamicMethod implements MethodArgs2, Cloneable {
private Callable<MethodHandle> maker0;
private Callable<MethodHandle> maker1;
private Callable<MethodHandle> maker2;
private Callable<MethodHandle> maker3;
private Callable<MethodHandle> maker4;
private MethodHandle target0;
private MethodHandle target1;
private MethodHandle target2;
private MethodHandle target3;
private MethodHandle target4;
private volatile boolean initialized0, initialized1, initialized2, initialized3, initialized4;
private final int min;
private final int max;
private final String parameterDesc;
private final Signature signature;
private final boolean builtin;
private final boolean notImplemented;
public HandleMethod(
RubyModule implementationClass,
Visibility visibility,
String name,
long encodedSignature,
boolean builtin,
boolean notImplemented,
String parameterDesc,
final int min,
final int max,
final Callable<MethodHandle> maker0,
final Callable<MethodHandle> maker1,
final Callable<MethodHandle> maker2,
final Callable<MethodHandle> maker3,
final Callable<MethodHandle> maker4) {
super(implementationClass, visibility, name);
this.signature = Signature.decode(encodedSignature);
this.builtin = builtin;
this.notImplemented = notImplemented;
this.parameterDesc = parameterDesc;
this.min = min;
this.max = max;
this.maker0 = maker0;
this.maker1 = maker1;
this.maker2 = maker2;
this.maker3 = maker3;
this.maker4 = maker4;
}
@Override
public Arity getArity() {
return signature.arity();
}
@Override
public boolean isBuiltin() {
return builtin;
}
@Override
public boolean isNotImplemented() {
return notImplemented;
}
@Override
public boolean isNative() {
return true;
}
private MethodHandle ensureTarget0() {
if (!initialized0) {
this.target0 = safeCall(maker0);
initialized0 = true;
maker0 = null;
}
return this.target0;
}
private MethodHandle ensureTarget1() {
if (!initialized1) {
this.target1 = safeCall(maker1);
initialized1 = true;
maker1 = null;
}
return this.target1;
}
private MethodHandle ensureTarget2() {
if (!initialized2) {
this.target2 = safeCall(maker2);
initialized2 = true;
maker2 = null;
}
return this.target2;
}
private MethodHandle ensureTarget3() {
if (!initialized3) {
this.target3 = safeCall(maker3);
initialized3 = true;
maker3 = null;
}
return this.target3;
}
private MethodHandle ensureTarget4() {
if (!initialized4) {
this.target4 = safeCall(maker4);
initialized4 = true;
maker4 = null;
}
return this.target4;
}
private static MethodHandle safeCall(Callable<MethodHandle> maker) {
try {
if (maker == null) return null;
return maker.call();
} catch (Exception e) {
Helpers.throwException(e);
return null;
}
}
@Override
public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject[] args, Block block) {
try {
MethodHandle target4 = ensureTarget4();
if (target4 != null) {
Arity.checkArgumentCount(context, args.length, min, max);
return (IRubyObject) target4.invokeExact(context, self, clazz, name, args, block);
} else {
int arity = Arity.checkArgumentCount(context, args.length, min, max);
switch (args.length) {
case 0: return (IRubyObject) ensureTarget0().invokeExact(context, self, clazz, name, block);
case 1: return (IRubyObject) ensureTarget1().invokeExact(context, self, clazz, name, args[0], block);
case 2: return (IRubyObject) ensureTarget2().invokeExact(context, self, clazz, name, args[0], args[1], block);
case 3: return (IRubyObject) ensureTarget3().invokeExact(context, self, clazz, name, args[0], args[1], args[2], block);
default:
throw new RuntimeException("invalid arity for call: " + arity);
}
}
} catch (Throwable t) {
Helpers.throwException(t);
return null;
}
}
@Override
public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, Block block) {
MethodHandle target0 = ensureTarget0();
if (target0 == null) {
return call(context, self, clazz, name, IRubyObject.NULL_ARRAY, block);
}
try {
return (IRubyObject) target0.invokeExact(context, self, clazz, name, block);
} catch (Throwable t) {
Helpers.throwException(t);
return null;
}
}
@Override
public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject arg0, Block block) {
MethodHandle target1 = ensureTarget1();
if (target1 == null) {
return call(context, self, clazz, name, new IRubyObject[]{arg0}, block);
}
try {
return (IRubyObject) target1.invokeExact(context, self, clazz, name, arg0, block);
} catch (Throwable t) {
Helpers.throwException(t);
return null;
}
}
@Override
public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject arg0, IRubyObject arg1, Block block) {
MethodHandle target2 = ensureTarget2();
if (target2 == null) {
return call(context, self, clazz, name, new IRubyObject[]{arg0, arg1}, block);
}
try {
return (IRubyObject) target2.invokeExact(context, self, clazz, name, arg0, arg1, block);
} catch (Throwable t) {
Helpers.throwException(t);
return null;
}
}
@Override
public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject arg0, IRubyObject arg1, IRubyObject arg2, Block block) {
MethodHandle target3 = ensureTarget3();
if (target3 == null) {
return call(context, self, clazz, name, new IRubyObject[]{arg0, arg1, arg2}, block);
}
try {
return (IRubyObject) target3.invokeExact(context, self, clazz, name, arg0, arg1, arg2, block);
} catch (Throwable t) {
Helpers.throwException(t);
return null;
}
}
@Override
public DynamicMethod dup() {
return new HandleMethod(implementationClass, getVisibility(), name, signature.encode(), builtin, notImplemented, parameterDesc, min, max, maker0, maker1, maker2, maker3, maker4);
}
@Override
public String[] getParameterList() {
if (parameterDesc != null && parameterDesc.length() > 0) {
return split(parameterDesc, ';').toArray(EMPTY_STRING_ARRAY);
}
return EMPTY_STRING_ARRAY;
}
public MethodHandle getHandle(int arity) {
switch (arity) {
case -1: return ensureTarget4();
case 0: return ensureTarget0();
case 1: return ensureTarget1();
case 2: return ensureTarget2();
case 3: return ensureTarget3();
default: return null;
}
}
}