package org.enumerable.lambda.support.jruby; import static org.enumerable.lambda.exception.UncheckedException.*; import static org.jruby.javasupport.JavaEmbedUtils.*; import java.lang.reflect.Method; import org.enumerable.lambda.Fn0; import org.enumerable.lambda.Fn1; import org.enumerable.lambda.Fn2; import org.enumerable.lambda.Fn3; import org.enumerable.lambda.annotation.NewLambda; import org.enumerable.lambda.exception.LambdaWeavingNotEnabledException; import org.jruby.Ruby; import org.jruby.RubyProc; import org.jruby.runtime.Arity; import org.jruby.runtime.Block; import org.jruby.runtime.BlockCallback; import org.jruby.runtime.CallBlock; import org.jruby.runtime.ThreadContext; import org.jruby.runtime.builtin.IRubyObject; /** * This is class is similar {@link org.enumerable.lambda.Lambda}, but instead of * creating lambdas inheriting from {@link org.enumerable.lambda.Fn0} it creates * lambdas extending {@link RubyProc} to be used together with JRuby. */ @SuppressWarnings("serial") public class LambdaJRuby { public abstract static class RubyProcFnBase extends RubyProc { private Method setup; class FnBlockCallback implements BlockCallback { public IRubyObject call(ThreadContext context, IRubyObject[] args, Block block) { args = getNormalArgumentsFromCallBlocksSingleRestArg(context, args); getBlock().arity().checkArity(context.getRuntime(), args); Object result = null; if (args.length == 0) result = RubyProcFnBase.this.call(); else if (args.length == 1) result = RubyProcFnBase.this.call(rubyToJava(args[0])); else if (args.length == 2) result = RubyProcFnBase.this.call(rubyToJava(args[0]), rubyToJava(args[1])); else if (args.length == 3) result = RubyProcFnBase.this .call(rubyToJava(args[0]), rubyToJava(args[1]), rubyToJava(args[2])); return javaToRuby(getRuntime(), result); } IRubyObject[] getNormalArgumentsFromCallBlocksSingleRestArg(ThreadContext context, IRubyObject[] args) { return getBlock().getBody().prepareArgumentsForCall(context, args, Block.Type.NORMAL); } } public RubyProcFnBase(Ruby runtime) { super(runtime, runtime.getProc(), Block.Type.LAMBDA); try { ThreadContext context = getRuntime().getThreadService().getCurrentContext(); setup().invoke(this, CallBlock.newCallClosure(this, getType(), getArityFromInstance(), new FnBlockCallback(), context)); } catch (Exception e) { throw uncheck(e); } } private Method setup() throws NoSuchMethodException { if (setup == null) { setup = RubyProc.class.getDeclaredMethod("setup", Block.class); setup.setAccessible(true); } return setup; } Arity getArityFromInstance() { return Arity.createArity(Fn0.getAndCheckArityForMethod(getImplementingClass(), "call")); } Class<?> getImplementingClass() { return getClass(); } protected Object call() { throw new UnsupportedOperationException(); } protected Object call(Object a1) { throw new UnsupportedOperationException(); } protected Object call(Object a1, Object a2) { throw new UnsupportedOperationException(); } protected Object call(Object a1, Object a2, Object a3) { throw new UnsupportedOperationException(); } } public static abstract class RubyProcFn0 extends RubyProcFnBase { public RubyProcFn0() { super(Ruby.getGlobalRuntime()); } public abstract Object call(); } public static abstract class RubyProcFn1 extends RubyProcFn0 { public Object call() { return call(default$1()); } protected Object default$1() { return null; } public abstract Object call(Object a1); } public static abstract class RubyProcFn2 extends RubyProcFn1 { public Object call(Object a1) { return call(a1, default$2()); } protected Object default$2() { return null; } public abstract Object call(Object a1, Object a2); } public static abstract class RubyProcFn3 extends RubyProcFn2 { public Object call(Object a1, Object a2) { return call(a1, a2, default$3()); } protected Object default$3() { return null; } public abstract Object call(Object a1, Object a2, Object a3); } /** * Creates a new lambda extending {@link RubyProc} taking no arguments. */ @NewLambda public static RubyProcFn0 lambda(Object block) { throw new LambdaWeavingNotEnabledException(); } /** * Creates a new lambda extending {@link RubyProc} taking one argument. */ @NewLambda public static RubyProcFn1 lambda(Object a1, Object block) { throw new LambdaWeavingNotEnabledException(); } /** * Creates a new lambda extending {@link RubyProc} taking two arguments. */ @NewLambda public static RubyProcFn2 lambda(Object a1, Object a2, Object block) { throw new LambdaWeavingNotEnabledException(); } /** * Creates a new lambda extending {@link RubyProc} taking three arguments. */ @NewLambda public static RubyProcFn3 lambda(Object a1, Object a2, Object a3, Object block) { throw new LambdaWeavingNotEnabledException(); } /** * Wraps the {@link RubyProc} in a {@link Fn0}. */ public static Fn0<Object> toFn0(final RubyProc proc) { return new Fn0<Object>() { public Object call() { Ruby ruby = proc.getRuntime(); return rubyToJava(proc.call(ruby.getThreadService().getCurrentContext(), new IRubyObject[0])); } }; } /** * Wraps the {@link RubyProc} in a {@link Fn1}. */ public static Fn1<Object, Object> toFn1(final RubyProc proc) { return new Fn1<Object, Object>() { public Object call() { Ruby ruby = proc.getRuntime(); return rubyToJava(proc.call(ruby.getThreadService().getCurrentContext(), new IRubyObject[0])); } public Object call(Object a1) { Ruby ruby = proc.getRuntime(); return rubyToJava(proc.call(ruby.getThreadService().getCurrentContext(), new IRubyObject[] { javaToRuby(ruby, a1) })); } }; } /** * Wraps the {@link RubyProc} in a {@link Fn2}. */ public static Fn2<Object, Object, Object> toFn2(final RubyProc proc) { return new Fn2<Object, Object, Object>() { public Object call() { Ruby ruby = proc.getRuntime(); return rubyToJava(proc.call(ruby.getThreadService().getCurrentContext(), new IRubyObject[0])); } public Object call(Object a1) { Ruby ruby = proc.getRuntime(); return rubyToJava(proc.call(ruby.getThreadService().getCurrentContext(), new IRubyObject[] { javaToRuby(ruby, a1) })); } public Object call(Object a1, Object a2) { Ruby ruby = proc.getRuntime(); return rubyToJava(proc.call(ruby.getThreadService().getCurrentContext(), new IRubyObject[] { javaToRuby(ruby, a1), javaToRuby(ruby, a2) })); } }; } /** * Wraps the {@link RubyProc} in a {@link Fn3}. */ public static Fn3<Object, Object, Object, Object> toFn3(final RubyProc proc) { return new Fn3<Object, Object, Object, Object>() { public Object call() { Ruby ruby = proc.getRuntime(); return rubyToJava(proc.call(ruby.getThreadService().getCurrentContext(), new IRubyObject[0])); } public Object call(Object a1) { Ruby ruby = proc.getRuntime(); return rubyToJava(proc.call(ruby.getThreadService().getCurrentContext(), new IRubyObject[] { javaToRuby(ruby, a1) })); } public Object call(Object a1, Object a2) { Ruby ruby = proc.getRuntime(); return rubyToJava(proc.call(ruby.getThreadService().getCurrentContext(), new IRubyObject[] { javaToRuby(ruby, a1), javaToRuby(ruby, a2) })); } public Object call(Object a1, Object a2, Object a3) { Ruby ruby = proc.getRuntime(); return rubyToJava(proc.call(ruby.getThreadService().getCurrentContext(), new IRubyObject[] { javaToRuby(ruby, a1), javaToRuby(ruby, a2), javaToRuby(ruby, a3) })); } }; } /** * Wraps the {@link Fn0} in a {@link RubyProc}. */ @SuppressWarnings("rawtypes") public static RubyProc toProc(final Fn0 fn) { return new RubyProcFn0() { public Object call() { return fn.call(); } }; } /** * Wraps the {@link Fn1} in a {@link RubyProc}. */ @SuppressWarnings({ "rawtypes", "unchecked" }) public static RubyProc toProc(final Fn1 fn) { return new RubyProcFn1() { public Object call() { return fn.call(); } public Object call(Object a1) { return fn.call(a1); } Class<?> getImplementingClass() { return fn.getClass(); } }; } /** * Wraps the {@link Fn2} in a {@link RubyProc}. */ @SuppressWarnings({ "rawtypes", "unchecked" }) public static RubyProc toProc(final Fn2 fn) { return new RubyProcFn2() { public Object call() { return fn.call(); } public Object call(Object a1) { return fn.call(a1); } public Object call(Object a1, Object a2) { return fn.call(a1, a2); } Class<?> getImplementingClass() { return fn.getClass(); } }; } /** * Wraps the {@link Fn3} in a {@link RubyProc}. */ @SuppressWarnings({ "rawtypes", "unchecked" }) public static RubyProc toProc(final Fn3 fn) { return new RubyProcFn3() { public Object call() { return fn.call(); } public Object call(Object a1) { return fn.call(a1); } public Object call(Object a1, Object a2) { return fn.call(a1, a2); } public Object call(Object a1, Object a2, Object a3) { return fn.call(a1, a2, a3); } Class<?> getImplementingClass() { return fn.getClass(); } }; } }