RubyToJavaInvoker.java

  1. package org.jruby.java.invokers;

  2. import java.lang.reflect.AccessibleObject;
  3. import java.lang.reflect.Array;
  4. import java.lang.reflect.Member;
  5. import java.lang.reflect.Method;
  6. import java.lang.reflect.Modifier;
  7. import java.util.ArrayList;
  8. import java.util.Arrays;
  9. import java.util.HashMap;
  10. import java.util.List;
  11. import java.util.Map;
  12. import java.util.concurrent.ConcurrentHashMap;
  13. import org.jruby.Ruby;
  14. import org.jruby.RubyModule;
  15. import org.jruby.internal.runtime.methods.CallConfiguration;
  16. import org.jruby.internal.runtime.methods.JavaMethod;
  17. import org.jruby.java.dispatch.CallableSelector;
  18. import org.jruby.java.proxies.ArrayJavaProxy;
  19. import org.jruby.java.proxies.JavaProxy;
  20. import org.jruby.javasupport.JavaCallable;
  21. import org.jruby.runtime.Arity;
  22. import org.jruby.runtime.Visibility;
  23. import org.jruby.runtime.builtin.IRubyObject;

  24. public abstract class RubyToJavaInvoker extends JavaMethod {
  25.     protected static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];
  26.     protected final JavaCallable javaCallable;
  27.     protected final JavaCallable[][] javaCallables;
  28.     protected final JavaCallable[] javaVarargsCallables;
  29.     protected final int minVarargsArity;
  30.     protected final Map cache;
  31.     protected final Ruby runtime;
  32.     private Member[] members;
  33.    
  34.     RubyToJavaInvoker(RubyModule host, Member[] members) {
  35.         super(host, Visibility.PUBLIC, CallConfiguration.FrameNoneScopeNone);
  36.         this.members = members;
  37.         this.runtime = host.getRuntime();
  38.         // we set all Java methods to optional, since many/most have overloads
  39.         setArity(Arity.OPTIONAL);

  40.         Ruby runtime = host.getRuntime();

  41.         // initialize all the callables for this method
  42.         JavaCallable callable = null;
  43.         JavaCallable[][] callables = null;
  44.         JavaCallable[] varargsCallables = null;
  45.         int varargsArity = Integer.MAX_VALUE;
  46.        
  47.         if (members.length == 1) {
  48.             callable = createCallable(runtime, members[0]);
  49.             if (callable.isVarArgs()) {
  50.                 varargsCallables = createCallableArray(callable);
  51.             }
  52.         } else {
  53.             Map<Integer, List<JavaCallable>> methodsMap = new HashMap<Integer, List<JavaCallable>>();
  54.             List<JavaCallable> varargsMethods = new ArrayList();
  55.             int maxArity = 0;
  56.             for (Member method: members) {
  57.                 int currentArity = getMemberParameterTypes(method).length;
  58.                 maxArity = Math.max(currentArity, maxArity);
  59.                 List<JavaCallable> methodsForArity = methodsMap.get(currentArity);
  60.                 if (methodsForArity == null) {
  61.                     methodsForArity = new ArrayList<JavaCallable>();
  62.                     methodsMap.put(currentArity,methodsForArity);
  63.                 }
  64.                 JavaCallable javaMethod = createCallable(runtime,method);
  65.                 methodsForArity.add(javaMethod);

  66.                 if (isMemberVarArgs(method)) {
  67.                     varargsArity = Math.min(currentArity - 1, varargsArity);
  68.                     varargsMethods.add(javaMethod);
  69.                 }
  70.             }

  71.             callables = createCallableArrayArray(maxArity + 1);
  72.             for (Map.Entry<Integer,List<JavaCallable>> entry : methodsMap.entrySet()) {
  73.                 List<JavaCallable> methodsForArity = (List<JavaCallable>)entry.getValue();

  74.                 JavaCallable[] methodsArray = methodsForArity.toArray(createCallableArray(methodsForArity.size()));
  75.                 callables[((Integer)entry.getKey()).intValue()] = methodsArray;
  76.             }

  77.             if (varargsMethods.size() > 0) {
  78.                 // have at least one varargs, build that map too
  79.                 varargsCallables = createCallableArray(varargsMethods.size());
  80.                 varargsMethods.toArray(varargsCallables);
  81.             }
  82.         }
  83.         members = null;

  84.         // initialize cache of parameter types to method
  85.         // FIXME: No real reason to use CHM, is there?
  86.         cache = new ConcurrentHashMap(0, 0.75f, 1);

  87.         this.javaCallable = callable;
  88.         this.javaCallables = callables;
  89.         this.javaVarargsCallables = varargsCallables;
  90.         this.minVarargsArity = varargsArity;
  91.        
  92.         // if it's not overloaded, set up a NativeCall
  93.         if (javaCallable != null) {
  94.             // no constructor support yet
  95.             if (javaCallable instanceof org.jruby.javasupport.JavaMethod) {
  96.                 org.jruby.javasupport.JavaMethod javaMethod = (org.jruby.javasupport.JavaMethod)javaCallable;
  97.                 Method method = (Method)javaMethod.getValue();
  98.                 // only public, since non-public don't bind
  99.                 if (Modifier.isPublic(method.getModifiers()) && Modifier.isPublic(method.getDeclaringClass().getModifiers())) {
  100.                     setNativeCall(method.getDeclaringClass(), method.getName(), method.getReturnType(), method.getParameterTypes(), Modifier.isStatic(method.getModifiers()), true);
  101.                 }
  102.             }
  103.         } else {
  104.             // use the lowest-arity non-overload
  105.             for (JavaCallable[] callablesForArity : javaCallables) {
  106.                 if (callablesForArity != null
  107.                         && callablesForArity.length == 1
  108.                         && callablesForArity[0] instanceof org.jruby.javasupport.JavaMethod) {
  109.                     Method method = (Method)((org.jruby.javasupport.JavaMethod)callablesForArity[0]).getValue();
  110.                     // only public, since non-public don't bind
  111.                     if (Modifier.isPublic(method.getModifiers()) && Modifier.isPublic(method.getDeclaringClass().getModifiers())) {
  112.                         setNativeCall(method.getDeclaringClass(), method.getName(), method.getReturnType(), method.getParameterTypes(), Modifier.isStatic(method.getModifiers()), true);
  113.                     }
  114.                 }
  115.             }
  116.         }
  117.     }

  118.     protected Member[] getMembers() {
  119.         return members;
  120.     }

  121.     protected AccessibleObject[] getAccessibleObjects() {
  122.         return (AccessibleObject[])getMembers();
  123.     }

  124.     protected abstract JavaCallable createCallable(Ruby ruby, Member member);

  125.     protected abstract JavaCallable[] createCallableArray(JavaCallable callable);

  126.     protected abstract JavaCallable[] createCallableArray(int size);

  127.     protected abstract JavaCallable[][] createCallableArrayArray(int size);

  128.     protected abstract Class[] getMemberParameterTypes(Member member);

  129.     protected abstract boolean isMemberVarArgs(Member member);

  130.     static Object convertArg(IRubyObject arg, JavaCallable method, int index) {
  131.         return arg.toJava(method.getParameterTypes()[index]);
  132.     }

  133.     static Object convertVarargs(IRubyObject[] args, JavaCallable method) {
  134.         Class[] types = method.getParameterTypes();
  135.         Class varargArrayType = types[types.length - 1];
  136.         Class varargType = varargArrayType.getComponentType();
  137.         int varargsStart = types.length - 1;
  138.         int varargsCount = args.length - varargsStart;

  139.         Object varargs;
  140.         if (args.length == 0) {
  141.             return Array.newInstance(varargType, 0);
  142.         } else if (varargsCount == 1 && args[varargsStart] instanceof ArrayJavaProxy) {
  143.             // we may have a pre-created array to pass; try that first
  144.             varargs = args[varargsStart].toJava(varargArrayType);
  145.         } else {
  146.             varargs = Array.newInstance(varargType, varargsCount);

  147.             for (int i = 0; i < varargsCount; i++) {
  148.                 Array.set(varargs, i, args[varargsStart + i].toJava(varargType));
  149.             }
  150.         }
  151.         return varargs;
  152.     }

  153.     static JavaProxy castJavaProxy(IRubyObject self) {
  154.         assert self instanceof JavaProxy : "Java methods can only be invoked on Java objects";
  155.         return (JavaProxy)self;
  156.     }

  157.     static void trySetAccessible(AccessibleObject[] accObjs) {
  158.         if (!Ruby.isSecurityRestricted()) {
  159.             try {
  160.                 AccessibleObject.setAccessible(accObjs, true);
  161.             } catch(SecurityException e) {}
  162.         }
  163.     }

  164.     void raiseNoMatchingCallableError(String name, IRubyObject proxy, Object... args) {
  165.         int len = args.length;
  166.         Class[] argTypes = new Class[args.length];
  167.         for (int i = 0; i < len; i++) {
  168.             argTypes[i] = args[i].getClass();
  169.         }
  170.         throw proxy.getRuntime().newArgumentError("no " + name + " with arguments matching " + Arrays.toString(argTypes) + " on object " + proxy.getMetaClass());
  171.     }

  172.     protected JavaCallable findCallable(IRubyObject self, String name, IRubyObject[] args, int arity) {
  173.         JavaCallable callable;
  174.         if ((callable = javaCallable) == null) {
  175.             JavaCallable[] callablesForArity = null;
  176.             if (arity >= javaCallables.length || (callablesForArity = javaCallables[arity]) == null) {
  177.                 if (javaVarargsCallables != null) {
  178.                     callable = CallableSelector.matchingCallableArityN(runtime, cache, javaVarargsCallables, args, arity);
  179.                     if (callable == null) {
  180.                         throw CallableSelector.argTypesDoNotMatch(self.getRuntime(), self, javaVarargsCallables, (Object[])args);
  181.                     }
  182.                     return callable;
  183.                 } else {
  184.                     throw self.getRuntime().newArgumentError(args.length, javaCallables.length - 1);
  185.                 }
  186.             }
  187.             callable = CallableSelector.matchingCallableArityN(runtime, cache, callablesForArity, args, arity);
  188.             if (callable == null && javaVarargsCallables != null) {
  189.                 callable = CallableSelector.matchingCallableArityN(runtime, cache, javaVarargsCallables, args, arity);
  190.                 if (callable == null) {
  191.                     throw CallableSelector.argTypesDoNotMatch(self.getRuntime(), self, javaVarargsCallables, (Object[])args);
  192.                 }
  193.                 return callable;
  194.             }
  195.             if (callable == null) {
  196.                 throw CallableSelector.argTypesDoNotMatch(self.getRuntime(), self, callablesForArity, (Object[])args);
  197.             }
  198.         } else {
  199.             if (!callable.isVarArgs() && callable.getParameterTypes().length != args.length) {
  200.                 throw self.getRuntime().newArgumentError(args.length, callable.getParameterTypes().length);
  201.             }
  202.         }
  203.         return callable;
  204.     }

  205.     protected JavaCallable findCallableArityZero(IRubyObject self, String name) {
  206.         JavaCallable callable;
  207.         if ((callable = javaCallable) == null) {
  208.             // TODO: varargs?
  209.             JavaCallable[] callablesForArity = null;
  210.             if (javaCallables.length == 0 || (callablesForArity = javaCallables[0]) == null) {
  211.                 raiseNoMatchingCallableError(name, self, EMPTY_OBJECT_ARRAY);
  212.             }
  213.             callable = callablesForArity[0];
  214.         } else {
  215.             if (callable.getParameterTypes().length != 0) {
  216.                 throw self.getRuntime().newArgumentError(0, callable.getParameterTypes().length);
  217.             }
  218.         }
  219.         return callable;
  220.     }

  221.     protected JavaCallable findCallableArityOne(IRubyObject self, String name, IRubyObject arg0) {
  222.         JavaCallable callable;
  223.         if ((callable = javaCallable) == null) {
  224.             // TODO: varargs?
  225.             JavaCallable[] callablesForArity = null;
  226.             if (javaCallables.length <= 1 || (callablesForArity = javaCallables[1]) == null) {
  227.                 throw self.getRuntime().newArgumentError(1, javaCallables.length - 1);
  228.             }
  229.             callable = CallableSelector.matchingCallableArityOne(runtime, cache, callablesForArity, arg0);
  230.             if (callable == null) {
  231.                 throw CallableSelector.argTypesDoNotMatch(self.getRuntime(), self, callablesForArity, arg0);
  232.             }
  233.         } else {
  234.             if (callable.getParameterTypes().length != 1) {
  235.                 throw self.getRuntime().newArgumentError(1, callable.getParameterTypes().length);
  236.             }
  237.         }
  238.         return callable;
  239.     }

  240.     protected JavaCallable findCallableArityTwo(IRubyObject self, String name, IRubyObject arg0, IRubyObject arg1) {
  241.         JavaCallable callable;
  242.         if ((callable = javaCallable) == null) {
  243.             // TODO: varargs?
  244.             JavaCallable[] callablesForArity = null;
  245.             if (javaCallables.length <= 2 || (callablesForArity = javaCallables[2]) == null) {
  246.                 throw self.getRuntime().newArgumentError(2, javaCallables.length - 1);
  247.             }
  248.             callable = CallableSelector.matchingCallableArityTwo(runtime, cache, callablesForArity, arg0, arg1);
  249.             if (callable == null) {
  250.                 throw CallableSelector.argTypesDoNotMatch(self.getRuntime(), self, callablesForArity, arg0, arg1);
  251.             }
  252.         } else {
  253.             if (callable.getParameterTypes().length != 2) {
  254.                 throw self.getRuntime().newArgumentError(2, callable.getParameterTypes().length);
  255.             }
  256.         }
  257.         return callable;
  258.     }

  259.     protected JavaCallable findCallableArityThree(IRubyObject self, String name, IRubyObject arg0, IRubyObject arg1, IRubyObject arg2) {
  260.         JavaCallable callable;
  261.         if ((callable = javaCallable) == null) {
  262.             // TODO: varargs?
  263.             JavaCallable[] callablesForArity = null;
  264.             if (javaCallables.length <= 3 || (callablesForArity = javaCallables[3]) == null) {
  265.                 throw self.getRuntime().newArgumentError(3, javaCallables.length - 1);
  266.             }
  267.             callable = CallableSelector.matchingCallableArityThree(runtime, cache, callablesForArity, arg0, arg1, arg2);
  268.             if (callable == null) {
  269.                 throw CallableSelector.argTypesDoNotMatch(self.getRuntime(), self, callablesForArity, arg0, arg1, arg2);
  270.             }
  271.         } else {
  272.             if (callable.getParameterTypes().length != 3) {
  273.                 throw self.getRuntime().newArgumentError(3, callable.getParameterTypes().length);
  274.             }
  275.         }
  276.         return callable;
  277.     }

  278.     protected JavaCallable findCallableArityFour(IRubyObject self, String name, IRubyObject arg0, IRubyObject arg1, IRubyObject arg2, IRubyObject arg3) {
  279.         JavaCallable callable;
  280.         if ((callable = javaCallable) == null) {
  281.             // TODO: varargs?
  282.             JavaCallable[] callablesForArity = null;
  283.             if (javaCallables.length <= 4 || (callablesForArity = javaCallables[4]) == null) {
  284.                 throw self.getRuntime().newArgumentError(4, javaCallables.length - 1);
  285.             }
  286.             callable = CallableSelector.matchingCallableArityFour(runtime, cache, callablesForArity, arg0, arg1, arg2, arg3);
  287.             if (callable == null) {
  288.                 throw CallableSelector.argTypesDoNotMatch(self.getRuntime(), self, callablesForArity, arg0, arg1, arg2, arg3);
  289.             }
  290.         } else {
  291.             if (callable.getParameterTypes().length != 4) {
  292.                 throw self.getRuntime().newArgumentError(4, callable.getParameterTypes().length);
  293.             }
  294.         }
  295.         return callable;
  296.     }
  297. }