package org.jruby.java.codegen;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.jruby.Ruby;
import org.jruby.RubyBasicObject;
import org.jruby.RubyClass;
import org.jruby.RubyModule;
import org.jruby.ast.executable.RuntimeCache;
import org.jruby.compiler.impl.SkinnyMethodAdapter;
import org.jruby.compiler.util.BasicObjectStubGenerator;
import org.jruby.internal.runtime.methods.DynamicMethod;
import org.jruby.javasupport.JavaUtil;
import org.jruby.runtime.Block;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.util.ASM;
import org.jruby.util.ClassDefiningClassLoader;
import org.jruby.util.ClassDefiningJRubyClassLoader;
import org.jruby.util.Loader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Label;
import org.objectweb.asm.Type;
import static org.jruby.RubyInstanceConfig.JAVA_VERSION;
import static org.jruby.util.CodegenUtils.ci;
import static org.jruby.util.CodegenUtils.getBoxType;
import static org.jruby.util.CodegenUtils.p;
import static org.jruby.util.CodegenUtils.params;
import static org.jruby.util.CodegenUtils.prettyParams;
import static org.jruby.util.CodegenUtils.sig;
import static org.objectweb.asm.Opcodes.ACC_FINAL;
import static org.objectweb.asm.Opcodes.ACC_PRIVATE;
import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
import static org.objectweb.asm.Opcodes.ACC_STATIC;
import static org.objectweb.asm.Opcodes.ACC_SUPER;
public abstract class RealClassGenerator {
private static final boolean DEBUG = false;
private static final int V_BC = JAVA_VERSION;
static Map<String, List<Method>> buildSimpleToAllMap(Class[] interfaces, String[] superTypeNames, RubyClass implClass)
throws SecurityException {
final LinkedHashMap<String, List<Method>> simpleToAll = new LinkedHashMap<>();
for (int i = 0; i < interfaces.length; i++) {
superTypeNames[i] = p(interfaces[i]);
for ( Method method : interfaces[i].getMethods() ) {
final String name = method.getName();
if ( Modifier.isStatic(method.getModifiers()) ) continue;
if ( implClass != null ) {
if ( ! Modifier.isAbstract(method.getModifiers()) && ! implClass.getMethods().containsKey(name) ) {
continue;
}
}
List<Method> methods = simpleToAll.get(name);
if (methods == null) {
simpleToAll.put(name, methods = new ArrayList<Method>(6));
if ( name.startsWith("is") && name.length() > 2 ) {
final String getName = "get" + name.substring(2);
List<Method> getMethods = simpleToAll.get(getName);
if ( getMethods != null ) {
simpleToAll.remove(getName);
simpleToAll.put(getName, getMethods);
}
}
}
methods.add(method);
}
}
return simpleToAll;
}
public static Class createOldStyleImplClass(Class[] superTypes, RubyClass rubyClass, Ruby ruby, String name, ClassDefiningClassLoader classLoader) {
String[] superTypeNames = new String[superTypes.length];
Map<String, List<Method>> simpleToAll = buildSimpleToAllMap(superTypes, superTypeNames, rubyClass);
Class newClass = defineOldStyleImplClass(ruby, name, superTypeNames, simpleToAll, classLoader);
return newClass;
}
public static Class createRealImplClass(Class superClass, Class[] interfaces, RubyClass rubyClass, Ruby ruby, String name) {
String[] superTypeNames = new String[interfaces.length];
Map<String, List<Method>> simpleToAll = buildSimpleToAllMap(interfaces, superTypeNames, rubyClass);
Class newClass = defineRealImplClass(ruby, name, superClass, superTypeNames, simpleToAll);
for (Class ifc : interfaces) {
assert ifc.isAssignableFrom(newClass);
}
return newClass;
}
public static Class defineOldStyleImplClass(final Ruby ruby, final String name,
final String[] superTypeNames, final Map<String, List<Method>> simpleToAll,
final ClassDefiningClassLoader loader) {
Class newClass;
synchronized (loader) {
try {
newClass = loader.loadClass(name);
}
catch (ClassNotFoundException ex) {
ClassWriter cw = ASM.newClassWriter(loader.asClassLoader());
String pathName = name.replace('.', '/');
cw.visit(V_BC, ACC_PUBLIC | ACC_SUPER, pathName, null, p(Object.class), superTypeNames);
cw.visitSource(pathName + ".gen", null);
cw.visitField(ACC_STATIC | ACC_FINAL | ACC_PRIVATE, "$runtimeCache", ci(RuntimeCache.class), null, null).visitEnd();
cw.visitField(ACC_PRIVATE | ACC_FINAL, "$self", ci(IRubyObject.class), null, null).visitEnd();
SkinnyMethodAdapter clinitMethod = new SkinnyMethodAdapter(cw, ACC_PUBLIC | ACC_STATIC, "<clinit>", sig(void.class), null, null);
SkinnyMethodAdapter initMethod = new SkinnyMethodAdapter(cw, ACC_PUBLIC, "<init>", sig(void.class, IRubyObject.class), null, null);
initMethod.aload(0);
initMethod.invokespecial(p(Object.class), "<init>", sig(void.class));
initMethod.aload(0);
initMethod.aload(1);
initMethod.putfield(pathName, "$self", ci(IRubyObject.class));
initMethod.voidreturn();
initMethod.end();
int cacheSize = 0;
final HashSet<String> implementedNames = new HashSet<String>();
for (Map.Entry<String, List<Method>> entry : simpleToAll.entrySet()) {
final String simpleName = entry.getKey();
final List<Method> methods = entry.getValue();
Set<String> nameSet = JavaUtil.getRubyNamesForJavaName(simpleName, methods);
implementedNames.clear();
for (int i = 0; i < methods.size(); i++) {
final Method method = methods.get(i);
final Class[] paramTypes = method.getParameterTypes();
final Class returnType = method.getReturnType();
String fullName = simpleName + prettyParams(paramTypes);
if (implementedNames.contains(fullName)) continue;
implementedNames.add(fullName);
final int baseIndex = calcBaseIndex(paramTypes, 1);
SkinnyMethodAdapter mv = new SkinnyMethodAdapter(
cw, ACC_PUBLIC, simpleName, sig(returnType, paramTypes), null, null);
mv.start();
mv.line(1);
switch ( simpleName ) {
case "equals" :
if ( defineDefaultEquals(2, mv, paramTypes, returnType) ) ;
else defineOldStyleBody(mv, pathName, simpleName, paramTypes, returnType, baseIndex, cacheSize++, nameSet);
break;
case "hashCode" :
if ( defineDefaultHashCode(3, mv, paramTypes, returnType) ) ;
else defineOldStyleBody(mv, pathName, simpleName, paramTypes, returnType, baseIndex, cacheSize++, nameSet);
break;
case "toString" :
if ( defineDefaultToString(4, mv, paramTypes, returnType) ) ;
else defineOldStyleBody(mv, pathName, simpleName, paramTypes, returnType, baseIndex, cacheSize++, nameSet);
break;
case "__ruby_object" :
if ( paramTypes.length == 0 && returnType == IRubyObject.class ) {
mv.aload(0);
mv.getfield(pathName, "$self", ci(IRubyObject.class));
mv.areturn();
break;
}
default :
defineOldStyleBody(mv, pathName, simpleName, paramTypes, returnType, baseIndex, cacheSize++, nameSet);
break;
}
mv.end();
}
}
clinitMethod.newobj(p(RuntimeCache.class));
clinitMethod.dup();
clinitMethod.invokespecial(p(RuntimeCache.class), "<init>", sig(void.class));
clinitMethod.dup();
clinitMethod.ldc(cacheSize);
clinitMethod.invokevirtual(p(RuntimeCache.class), "initMethodCache", sig(void.class, int.class));
clinitMethod.putstatic(pathName, "$runtimeCache", ci(RuntimeCache.class));
clinitMethod.voidreturn();
clinitMethod.end();
cw.visitEnd();
final byte[] bytecode = cw.toByteArray();
newClass = loader.defineClass(name, bytecode);
if ( DEBUG ) writeClassFile(name, bytecode);
}
}
return newClass;
}
private static void defineOldStyleBody(SkinnyMethodAdapter mv, final String pathName,
final String simpleName, final Class[] paramTypes, final Class returnType,
final int baseIndex, final int cacheIndex, final Set<String> nameSet) {
final int selfIndex = baseIndex;
final int rubyIndex = selfIndex + 1;
mv.line(5);
mv.aload(0);
mv.getfield(pathName, "$self", ci(IRubyObject.class));
mv.astore(selfIndex);
mv.aload(selfIndex);
mv.invokeinterface(p(IRubyObject.class), "getRuntime", sig(Ruby.class));
mv.astore(rubyIndex);
mv.getstatic(pathName, "$runtimeCache", ci(RuntimeCache.class));
mv.aload(selfIndex);
mv.ldc(cacheIndex);
for (String eachName : nameSet) {
mv.ldc(eachName);
}
mv.invokevirtual(p(RuntimeCache.class), "searchWithCache",
sig(DynamicMethod.class, params(IRubyObject.class, int.class, String.class, nameSet.size())));
mv.aload(rubyIndex);
mv.invokevirtual(p(Ruby.class), "getCurrentContext", sig(ThreadContext.class));
mv.aloadMany(selfIndex, selfIndex);
mv.invokeinterface(p(IRubyObject.class), "getMetaClass", sig(RubyClass.class));
mv.ldc(simpleName);
coerceArgumentsToRuby(mv, paramTypes, rubyIndex);
mv.getstatic(p(Block.class), "NULL_BLOCK", ci(Block.class));
mv.line(13);
mv.invokevirtual(p(DynamicMethod.class), "call", sig(IRubyObject.class, ThreadContext.class, IRubyObject.class, RubyModule.class, String.class, IRubyObject[].class, Block.class));
coerceResultAndReturn(mv, returnType);
}
public static Class defineRealImplClass(final Ruby runtime, final String name,
final Class superClass, final String[] superTypeNames,
final Map<String, List<Method>> simpleToAll) {
final ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
final String pathName = name.replace('.', '/');
boolean isRubyHierarchy = RubyBasicObject.class.isAssignableFrom(superClass);
if (isRubyHierarchy) {
cw.visit(V_BC, ACC_PUBLIC | ACC_SUPER, pathName, null, p(superClass), superTypeNames);
}
else {
String[] plusIRubyObject = new String[superTypeNames.length + 1];
plusIRubyObject[0] = p(IRubyObject.class);
System.arraycopy(superTypeNames, 0, plusIRubyObject, 1, superTypeNames.length);
cw.visit(V_BC, ACC_PUBLIC | ACC_SUPER, pathName, null, p(superClass), plusIRubyObject);
}
cw.visitSource(pathName + ".gen", null);
cw.visitField(ACC_STATIC | ACC_FINAL | ACC_PRIVATE, "$runtimeCache", ci(RuntimeCache.class), null, null).visitEnd();
SkinnyMethodAdapter clinitMethod = new SkinnyMethodAdapter(cw, ACC_PUBLIC | ACC_STATIC, "<clinit>", sig(void.class), null, null);
SkinnyMethodAdapter initMethod = new SkinnyMethodAdapter(cw, ACC_PUBLIC, "<init>", sig(void.class, Ruby.class, RubyClass.class), null, null);
if (isRubyHierarchy) {
initMethod.aloadMany(0, 1, 2);
initMethod.invokespecial(p(superClass), "<init>", sig(void.class, Ruby.class, RubyClass.class));
}
else {
cw.visitField(ACC_FINAL | ACC_PRIVATE, "$ruby", ci(Ruby.class), null, null).visitEnd();
cw.visitField(ACC_FINAL | ACC_PRIVATE, "$rubyClass", ci(RubyClass.class), null, null).visitEnd();
initMethod.aloadMany(0, 1);
initMethod.putfield(pathName, "$ruby", ci(Ruby.class));
initMethod.aloadMany(0, 2);
initMethod.putfield(pathName, "$rubyClass", ci(RubyClass.class));
initMethod.aload(0);
initMethod.invokespecial(p(superClass), "<init>", sig(void.class));
}
initMethod.voidreturn();
initMethod.end();
if (isRubyHierarchy) {
SkinnyMethodAdapter toJavaMethod = new SkinnyMethodAdapter(cw, ACC_PUBLIC, "toJava", sig(Object.class, Class.class), null, null);
toJavaMethod.aload(0);
toJavaMethod.areturn();
toJavaMethod.end();
}
else {
BasicObjectStubGenerator.addBasicObjectStubsToClass(cw);
SkinnyMethodAdapter getRuntimeMethod = new SkinnyMethodAdapter(cw, ACC_PUBLIC, "getRuntime", sig(Ruby.class), null, null);
getRuntimeMethod.aload(0);
getRuntimeMethod.getfield(pathName, "$ruby", ci(Ruby.class));
getRuntimeMethod.areturn();
getRuntimeMethod.end();
SkinnyMethodAdapter getMetaClassMethod = new SkinnyMethodAdapter(cw, ACC_PUBLIC, "getMetaClass", sig(RubyClass.class), null, null);
getMetaClassMethod.aload(0);
getMetaClassMethod.getfield(pathName, "$rubyClass", ci(RubyClass.class));
getMetaClassMethod.areturn();
getMetaClassMethod.end();
}
int cacheSize = 0;
final HashSet<String> implementedNames = new HashSet<String>();
for (Map.Entry<String, List<Method>> entry : simpleToAll.entrySet()) {
final String simpleName = entry.getKey();
final List<Method> methods = entry.getValue();
Set<String> nameSet = JavaUtil.getRubyNamesForJavaName(simpleName, methods);
implementedNames.clear();
for (int i = 0; i < methods.size(); i++) {
final Method method = methods.get(i);
final Class[] paramTypes = method.getParameterTypes();
final Class returnType = method.getReturnType();
String fullName = simpleName + prettyParams(paramTypes);
if (implementedNames.contains(fullName)) continue;
implementedNames.add(fullName);
final int baseIndex = calcBaseIndex(paramTypes, 1);
SkinnyMethodAdapter mv = new SkinnyMethodAdapter(
cw, ACC_PUBLIC, simpleName, sig(returnType, paramTypes), null, null);
mv.start();
mv.line(1);
switch ( simpleName ) {
case "equals" :
if ( paramTypes.length == 1 && paramTypes[0] == Object.class && returnType == Boolean.TYPE ) {
defineRealEqualsWithFallback(mv, pathName, simpleName, paramTypes, returnType, baseIndex, cacheSize++, nameSet);
}
else defineRealBody(mv, pathName, simpleName, paramTypes, returnType, baseIndex, cacheSize++, nameSet);
break;
case "hashCode" :
if ( paramTypes.length == 0 && returnType == Integer.TYPE ) {
defineRealHashCodeWithFallback(mv, pathName, simpleName, paramTypes, returnType, baseIndex, cacheSize++, nameSet);
}
else defineRealBody(mv, pathName, simpleName, paramTypes, returnType, baseIndex, cacheSize++, nameSet);
break;
case "toString" :
if ( paramTypes.length == 0 && returnType == String.class ) {
defineRealToStringWithFallback(mv, pathName, simpleName, paramTypes, returnType, baseIndex, cacheSize++, nameSet);
}
else defineRealBody(mv, pathName, simpleName, paramTypes, returnType, baseIndex, cacheSize++, nameSet);
break;
default :
defineRealBody(mv, pathName, simpleName, paramTypes, returnType, baseIndex, cacheSize++, nameSet);
break;
}
mv.end();
}
}
clinitMethod.newobj(p(RuntimeCache.class));
clinitMethod.dup();
clinitMethod.invokespecial(p(RuntimeCache.class), "<init>", sig(void.class));
clinitMethod.dup();
clinitMethod.ldc(cacheSize);
clinitMethod.invokevirtual(p(RuntimeCache.class), "initMethodCache", sig(void.class, int.class));
clinitMethod.putstatic(pathName, "$runtimeCache", ci(RuntimeCache.class));
clinitMethod.voidreturn();
clinitMethod.end();
cw.visitEnd();
Class newClass = null;
for(Loader loader : runtime.getInstanceConfig().getExtraLoaders()) {
try {
newClass = loader.loadClass(name);
break;
}
catch(ClassNotFoundException ignored) {
}
}
final ClassDefiningJRubyClassLoader loader;
if (superClass.getClassLoader() instanceof ClassDefiningJRubyClassLoader) {
loader = new ClassDefiningJRubyClassLoader(superClass.getClassLoader());
} else {
loader = new ClassDefiningJRubyClassLoader(runtime.getJRubyClassLoader());
}
if (newClass == null) {
try {
newClass = loader.loadClass(name);
}
catch (ClassNotFoundException ignored) {
}
}
if (newClass == null) {
final byte[] bytecode = cw.toByteArray();
MultiClassLoader multiClassLoader = new MultiClassLoader(superClass.getClassLoader());
for(Loader cLoader : runtime.getInstanceConfig().getExtraLoaders()) {
multiClassLoader.addClassLoader(cLoader.getClassLoader());
}
try {
newClass = new ClassDefiningJRubyClassLoader(multiClassLoader).defineClass(name, bytecode);
}
catch(Error ignored) {
}
if (newClass == null) {
newClass = loader.defineClass(name, bytecode);
}
if ( DEBUG ) writeClassFile(name, bytecode);
}
return newClass;
}
private static void defineRealBody(SkinnyMethodAdapter mv, final String pathName,
final String simpleName, final Class[] paramTypes, final Class returnType,
final int baseIndex, final int cacheIndex, final Set<String> nameSet) {
final int rubyIndex = baseIndex + 1;
mv.line(5);
mv.aload(0);
mv.invokeinterface(p(IRubyObject.class), "getRuntime", sig(Ruby.class));
mv.astore(rubyIndex);
mv.getstatic(pathName, "$runtimeCache", ci(RuntimeCache.class));
mv.aload(0);
mv.ldc(cacheIndex);
for (String eachName : nameSet) {
mv.ldc(eachName);
}
mv.invokevirtual(p(RuntimeCache.class), "searchWithCache",
sig(DynamicMethod.class, params(IRubyObject.class, int.class, String.class, nameSet.size())));
mv.aload(rubyIndex);
mv.invokevirtual(p(Ruby.class), "getCurrentContext", sig(ThreadContext.class));
mv.aloadMany(0, 0);
mv.invokeinterface(p(IRubyObject.class), "getMetaClass", sig(RubyClass.class));
mv.ldc(simpleName);
coerceArgumentsToRuby(mv, paramTypes, rubyIndex);
mv.getstatic(p(Block.class), "NULL_BLOCK", ci(Block.class));
mv.line(13);
mv.invokevirtual(p(DynamicMethod.class), "call", sig(IRubyObject.class, ThreadContext.class, IRubyObject.class, RubyModule.class, String.class, IRubyObject[].class, Block.class));
coerceResultAndReturn(mv, returnType);
}
private static void defineRealBodyWithFallback(SkinnyMethodAdapter mv, final String pathName,
final String simpleName, final Class[] paramTypes, final Class returnType,
final int baseIndex, final int cacheIndex, final Set<String> nameSet) {
final int rubyIndex = baseIndex + 1;
mv.aload(0);
mv.invokeinterface(p(IRubyObject.class), "getRuntime", sig(Ruby.class));
mv.astore(rubyIndex);
mv.getstatic(pathName, "$runtimeCache", ci(RuntimeCache.class));
mv.aload(0);
mv.ldc(cacheIndex);
for (String eachName : nameSet) {
mv.ldc(eachName);
}
mv.invokevirtual(p(RuntimeCache.class), "searchWithCacheNoMethodMissing",
sig(DynamicMethod.class, params(IRubyObject.class, int.class, String.class, nameSet.size())));
final int methodIndex = baseIndex + 2;
mv.astore(methodIndex);
Label fallback = new Label();
mv.aload(methodIndex);
mv.ifnull(fallback);
mv.aload(methodIndex);
mv.aload(rubyIndex);
mv.invokevirtual(p(Ruby.class), "getCurrentContext", sig(ThreadContext.class));
mv.aloadMany(0, 0);
mv.invokeinterface(p(IRubyObject.class), "getMetaClass", sig(RubyClass.class));
mv.ldc(simpleName);
coerceArgumentsToRuby(mv, paramTypes, rubyIndex);
mv.getstatic(p(Block.class), "NULL_BLOCK", ci(Block.class));
mv.invokevirtual(p(DynamicMethod.class), "call", sig(IRubyObject.class, ThreadContext.class, IRubyObject.class, RubyModule.class, String.class, IRubyObject[].class, Block.class));
coerceResultAndReturn(mv, returnType);
mv.label(fallback);
switch ( simpleName ) {
case "equals" : objectEquals(-1, mv); break;
case "hashCode" : objectHashCode(-1, mv); break;
case "toString" : objectToString(-1, mv); break;
default : throw new UnsupportedOperationException(simpleName);
}
}
private static void defineRealEqualsWithFallback(SkinnyMethodAdapter mv, final String pathName,
final String simpleName, final Class[] paramTypes, final Class returnType,
final int baseIndex, final int cacheIndex, final Set<String> nameSet) {
defineRealBodyWithFallback(mv, pathName, simpleName, paramTypes, returnType, baseIndex, cacheIndex, nameSet);
}
private static void defineRealHashCodeWithFallback(SkinnyMethodAdapter mv, final String pathName,
final String simpleName, final Class[] paramTypes, final Class returnType,
final int baseIndex, final int cacheIndex, final Set<String> nameSet) {
defineRealBodyWithFallback(mv, pathName, simpleName, paramTypes, returnType, baseIndex, cacheIndex, nameSet);
}
private static void defineRealToStringWithFallback(SkinnyMethodAdapter mv, final String pathName,
final String simpleName, final Class[] paramTypes, final Class returnType,
final int baseIndex, final int cacheIndex, final Set<String> nameSet) {
defineRealBodyWithFallback(mv, pathName, simpleName, paramTypes, returnType, baseIndex, cacheIndex, nameSet);
}
private static boolean defineDefaultEquals(final int line, SkinnyMethodAdapter mv,
final Class[] paramTypes, final Class returnType) {
if ( paramTypes.length == 1 && paramTypes[0] == Object.class && returnType == Boolean.TYPE ) {
objectEquals(line, mv);
return true;
}
return false;
}
private static void objectEquals(final int line, SkinnyMethodAdapter mv) {
if ( line > 0 ) mv.line(line);
mv.aload(0);
mv.aload(1);
mv.invokespecial(p(Object.class), "equals", sig(Boolean.TYPE, params(Object.class)));
mv.ireturn();
}
private static boolean defineDefaultHashCode(final int line, SkinnyMethodAdapter mv,
final Class[] paramTypes, final Class returnType) {
if ( paramTypes.length == 0 && returnType == Integer.TYPE ) {
objectHashCode(line, mv);
return true;
}
return false;
}
private static void objectHashCode(final int line, SkinnyMethodAdapter mv) {
if ( line > 0 ) mv.line(line);
mv.aload(0);
mv.invokespecial(p(Object.class), "hashCode", sig(Integer.TYPE));
mv.ireturn();
}
private static boolean defineDefaultToString(final int line, SkinnyMethodAdapter mv,
final Class[] paramTypes, final Class returnType) {
if ( paramTypes.length == 0 && returnType == String.class ) {
objectToString(line, mv);
return true;
}
return false;
}
private static void objectToString(final int line, SkinnyMethodAdapter mv) {
if ( line > 0 ) mv.line(line);
mv.aload(0);
mv.invokespecial(p(Object.class), "toString", sig(String.class));
mv.areturn();
}
private static void writeClassFile(final String name, final byte[] bytecode) {
FileOutputStream fos = null;
try {
fos = new FileOutputStream(name + ".class");
fos.write(bytecode);
}
catch (IOException ex) { ex.printStackTrace(); }
finally {
try { if ( fos != null ) fos.close(); } catch (Exception e) {}
}
}
public static void coerceArgumentsToRuby(SkinnyMethodAdapter mv, Class[] paramTypes, int rubyIndex) {
if (paramTypes.length != 0) {
mv.pushInt(paramTypes.length);
mv.anewarray(p(IRubyObject.class));
for (int i = 0, argIndex = 1; i < paramTypes.length; i++) {
Class paramType = paramTypes[i];
mv.dup();
mv.pushInt(i);
if (paramTypes[i].isPrimitive()) {
mv.aload(rubyIndex);
if (paramType == byte.class || paramType == short.class || paramType == char.class || paramType == int.class) {
mv.iload(argIndex++);
mv.invokestatic(p(JavaUtil.class), "convertJavaToRuby", sig(IRubyObject.class, Ruby.class, int.class));
} else if (paramType == long.class) {
mv.lload(argIndex);
argIndex += 2;
mv.invokestatic(p(JavaUtil.class), "convertJavaToRuby", sig(IRubyObject.class, Ruby.class, long.class));
} else if (paramType == float.class) {
mv.fload(argIndex++);
mv.invokestatic(p(JavaUtil.class), "convertJavaToRuby", sig(IRubyObject.class, Ruby.class, float.class));
} else if (paramType == double.class) {
mv.dload(argIndex);
argIndex += 2;
mv.invokestatic(p(JavaUtil.class), "convertJavaToRuby", sig(IRubyObject.class, Ruby.class, double.class));
} else if (paramType == boolean.class) {
mv.iload(argIndex++);
mv.invokestatic(p(JavaUtil.class), "convertJavaToRuby", sig(IRubyObject.class, Ruby.class, boolean.class));
}
} else if (!IRubyObject.class.isAssignableFrom(paramType)) {
mv.aload(rubyIndex);
mv.aload(argIndex++);
mv.invokestatic(p(JavaUtil.class), "convertJavaToUsableRubyObject", sig(IRubyObject.class, Ruby.class, Object.class));
} else {
mv.aload(argIndex++);
}
mv.aastore();
}
} else {
mv.getstatic(p(IRubyObject.class), "NULL_ARRAY", ci(IRubyObject[].class));
}
}
public static void coerceResultAndReturn(SkinnyMethodAdapter mv, Class returnType) {
if (returnType != void.class) {
if (returnType.isPrimitive()) {
if (returnType == boolean.class) {
mv.getstatic(p(Boolean.class), "TYPE", ci(Class.class));
mv.invokeinterface(p(IRubyObject.class), "toJava", sig(Object.class, Class.class));
mv.checkcast(p(Boolean.class));
mv.invokevirtual(p(Boolean.class), "booleanValue", sig(boolean.class));
mv.ireturn();
} else {
mv.getstatic(p(getBoxType(returnType)), "TYPE", ci(Class.class));
mv.invokeinterface(p(IRubyObject.class), "toJava", sig(Object.class, Class.class));
if (returnType == byte.class) {
mv.checkcast(p(Number.class));
mv.invokevirtual(p(Number.class), "byteValue", sig(byte.class));
mv.ireturn();
} else if (returnType == short.class) {
mv.checkcast(p(Number.class));
mv.invokevirtual(p(Number.class), "shortValue", sig(short.class));
mv.ireturn();
} else if (returnType == char.class) {
mv.checkcast(p(Character.class));
mv.invokevirtual(p(Character.class), "charValue", sig(char.class));
mv.ireturn();
} else if (returnType == int.class) {
mv.checkcast(p(Number.class));
mv.invokevirtual(p(Number.class), "intValue", sig(int.class));
mv.ireturn();
} else if (returnType == long.class) {
mv.checkcast(p(Number.class));
mv.invokevirtual(p(Number.class), "longValue", sig(long.class));
mv.lreturn();
} else if (returnType == float.class) {
mv.checkcast(p(Number.class));
mv.invokevirtual(p(Number.class), "floatValue", sig(float.class));
mv.freturn();
} else if (returnType == double.class) {
mv.checkcast(p(Number.class));
mv.invokevirtual(p(Number.class), "doubleValue", sig(double.class));
mv.dreturn();
}
}
} else {
if (!IRubyObject.class.isAssignableFrom(returnType)) {
mv.ldc(Type.getType(returnType));
mv.invokeinterface(
p(IRubyObject.class), "toJava", sig(Object.class, Class.class));
}
mv.checkcast(p(returnType));
mv.areturn();
}
} else {
mv.voidreturn();
}
}
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;
}
}