cw.visitField(ACC_STATIC | ACC_PRIVATE, "ruby", ci(Ruby.class), null, null).visitEnd();
cw.visitField(ACC_STATIC | ACC_PRIVATE, "rubyClass", ci(RubyClass.class), null, null).visitEnd();
cw.visitField(ACC_PRIVATE | ACC_FINAL, "self", ci(IRubyObject.class), null, null).visitEnd();
// create constructor
SkinnyMethodAdapter initMethod = new SkinnyMethodAdapter(cw.visitMethod(ACC_PUBLIC, "<init>", sig(void.class, IRubyObject.class), null, null));
initMethod.aload(0);
initMethod.invokespecial(p(Object.class), "<init>", sig(void.class));
// store the wrapper
initMethod.aload(0);
initMethod.aload(1);
initMethod.putfield(pathName, "self", ci(IRubyObject.class));
// end constructor
initMethod.voidreturn();
initMethod.end();
// start setup method
SkinnyMethodAdapter setupMethod = new SkinnyMethodAdapter(cw.visitMethod(ACC_STATIC | ACC_PUBLIC | ACC_SYNTHETIC, "__setup__", sig(void.class, RubyClass.class), null, null));
setupMethod.start();
// set RubyClass
setupMethod.aload(0);
setupMethod.dup();
setupMethod.putstatic(pathName, "rubyClass", ci(RubyClass.class));
// set Ruby
setupMethod.invokevirtual(p(RubyClass.class), "getClassRuntime", sig(Ruby.class));
setupMethod.putstatic(pathName, "ruby", ci(Ruby.class));
// for each simple method name, implement the complex methods, calling the simple version
for (Map.Entry<String, List<Method>> entry : simpleToAll.entrySet()) {
String simpleName = entry.getKey();
Set<String> nameSet = JavaUtil.getRubyNamesForJavaName(simpleName, entry.getValue());
// all methods dispatch to the simple version by default, or method_missing if it's not present
cw.visitField(ACC_STATIC | ACC_PUBLIC | ACC_VOLATILE, simpleName, ci(DynamicMethod.class), null, null).visitEnd();
for (Method method : entry.getValue()) {
Class[] paramTypes = method.getParameterTypes();
Class returnType = method.getReturnType();
SkinnyMethodAdapter mv = new SkinnyMethodAdapter(
cw.visitMethod(ACC_PUBLIC, simpleName, sig(returnType, paramTypes), null, null));
mv.start();
// TODO: this code should really check if a Ruby equals method is implemented or not.
if(simpleName.equals("equals") && paramTypes.length == 1 && paramTypes[0] == Object.class && returnType == Boolean.TYPE) {
mv.aload(0);
mv.aload(1);
mv.invokespecial(p(Object.class), "equals", sig(Boolean.TYPE, params(Object.class)));
mv.ireturn();
} else if(simpleName.equals("hashCode") && paramTypes.length == 0 && returnType == Integer.TYPE) {
mv.aload(0);
mv.invokespecial(p(Object.class), "hashCode", sig(Integer.TYPE));
mv.ireturn();
} else if(simpleName.equals("toString") && paramTypes.length == 0 && returnType == String.class) {
mv.aload(0);
mv.invokespecial(p(Object.class), "toString", sig(String.class));
mv.areturn();
} else {
Label dispatch = new Label();
Label end = new Label();
Label recheckMethod = new Label();
// Try to look up field for simple name
// get field; if nonnull, go straight to dispatch
mv.getstatic(pathName, simpleName, ci(DynamicMethod.class));
mv.dup();
mv.ifnonnull(dispatch);
// field is null, lock class and try to populate
mv.pop();
mv.getstatic(pathName, "rubyClass", ci(RubyClass.class));
mv.monitorenter();
// try/finally block to ensure unlock
Label tryStart = new Label();
Label tryEnd = new Label();
Label finallyStart = new Label();
Label finallyEnd = new Label();
mv.label(tryStart);
mv.aload(0);
mv.getfield(pathName, "self", ci(IRubyObject.class));
for (String eachName : nameSet) {
mv.ldc(eachName);
}
mv.invokestatic(p(MiniJava.class), "searchMethod", sig(DynamicMethod.class, params(IRubyObject.class, String.class, nameSet.size())));
mv.dup();
// if it's not undefined...
mv.getstatic(p(UndefinedMethod.class), "INSTANCE", ci(UndefinedMethod.class));
Label noStore = new Label();
mv.if_acmpeq(noStore);
// store it
mv.dup();
mv.putstatic(pathName, simpleName, ci(DynamicMethod.class));
// all done with lookup attempts, pop any result and release monitor
mv.label(noStore);
mv.pop();
mv.getstatic(pathName, "rubyClass", ci(RubyClass.class));
mv.monitorexit();
mv.go_to(recheckMethod);
// end of try block
mv.label(tryEnd);
// finally block to release monitor
mv.label(finallyStart);
mv.getstatic(pathName, "rubyClass", ci(RubyClass.class));
mv.monitorexit();
mv.label(finallyEnd);
mv.athrow();
// exception handling for monitor release
mv.trycatch(tryStart, tryEnd, finallyStart, null);
mv.trycatch(finallyStart, finallyEnd, finallyStart, null);
// re-get, re-check method; if not null now, go to dispatch
mv.label(recheckMethod);
mv.getstatic(pathName, simpleName, ci(DynamicMethod.class));
mv.dup();
mv.ifnonnull(dispatch);
// method still not available, call method_missing
mv.pop();
// exit monitor before making call
// FIXME: this not being in a finally is a little worrisome
mv.aload(0);
mv.getfield(pathName, "self", ci(IRubyObject.class));
mv.ldc(simpleName);
coerceArgumentsToRuby(mv, paramTypes, pathName);
mv.invokestatic(p(RuntimeHelpers.class), "invokeMethodMissing", sig(IRubyObject.class, IRubyObject.class, String.class, IRubyObject[].class));
mv.go_to(end);
// perform the dispatch
mv.label(dispatch);
// get current context
mv.getstatic(pathName, "ruby", ci(Ruby.class));
mv.invokevirtual(p(Ruby.class), "getCurrentContext", sig(ThreadContext.class));
// load self, class, and name
mv.aload(0);
mv.getfield(pathName, "self", ci(IRubyObject.class));
mv.getstatic(pathName, "rubyClass", ci(RubyClass.class));
mv.ldc(simpleName);
// coerce arguments
coerceArgumentsToRuby(mv, paramTypes, pathName);
// load null block
mv.getstatic(p(Block.class), "NULL_BLOCK", ci(Block.class));
// invoke method
mv.invokevirtual(p(DynamicMethod.class), "call", sig(IRubyObject.class, ThreadContext.class, IRubyObject.class, RubyModule.class, String.class, IRubyObject[].class, Block.class));
mv.label(end);
coerceResultAndReturn(method, mv, returnType);
}
mv.end();
}
}
// end setup method
setupMethod.voidreturn();