Package org.jruby.javasupport

Source Code of org.jruby.javasupport.Java

/***** BEGIN LICENSE BLOCK *****
* Version: CPL 1.0/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Common Public
* License Version 1.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.eclipse.org/legal/cpl-v10.html
*
* Software distributed under the License is distributed on an "AS
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
* implied. See the License for the specific language governing
* rights and limitations under the License.
*
* Copyright (C) 2002 Jan Arne Petersen <jpetersen@uni-bonn.de>
* Copyright (C) 2002-2004 Anders Bengtsson <ndrsbngtssn@yahoo.se>
* Copyright (C) 2004 Stefan Matthias Aust <sma@3plus4.de>
* Copyright (C) 2004 David Corbin <dcorbin@users.sourceforge.net>
* Copyright (C) 2004-2005 Thomas E Enebo <enebo@acm.org>
* Copyright (C) 2006 Kresten Krab Thorup <krab@gnu.org>
* Copyright (C) 2007 William N Dortch <bill.dortch@gmail.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the CPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the CPL, the GPL or the LGPL.
***** END LICENSE BLOCK *****/
package org.jruby.javasupport;

import org.jruby.java.proxies.JavaInterfaceTemplate;
import org.jruby.java.addons.KernelJavaAddons;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.concurrent.ConcurrentHashMap;

import org.jruby.MetaClass;
import org.jruby.Ruby;
import org.jruby.RubyArray;
import org.jruby.RubyClass;
import org.jruby.RubyClassPathVariable;
import org.jruby.RubyException;
import org.jruby.RubyModule;
import org.jruby.RubyProc;
import org.jruby.RubyString;
import org.jruby.common.IRubyWarnings.ID;
import org.jruby.exceptions.RaiseException;
import org.jruby.javasupport.proxy.JavaProxyClass;
import org.jruby.javasupport.proxy.JavaProxyConstructor;
import org.jruby.javasupport.util.RuntimeHelpers;
import org.jruby.runtime.Arity;
import org.jruby.runtime.Block;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.Visibility;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.runtime.load.Library;
import org.jruby.util.ClassProvider;
import org.jruby.anno.JRubyMethod;
import org.jruby.anno.JRubyModule;
import org.jruby.internal.runtime.methods.JavaMethod.JavaMethodNoBlock;
import org.jruby.internal.runtime.methods.JavaMethod.JavaMethodZero;
import org.jruby.java.MiniJava;
import org.jruby.java.addons.ArrayJavaAddons;
import org.jruby.java.addons.IOJavaAddons;
import org.jruby.java.addons.StringJavaAddons;
import org.jruby.java.proxies.ArrayJavaProxy;
import org.jruby.java.proxies.ConcreteJavaProxy;
import org.jruby.java.proxies.JavaProxy;
import org.jruby.runtime.callback.Callback;

@JRubyModule(name = "Java")
public class Java implements Library {

    public void load(Ruby runtime, boolean wrap) throws IOException {
        createJavaModule(runtime);
        runtime.getLoadService().smartLoad("builtin/javasupport");
        RubyClassPathVariable.createClassPathVariable(runtime);
    }

    public static RubyModule createJavaModule(Ruby runtime) {
        ThreadContext context = runtime.getCurrentContext();
        RubyModule javaModule = runtime.defineModule("Java");
       
        javaModule.defineAnnotatedMethods(Java.class);

        JavaObject.createJavaObjectClass(runtime, javaModule);
        JavaArray.createJavaArrayClass(runtime, javaModule);
        JavaClass.createJavaClassClass(runtime, javaModule);
        JavaMethod.createJavaMethodClass(runtime, javaModule);
        JavaConstructor.createJavaConstructorClass(runtime, javaModule);
        JavaField.createJavaFieldClass(runtime, javaModule);
       
        // set of utility methods for Java-based proxy objects
        JavaProxyMethods.createJavaProxyMethods(context);
       
        // the proxy (wrapper) type hierarchy
        JavaProxy.createJavaProxy(context);
        ConcreteJavaProxy.createConcreteJavaProxy(context);
        //InterfaceJavaProxy.createInterfaceJavaProxy(context);
        ArrayJavaProxy.createArrayJavaProxy(context);

        // also create the JavaProxy* classes
        JavaProxyClass.createJavaProxyModule(runtime);

        // The template for interface modules
        JavaInterfaceTemplate.createJavaInterfaceTemplateModule(context);

        RubyModule javaUtils = runtime.defineModule("JavaUtilities");
       
        javaUtils.defineAnnotatedMethods(JavaUtilities.class);

        runtime.getJavaSupport().setConcreteProxyCallback(new Callback() {
            public IRubyObject execute(IRubyObject recv, IRubyObject[] args, Block block) {
                Arity.checkArgumentCount(recv.getRuntime(), args, 1, 1);
               
                return Java.concrete_proxy_inherited(recv, args[0]);
            }

            public Arity getArity() {
                return Arity.ONE_ARGUMENT;
            }
        });

        JavaArrayUtilities.createJavaArrayUtilitiesModule(runtime);

        RubyClass javaProxy = runtime.defineClass("JavaProxy", runtime.getObject(), runtime.getObject().getAllocator());
        javaProxy.defineAnnotatedMethods(JavaProxy.class);
       
        // Now attach Java-related extras to core classes
        runtime.getArray().defineAnnotatedMethods(ArrayJavaAddons.class);
        runtime.getKernel().defineAnnotatedMethods(KernelJavaAddons.class);
        runtime.getString().defineAnnotatedMethods(StringJavaAddons.class);
        runtime.getIO().defineAnnotatedMethods(IOJavaAddons.class);
       
        // add all name-to-class mappings
        addNameClassMappings(runtime, runtime.getJavaSupport().getNameClassMap());
       
        // add some base Java classes everyone will need
        runtime.getJavaSupport().setObjectJavaClass(JavaClass.get(runtime, Object.class));
       
        // finally, set JavaSupport.isEnabled to true
        runtime.getJavaSupport().setActive(true);

        return javaModule;
    }
   
    /**
     * This populates the master map from short-cut names to JavaClass instances for
     * a number of core Java types.
     *
     * @param runtime
     * @param nameClassMap
     */
    private static void addNameClassMappings(Ruby runtime, Map<String, JavaClass> nameClassMap) {
        JavaClass booleanPrimClass = JavaClass.get(runtime, Boolean.TYPE);
        JavaClass booleanClass = JavaClass.get(runtime, Boolean.class);
        nameClassMap.put("boolean", booleanPrimClass);
        nameClassMap.put("Boolean", booleanClass);
        nameClassMap.put("java.lang.Boolean", booleanClass);
       
        JavaClass bytePrimClass = JavaClass.get(runtime, Byte.TYPE);
        JavaClass byteClass = JavaClass.get(runtime, Byte.class);
        nameClassMap.put("byte", bytePrimClass);
        nameClassMap.put("Byte", byteClass);
        nameClassMap.put("java.lang.Byte", byteClass);
       
        JavaClass shortPrimClass = JavaClass.get(runtime, Short.TYPE);
        JavaClass shortClass = JavaClass.get(runtime, Short.class);
        nameClassMap.put("short", shortPrimClass);
        nameClassMap.put("Short", shortClass);
        nameClassMap.put("java.lang.Short", shortClass);
       
        JavaClass charPrimClass = JavaClass.get(runtime, Character.TYPE);
        JavaClass charClass = JavaClass.get(runtime, Character.class);
        nameClassMap.put("char", charPrimClass);
        nameClassMap.put("Character", charClass);
        nameClassMap.put("Char", charClass);
        nameClassMap.put("java.lang.Character", charClass);
       
        JavaClass intPrimClass = JavaClass.get(runtime, Integer.TYPE);
        JavaClass intClass = JavaClass.get(runtime, Integer.class);
        nameClassMap.put("int", intPrimClass);
        nameClassMap.put("Integer", intClass);
        nameClassMap.put("Int", intClass);
        nameClassMap.put("java.lang.Integer", intClass);
       
        JavaClass longPrimClass = JavaClass.get(runtime, Long.TYPE);
        JavaClass longClass = JavaClass.get(runtime, Long.class);
        nameClassMap.put("long", longPrimClass);
        nameClassMap.put("Long", longClass);
        nameClassMap.put("java.lang.Long", longClass);
       
        JavaClass floatPrimClass = JavaClass.get(runtime, Float.TYPE);
        JavaClass floatClass = JavaClass.get(runtime, Float.class);
        nameClassMap.put("float", floatPrimClass);
        nameClassMap.put("Float", floatClass);
        nameClassMap.put("java.lang.Float", floatClass);
       
        JavaClass doublePrimClass = JavaClass.get(runtime, Double.TYPE);
        JavaClass doubleClass = JavaClass.get(runtime, Double.class);
        nameClassMap.put("double", doublePrimClass);
        nameClassMap.put("Double", doubleClass);
        nameClassMap.put("java.lang.Double", doubleClass);
       
        JavaClass bigintClass = JavaClass.get(runtime, BigInteger.class);
        nameClassMap.put("big_int", bigintClass);
        nameClassMap.put("big_integer", bigintClass);
        nameClassMap.put("BigInteger", bigintClass);
        nameClassMap.put("java.math.BigInteger", bigintClass);
       
        JavaClass bigdecimalClass = JavaClass.get(runtime, BigDecimal.class);
        nameClassMap.put("big_decimal", bigdecimalClass);
        nameClassMap.put("BigDecimal", bigdecimalClass);
        nameClassMap.put("java.math.BigDecimal", bigdecimalClass);
       
        JavaClass objectClass = JavaClass.get(runtime, Object.class);
        nameClassMap.put("object", objectClass);
        nameClassMap.put("Object", objectClass);
        nameClassMap.put("java.lang.Object", objectClass);
       
        JavaClass stringClass = JavaClass.get(runtime, String.class);
        nameClassMap.put("string", stringClass);
        nameClassMap.put("String", stringClass);
        nameClassMap.put("java.lang.String", stringClass);
    }

    @JRubyModule(name = "JavaUtilities")
    public static class JavaUtilities {
        @JRubyMethod(module = true, visibility = Visibility.PRIVATE)
        public static IRubyObject wrap(IRubyObject recv, IRubyObject arg0) {
            return Java.wrap(recv, arg0);
        }
       
        @JRubyMethod(name = "valid_constant_name?", module = true, visibility = Visibility.PRIVATE)
        public static IRubyObject valid_constant_name_p(IRubyObject recv, IRubyObject arg0) {
            return Java.valid_constant_name_p(recv, arg0);
        }
       
        @JRubyMethod(module = true, visibility = Visibility.PRIVATE)
        public static IRubyObject primitive_match(IRubyObject recv, IRubyObject arg0, IRubyObject arg1) {
            return Java.primitive_match(recv, arg0, arg1);
        }
       
        @JRubyMethod(module = true, visibility = Visibility.PRIVATE)
        public static IRubyObject access(IRubyObject recv, IRubyObject arg0) {
            return Java.access(recv, arg0);
        }
       
        @JRubyMethod(module = true, visibility = Visibility.PRIVATE)
        public static IRubyObject matching_method(IRubyObject recv, IRubyObject arg0, IRubyObject arg1) {
            return Java.matching_method(recv, arg0, arg1);
        }
       
        @JRubyMethod(module = true, visibility = Visibility.PRIVATE)
        public static IRubyObject set_java_object(IRubyObject recv, IRubyObject self, IRubyObject java_object) {
            self.getInstanceVariables().fastSetInstanceVariable("@java_object", java_object);
            self.dataWrapStruct(java_object);
            return java_object;
        }
       
        @JRubyMethod(module = true, visibility = Visibility.PRIVATE)
        public static IRubyObject get_deprecated_interface_proxy(ThreadContext context, IRubyObject recv, IRubyObject arg0) {
            return Java.get_deprecated_interface_proxy(context, recv, arg0);
        }
       
        @JRubyMethod(module = true, visibility = Visibility.PRIVATE)
        public static IRubyObject get_interface_module(IRubyObject recv, IRubyObject arg0) {
            return Java.get_interface_module(recv, arg0);
        }
       
        @JRubyMethod(module = true, visibility = Visibility.PRIVATE)
        public static IRubyObject get_package_module(IRubyObject recv, IRubyObject arg0) {
            return Java.get_package_module(recv, arg0);
        }
       
        @JRubyMethod(module = true, visibility = Visibility.PRIVATE)
        public static IRubyObject get_package_module_dot_format(IRubyObject recv, IRubyObject arg0) {
            return Java.get_package_module_dot_format(recv, arg0);
        }
       
        @JRubyMethod(module = true, visibility = Visibility.PRIVATE)
        public static IRubyObject get_proxy_class(IRubyObject recv, IRubyObject arg0) {
            return Java.get_proxy_class(recv, arg0);
        }
       
        @JRubyMethod(module = true, visibility = Visibility.PRIVATE)
        public static IRubyObject is_primitive_type(IRubyObject recv, IRubyObject arg0) {
            return Java.is_primitive_type(recv, arg0);
        }

        @JRubyMethod(module = true, visibility = Visibility.PRIVATE)
        public static IRubyObject create_proxy_class(IRubyObject recv, IRubyObject arg0, IRubyObject arg1, IRubyObject arg2) {
            return Java.create_proxy_class(recv, arg0, arg1, arg2);
        }
       
        @JRubyMethod(module = true, visibility = Visibility.PRIVATE)
        public static IRubyObject get_java_class(IRubyObject recv, IRubyObject arg0) {
            return Java.get_java_class(recv, arg0);
        }
       
        @JRubyMethod(module = true, visibility = Visibility.PRIVATE)
        public static IRubyObject get_top_level_proxy_or_package(ThreadContext context, IRubyObject recv, IRubyObject arg0) {
            return Java.get_top_level_proxy_or_package(context, recv, arg0);
        }
       
        @JRubyMethod(module = true, backtrace = true, visibility = Visibility.PRIVATE)
        public static IRubyObject get_proxy_or_package_under_package(ThreadContext context, IRubyObject recv, IRubyObject arg0, IRubyObject arg1) {
            return Java.get_proxy_or_package_under_package(context, recv, arg0, arg1);
        }
       
        @Deprecated
        @JRubyMethod(module = true, visibility = Visibility.PRIVATE)
        public static IRubyObject add_proxy_extender(IRubyObject recv, IRubyObject arg0) {
            return Java.add_proxy_extender(recv, arg0);
        }
    }

    private static final ClassProvider JAVA_PACKAGE_CLASS_PROVIDER = new ClassProvider() {

        public RubyClass defineClassUnder(RubyModule pkg, String name, RubyClass superClazz) {
            // shouldn't happen, but if a superclass is specified, it's not ours
            if (superClazz != null) {
                return null;
            }
            IRubyObject packageName;
            // again, shouldn't happen. TODO: might want to throw exception instead.
            if ((packageName = pkg.getInstanceVariables().fastGetInstanceVariable("@package_name")) == null) {
                return null;
            }
            Ruby runtime = pkg.getRuntime();
            return (RubyClass) get_proxy_class(
                    runtime.getJavaSupport().getJavaUtilitiesModule(),
                    JavaClass.forNameVerbose(runtime, packageName.asJavaString() + name));
        }

        public RubyModule defineModuleUnder(RubyModule pkg, String name) {
            IRubyObject packageName;
            // again, shouldn't happen. TODO: might want to throw exception instead.
            if ((packageName = pkg.getInstanceVariables().fastGetInstanceVariable("@package_name")) == null) {
                return null;
            }
            Ruby runtime = pkg.getRuntime();
            return (RubyModule) get_interface_module(
                    runtime.getJavaSupport().getJavaUtilitiesModule(),
                    JavaClass.forNameVerbose(runtime, packageName.asJavaString() + name));
        }
    };
    private static final Map<String, Boolean> JAVA_PRIMITIVES = new HashMap<String, Boolean>();
   

    static {
        String[] primitives = {"boolean", "byte", "char", "short", "int", "long", "float", "double"};
        for (String primitive : primitives) {
            JAVA_PRIMITIVES.put(primitive, Boolean.TRUE);
        }
    }

    public static IRubyObject is_primitive_type(IRubyObject recv, IRubyObject sym) {
        return recv.getRuntime().newBoolean(JAVA_PRIMITIVES.containsKey(sym.asJavaString()));
    }

    public static IRubyObject create_proxy_class(
            IRubyObject recv,
            IRubyObject constant,
            IRubyObject javaClass,
            IRubyObject module) {
        if (!(module instanceof RubyModule)) {
            throw recv.getRuntime().newTypeError(module, recv.getRuntime().getModule());
        }
        return ((RubyModule) module).const_set(constant, get_proxy_class(recv, javaClass));
    }

    public static IRubyObject get_java_class(IRubyObject recv, IRubyObject name) {
        try {
            return JavaClass.for_name(recv, name);
        } catch (Exception e) {
            return recv.getRuntime().getNil();
        }
    }

    /**
     * Returns a new proxy instance of type (RubyClass)recv for the wrapped java_object,
     * or the cached proxy if we've already seen this object.
     *
     * @param recv the class for this object
     * @param java_object the java object wrapped in a JavaObject wrapper
     * @return the new or cached proxy for the specified Java object
     */
    public static IRubyObject new_instance_for(IRubyObject recv, IRubyObject java_object) {
        // FIXME: note temporary double-allocation of JavaObject as we move to cleaner interface
        if (java_object instanceof JavaObject) {
            return getInstance(((JavaObject) java_object).getValue(), (RubyClass) recv);
        }
        // in theory we should never get here, keeping around temporarily
        IRubyObject new_instance = ((RubyClass) recv).allocate();
        new_instance.getInstanceVariables().fastSetInstanceVariable("@java_object", java_object);
        new_instance.dataWrapStruct(java_object);
        return new_instance;
    }

    /**
     * Returns a new proxy instance of type clazz for rawJavaObject, or the cached
     * proxy if we've already seen this object.
     *
     * @param rawJavaObject
     * @param clazz
     * @return the new or cached proxy for the specified Java object
     */
    public static IRubyObject getInstance(Object rawJavaObject, RubyClass clazz) {
        return clazz.getRuntime().getJavaSupport().getObjectProxyCache().getOrCreate(rawJavaObject, clazz);
    }

    /**
     * Returns a new proxy instance of a type corresponding to rawJavaObject's class,
     * or the cached proxy if we've already seen this object.  Note that primitives
     * and strings are <em>not</em> coerced to corresponding Ruby types; use
     * JavaUtil.convertJavaToUsableRubyObject to get coerced types or proxies as
     * appropriate.
     *
     * @param runtime
     * @param rawJavaObject
     * @return the new or cached proxy for the specified Java object
     * @see JavaUtil.convertJavaToUsableRubyObject
     */
    public static IRubyObject getInstance(Ruby runtime, Object rawJavaObject) {
        if (rawJavaObject != null) {
            return runtime.getJavaSupport().getObjectProxyCache().getOrCreate(rawJavaObject,
                    (RubyClass) getProxyClass(runtime, JavaClass.get(runtime, rawJavaObject.getClass())));
        }
        return runtime.getNil();
    }

    // If the proxy class itself is passed as a parameter this will be called by Java#ruby_to_java   
    public static IRubyObject to_java_object(IRubyObject recv) {
        return recv.getInstanceVariables().fastGetInstanceVariable("@java_class");
    }

    // JavaUtilities
    /**
     * Add a new proxy extender. This is used by JavaUtilities to allow adding methods
     * to a given type's proxy and all types descending from that proxy's Java class.
     */
    @Deprecated
    public static IRubyObject add_proxy_extender(IRubyObject recv, IRubyObject extender) {
        // hacky workaround in case any users call this directly.
        // most will have called JavaUtilities.extend_proxy instead.
        recv.getRuntime().getWarnings().warn(ID.DEPRECATED_METHOD, "JavaUtilities.add_proxy_extender is deprecated - use JavaUtilities.extend_proxy instead", "add_proxy_extender", "JavaUtilities.extend_proxy");
        IRubyObject javaClassVar = extender.getInstanceVariables().fastGetInstanceVariable("@java_class");
        if (!(javaClassVar instanceof JavaClass)) {
            throw recv.getRuntime().newArgumentError("extender does not have a valid @java_class");
        }
        ((JavaClass) javaClassVar).addProxyExtender(extender);
        return recv.getRuntime().getNil();
    }

    public static RubyModule getInterfaceModule(Ruby runtime, JavaClass javaClass) {
        if (!javaClass.javaClass().isInterface()) {
            throw runtime.newArgumentError(javaClass.toString() + " is not an interface");
        }
        RubyModule interfaceModule;
        if ((interfaceModule = javaClass.getProxyModule()) != null) {
            return interfaceModule;
        }
        javaClass.lockProxy();
        try {
            if ((interfaceModule = javaClass.getProxyModule()) == null) {
                interfaceModule = (RubyModule) runtime.getJavaSupport().getJavaInterfaceTemplate().dup();
                interfaceModule.fastSetInstanceVariable("@java_class", javaClass);
                addToJavaPackageModule(interfaceModule, javaClass);
                javaClass.setupInterfaceModule(interfaceModule);
                // include any interfaces we extend
                Class<?>[] extended = javaClass.javaClass().getInterfaces();
                for (int i = extended.length; --i >= 0;) {
                    JavaClass extendedClass = JavaClass.get(runtime, extended[i]);
                    RubyModule extModule = getInterfaceModule(runtime, extendedClass);
                    interfaceModule.includeModule(extModule);
                }
            }
        } finally {
            javaClass.unlockProxy();
        }
        return interfaceModule;
    }

    public static IRubyObject get_interface_module(IRubyObject recv, IRubyObject javaClassObject) {
        Ruby runtime = recv.getRuntime();
        JavaClass javaClass;
        if (javaClassObject instanceof RubyString) {
            javaClass = JavaClass.for_name(recv, javaClassObject);
        } else if (javaClassObject instanceof JavaClass) {
            javaClass = (JavaClass) javaClassObject;
        } else {
            throw runtime.newArgumentError("expected JavaClass, got " + javaClassObject);
        }
        return getInterfaceModule(runtime, javaClass);
    }

    // Note: this isn't really all that deprecated, as it is used for
    // internal purposes, at least for now. But users should be discouraged
    // from calling this directly; eventually it will go away.
    public static IRubyObject get_deprecated_interface_proxy(ThreadContext context, IRubyObject recv, IRubyObject javaClassObject) {
        Ruby runtime = context.getRuntime();
        JavaClass javaClass;
        if (javaClassObject instanceof RubyString) {
            javaClass = JavaClass.for_name(recv, javaClassObject);
        } else if (javaClassObject instanceof JavaClass) {
            javaClass = (JavaClass) javaClassObject;
        } else {
            throw runtime.newArgumentError("expected JavaClass, got " + javaClassObject);
        }
        if (!javaClass.javaClass().isInterface()) {
            throw runtime.newArgumentError("expected Java interface class, got " + javaClassObject);
        }
        RubyClass proxyClass;
        if ((proxyClass = javaClass.getProxyClass()) != null) {
            return proxyClass;
        }
        javaClass.lockProxy();
        try {
            if ((proxyClass = javaClass.getProxyClass()) == null) {
                RubyModule interfaceModule = getInterfaceModule(runtime, javaClass);
                RubyClass interfaceJavaProxy = runtime.fastGetClass("InterfaceJavaProxy");
                proxyClass = RubyClass.newClass(runtime, interfaceJavaProxy);
                proxyClass.setAllocator(interfaceJavaProxy.getAllocator());
                proxyClass.makeMetaClass(interfaceJavaProxy.getMetaClass());
                // parent.setConstant(name, proxyClass); // where the name should come from ?
                proxyClass.inherit(interfaceJavaProxy);
                proxyClass.callMethod(context, "java_class=", javaClass);
                // including interface module so old-style interface "subclasses" will
                // respond correctly to #kind_of?, etc.
                proxyClass.includeModule(interfaceModule);
                javaClass.setupProxy(proxyClass);
                // add reference to interface module
                if (proxyClass.fastGetConstantAt("Includable") == null) {
                    proxyClass.fastSetConstant("Includable", interfaceModule);
                }

            }
        } finally {
            javaClass.unlockProxy();
        }
        return proxyClass;
    }

    public static RubyModule getProxyClass(Ruby runtime, JavaClass javaClass) {
        RubyClass proxyClass;
        final Class<?> c = javaClass.javaClass();
        if ((proxyClass = javaClass.getProxyClass()) != null) {
            return proxyClass;
        }
        if (c.isInterface()) {
            return getInterfaceModule(runtime, javaClass);
        }
        javaClass.lockProxy();
        try {
            if ((proxyClass = javaClass.getProxyClass()) == null) {

                if (c.isArray()) {
                    proxyClass = createProxyClass(runtime,
                            runtime.getJavaSupport().getArrayProxyClass(),
                            javaClass, true);

                } else if (c.isPrimitive()) {
                    proxyClass = createProxyClass(runtime,
                            runtime.getJavaSupport().getConcreteProxyClass(),
                            javaClass, true);

                } else if (c == Object.class) {
                    // java.lang.Object is added at root of java proxy classes
                    proxyClass = createProxyClass(runtime,
                            runtime.getJavaSupport().getConcreteProxyClass(),
                            javaClass, true);
                    proxyClass.getMetaClass().defineFastMethod("inherited",
                            runtime.getJavaSupport().getConcreteProxyCallback());
                    addToJavaPackageModule(proxyClass, javaClass);

                } else {
                    // other java proxy classes added under their superclass' java proxy
                    proxyClass = createProxyClass(runtime,
                            (RubyClass) getProxyClass(runtime, JavaClass.get(runtime, c.getSuperclass())),
                            javaClass, false);

                    // include interface modules into the proxy class
                    Class<?>[] interfaces = c.getInterfaces();
                    for (int i = interfaces.length; --i >= 0;) {
                        JavaClass ifc = JavaClass.get(runtime, interfaces[i]);
                        proxyClass.includeModule(getInterfaceModule(runtime, ifc));
                    }
                    if (Modifier.isPublic(c.getModifiers())) {
                        addToJavaPackageModule(proxyClass, javaClass);
                    }
                }

                // JRUBY-1000, fail early when attempting to subclass a final Java class;
                // solved here by adding an exception-throwing "inherited"
                if (Modifier.isFinal(c.getModifiers())) {
                    proxyClass.getMetaClass().addMethod("inherited", new org.jruby.internal.runtime.methods.JavaMethod() {
                        @Override
                        public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject[] args, Block block) {
                            throw context.getRuntime().newTypeError("can not extend final Java class: " + c.getCanonicalName());
                        }
                    });
                }
            }
        } finally {
            javaClass.unlockProxy();
        }
        return proxyClass;
    }

    public static IRubyObject get_proxy_class(IRubyObject recv, IRubyObject java_class_object) {
        Ruby runtime = recv.getRuntime();
        JavaClass javaClass;
        if (java_class_object instanceof RubyString) {
            javaClass = JavaClass.for_name(recv, java_class_object);
        } else if (java_class_object instanceof JavaClass) {
            javaClass = (JavaClass) java_class_object;
        } else {
            throw runtime.newTypeError(java_class_object, runtime.getJavaSupport().getJavaClassClass());
        }
        return getProxyClass(runtime, javaClass);
    }

    private static RubyClass createProxyClass(Ruby runtime, RubyClass baseType,
            JavaClass javaClass, boolean invokeInherited) {
  // JRUBY-2938 the proxy class might already exist
  RubyClass proxyClass = javaClass.getProxyClass();
  if (proxyClass != null)
      return proxyClass;

        // this needs to be split, since conditional calling #inherited doesn't fit standard ruby semantics
        RubyClass.checkInheritable(baseType);
        RubyClass superClass = (RubyClass) baseType;
        proxyClass = RubyClass.newClass(runtime, superClass);
        proxyClass.makeMetaClass(superClass.getMetaClass());
        proxyClass.setAllocator(superClass.getAllocator());
        if (invokeInherited) {
            proxyClass.inherit(superClass);
        }
        proxyClass.callMethod(runtime.getCurrentContext(), "java_class=", javaClass);
        javaClass.setupProxy(proxyClass);
        return proxyClass;
    }

    public static IRubyObject concrete_proxy_inherited(IRubyObject recv, IRubyObject subclass) {
        Ruby runtime = recv.getRuntime();
        ThreadContext tc = runtime.getCurrentContext();
        JavaSupport javaSupport = runtime.getJavaSupport();
        RubyClass javaProxyClass = javaSupport.getJavaProxyClass().getMetaClass();
        RuntimeHelpers.invokeAs(tc, javaProxyClass, recv, "inherited", subclass,
                Block.NULL_BLOCK);
        return setupJavaSubclass(tc, subclass, recv.callMethod(tc, "java_class"));
    }

    private static IRubyObject setupJavaSubclass(ThreadContext context, IRubyObject subclass, IRubyObject java_class) {
        Ruby runtime = context.getRuntime();

        if (!(subclass instanceof RubyClass)) {
            throw runtime.newTypeError(subclass, runtime.getClassClass());
        }
        RubyClass rubySubclass = (RubyClass)subclass;
        rubySubclass.getInstanceVariables().fastSetInstanceVariable("@java_proxy_class", runtime.getNil());

        RubyClass subclassSingleton = rubySubclass.getSingletonClass();
        subclassSingleton.addReadWriteAttribute(context, "java_proxy_class");
        subclassSingleton.addMethod("java_interfaces", new JavaMethodZero(subclassSingleton, Visibility.PUBLIC) {
            @Override
            public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name) {
                IRubyObject javaInterfaces = self.getInstanceVariables().fastGetInstanceVariable("@java_interfaces");
                if (javaInterfaces != null) return javaInterfaces.dup();
                return context.getRuntime().getNil();
            }
        });

        rubySubclass.addMethod("__jcreate!", new JavaMethodNoBlock(subclassSingleton, Visibility.PUBLIC) {
            private final Map<Integer, ParameterTypes> methodCache = new HashMap<Integer, ParameterTypes>();
            @Override
            public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject[] args) {
                IRubyObject proxyClass = self.getMetaClass().getInstanceVariables().fastGetInstanceVariable("@java_proxy_class");
                if (proxyClass == null || proxyClass.isNil()) {
                    proxyClass = JavaProxyClass.get_with_class(self, self.getMetaClass());
                    self.getMetaClass().getInstanceVariables().fastSetInstanceVariable("@java_proxy_class", proxyClass);
                }
                JavaProxyClass realProxyClass = (JavaProxyClass)proxyClass;
                RubyArray constructors = realProxyClass.constructors();
                ArrayList<JavaProxyConstructor> forArity = new ArrayList<JavaProxyConstructor>();
                for (int i = 0; i < constructors.size(); i++) {
                    JavaProxyConstructor constructor = (JavaProxyConstructor)constructors.eltInternal(i);
                    if (constructor.getParameterTypes().length == args.length) {
                        forArity.add(constructor);
                    }
                }
                if (forArity.size() == 0) {
                    throw context.getRuntime().newArgumentError("wrong # of arguments for constructor");
                }
                JavaProxyConstructor matching = (JavaProxyConstructor)matchingCallableArityN(
                        self,
                        methodCache,
                        forArity.toArray(new JavaProxyConstructor[forArity.size()]), args, args.length);
                Object[] newArgs = new Object[args.length];
                Class[] parameterTypes = matching.getParameterTypes();
                for (int i = 0; i < args.length; i++) {
                    newArgs[i] = JavaUtil.convertArgumentToType(context, args[i], parameterTypes[i]);
                }
                JavaObject newObject = matching.newInstance(self, newArgs);
                return JavaUtilities.set_java_object(self, self, newObject);
            }
        });

        return runtime.getNil();
    }

    // package scheme 2: separate module for each full package name, constructed
    // from the camel-cased package segments: Java::JavaLang::Object,
    private static void addToJavaPackageModule(RubyModule proxyClass, JavaClass javaClass) {
        Class<?> clazz = javaClass.javaClass();
        String fullName;
        if ((fullName = clazz.getName()) == null) {
            return;
        }
        int endPackage = fullName.lastIndexOf('.');
        // we'll only map conventional class names to modules
        if (fullName.indexOf('$') != -1 || !Character.isUpperCase(fullName.charAt(endPackage + 1))) {
            return;
        }
        Ruby runtime = proxyClass.getRuntime();
        String packageString = endPackage < 0 ? "" : fullName.substring(0, endPackage);
        RubyModule packageModule = getJavaPackageModule(runtime, packageString);
        if (packageModule != null) {
            String className = fullName.substring(endPackage + 1);
            if (packageModule.getConstantAt(className) == null) {
                packageModule.const_set(runtime.newSymbol(className), proxyClass);
            }
        }
    }

    private static RubyModule getJavaPackageModule(Ruby runtime, String packageString) {
        String packageName;
        int length = packageString.length();
        if (length == 0) {
            packageName = "Default";
        } else {
            StringBuilder buf = new StringBuilder();
            for (int start = 0, offset = 0; start < length; start = offset + 1) {
                if ((offset = packageString.indexOf('.', start)) == -1) {
                    offset = length;
                }
                buf.append(Character.toUpperCase(packageString.charAt(start))).append(packageString.substring(start + 1, offset));
            }
            packageName = buf.toString();
        }

        RubyModule javaModule = runtime.getJavaSupport().getJavaModule();
        IRubyObject packageModule = javaModule.getConstantAt(packageName);
        if (packageModule == null) {
            return createPackageModule(javaModule, packageName, packageString);
        } else if (packageModule instanceof RubyModule) {
            return (RubyModule) packageModule;
        } else {
            return null;
        }
    }

    private static RubyModule createPackageModule(RubyModule parent, String name, String packageString) {
        Ruby runtime = parent.getRuntime();
        RubyModule packageModule = (RubyModule) runtime.getJavaSupport().getPackageModuleTemplate().dup();
        packageModule.fastSetInstanceVariable("@package_name", runtime.newString(
                packageString.length() > 0 ? packageString + '.' : packageString));

        // this is where we'll get connected when classes are opened using
        // package module syntax.
        packageModule.addClassProvider(JAVA_PACKAGE_CLASS_PROVIDER);

        parent.const_set(runtime.newSymbol(name), packageModule);
        MetaClass metaClass = (MetaClass) packageModule.getMetaClass();
        metaClass.setAttached(packageModule);
        return packageModule;
    }
    private static final Pattern CAMEL_CASE_PACKAGE_SPLITTER = Pattern.compile("([a-z][0-9]*)([A-Z])");

    public static RubyModule getPackageModule(Ruby runtime, String name) {
        RubyModule javaModule = runtime.getJavaSupport().getJavaModule();
        IRubyObject value;
        if ((value = javaModule.getConstantAt(name)) instanceof RubyModule) {
            return (RubyModule) value;
        }
        String packageName;
        if ("Default".equals(name)) {
            packageName = "";
        } else {
            Matcher m = CAMEL_CASE_PACKAGE_SPLITTER.matcher(name);
            packageName = m.replaceAll("$1.$2").toLowerCase();
        }
        return createPackageModule(javaModule, name, packageName);
    }

    public static IRubyObject get_package_module(IRubyObject recv, IRubyObject symObject) {
        return getPackageModule(recv.getRuntime(), symObject.asJavaString());
    }

    public static IRubyObject get_package_module_dot_format(IRubyObject recv, IRubyObject dottedName) {
        Ruby runtime = recv.getRuntime();
        RubyModule module = getJavaPackageModule(runtime, dottedName.asJavaString());
        return module == null ? runtime.getNil() : module;
    }

    public static RubyModule getProxyOrPackageUnderPackage(ThreadContext context, final Ruby runtime,
            RubyModule parentPackage, String sym) {
        IRubyObject packageNameObj = parentPackage.fastGetInstanceVariable("@package_name");
        if (packageNameObj == null) {
            throw runtime.newArgumentError("invalid package module");
        }
        String packageName = packageNameObj.asJavaString();
        final String name = sym.trim().intern();
        if (name.length() == 0) {
            throw runtime.newArgumentError("empty class or package name");
        }
        String fullName = packageName + name;
        if (Character.isLowerCase(name.charAt(0))) {
            // TODO: should check against all Java reserved names here, not just primitives
            if (JAVA_PRIMITIVES.containsKey(name)) {
                throw runtime.newArgumentError("illegal package name component: " + name);
            // this covers the rare case of lower-case class names (and thus will
            // fail 99.999% of the time). fortunately, we'll only do this once per
            // package name. (and seriously, folks, look into best practices...)
            }
            try {
                return getProxyClass(runtime, JavaClass.forNameQuiet(runtime, fullName));
            } catch (RaiseException re) { /* expected */
                RubyException rubyEx = re.getException();
                if (rubyEx.kind_of_p(context, runtime.getStandardError()).isTrue()) {
                    RuntimeHelpers.setErrorInfo(runtime, runtime.getNil());
                }
            } catch (Exception e) { /* expected */ }

            RubyModule packageModule;
            // TODO: decompose getJavaPackageModule so we don't parse fullName
            if ((packageModule = getJavaPackageModule(runtime, fullName)) == null) {
                return null;
            // save package module as ivar in parent, and add method to parent so
            // we don't have to come back here.
            }
            final String ivarName = ("@__pkg__" + name).intern();
            parentPackage.fastSetInstanceVariable(ivarName, packageModule);
            RubyClass singleton = parentPackage.getSingletonClass();
            singleton.addMethod(name, new org.jruby.internal.runtime.methods.JavaMethod(singleton, Visibility.PUBLIC) {

                public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject[] args, Block block) {
                    if (args.length != 0) {
                        Arity.raiseArgumentError(runtime, args.length, 0, 0);
                    }
                    IRubyObject variable;
                    if ((variable = ((RubyModule) self).fastGetInstanceVariable(ivarName)) != null) {
                        return variable;
                    }
                    return runtime.getNil();
                }

                @Override
                public Arity getArity() {
                    return Arity.noArguments();
                }
            });
            return packageModule;
        } else {
            // upper case name, so most likely a class
            return getProxyClass(runtime, JavaClass.forNameVerbose(runtime, fullName));

        // FIXME: we should also support orgs that use capitalized package
        // names (including, embarrassingly, the one I work for), but this
        // should be enabled by a system property, as the expected default
        // behavior for an upper-case value should be (and is) to treat it
        // as a class name, and raise an exception if it's not found

//            try {
//                return getProxyClass(runtime, JavaClass.forName(runtime, fullName));
//            } catch (Exception e) {
//                // but for those not hip to conventions and best practices,
//                // we'll try as a package
//                return getJavaPackageModule(runtime, fullName);
//            }
        }
    }

    public static IRubyObject get_proxy_or_package_under_package(
            ThreadContext context,
            IRubyObject recv,
            IRubyObject parentPackage,
            IRubyObject sym) {
        Ruby runtime = recv.getRuntime();
        if (!(parentPackage instanceof RubyModule)) {
            throw runtime.newTypeError(parentPackage, runtime.getModule());
        }
        RubyModule result;
        if ((result = getProxyOrPackageUnderPackage(context, runtime,
                (RubyModule) parentPackage, sym.asJavaString())) != null) {
            return result;
        }
        return runtime.getNil();
    }

    public static RubyModule getTopLevelProxyOrPackage(ThreadContext context, final Ruby runtime, String sym) {
        final String name = sym.trim().intern();
        if (name.length() == 0) {
            throw runtime.newArgumentError("empty class or package name");
        }
        if (Character.isLowerCase(name.charAt(0))) {
            // this covers primitives and (unlikely) lower-case class names
            try {
                return getProxyClass(runtime, JavaClass.forNameQuiet(runtime, name));
            } catch (RaiseException re) { /* not primitive or lc class */
                RubyException rubyEx = re.getException();
                if (rubyEx.kind_of_p(context, runtime.getStandardError()).isTrue()) {
                    RuntimeHelpers.setErrorInfo(runtime, runtime.getNil());
                }
            } catch (Exception e) { /* not primitive or lc class */ }

            // TODO: check for Java reserved names and raise exception if encountered

            RubyModule packageModule;
            // TODO: decompose getJavaPackageModule so we don't parse fullName
            if ((packageModule = getJavaPackageModule(runtime, name)) == null) {
                return null;
            }
            RubyModule javaModule = runtime.getJavaSupport().getJavaModule();
            if (javaModule.getMetaClass().isMethodBound(name, false)) {
                return packageModule;
            // save package module as ivar in parent, and add method to parent so
            // we don't have to come back here.
            }
            final String ivarName = ("@__pkg__" + name).intern();
            javaModule.fastSetInstanceVariable(ivarName, packageModule);
            RubyClass singleton = javaModule.getSingletonClass();
            singleton.addMethod(name, new org.jruby.internal.runtime.methods.JavaMethod(singleton, Visibility.PUBLIC) {

                public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject[] args, Block block) {
                    if (args.length != 0) {
                        Arity.raiseArgumentError(runtime, args.length, 0, 0);
                    }
                    IRubyObject variable;
                    if ((variable = ((RubyModule) self).fastGetInstanceVariable(ivarName)) != null) {
                        return variable;
                    }
                    return runtime.getNil();
                }

                @Override
                public Arity getArity() {
                    return Arity.noArguments();
                }
            });
            return packageModule;
        } else {
            try {
                return getProxyClass(runtime, JavaClass.forNameQuiet(runtime, name));
            } catch (RaiseException re) { /* not a class */
                RubyException rubyEx = re.getException();
                if (rubyEx.kind_of_p(context, runtime.getStandardError()).isTrue()) {
                    RuntimeHelpers.setErrorInfo(runtime, runtime.getNil());
                }
            } catch (Exception e) { /* not a class */ }

            // upper-case package name
            // TODO: top-level upper-case package was supported in the previous (Ruby-based)
            // implementation, so leaving as is.  see note at #getProxyOrPackageUnderPackage
            // re: future approach below the top-level.
            return getPackageModule(runtime, name);
        }
    }

    public static IRubyObject get_top_level_proxy_or_package(ThreadContext context, IRubyObject recv, IRubyObject sym) {
        Ruby runtime = context.getRuntime();
        RubyModule result = getTopLevelProxyOrPackage(context, runtime, sym.asJavaString());

        return result != null ? result : runtime.getNil();
    }

    public static IRubyObject matching_method(IRubyObject recv, IRubyObject methods, IRubyObject args) {
        Map matchCache = recv.getRuntime().getJavaSupport().getMatchCache();

        List<Class<?>> arg_types = new ArrayList<Class<?>>();
        int alen = ((RubyArray) args).getLength();
        IRubyObject[] aargs = ((RubyArray) args).toJavaArrayMaybeUnsafe();
        for (int i = 0; i < alen; i++) {
            if (aargs[i] instanceof JavaObject) {
                arg_types.add(((JavaClass) ((JavaObject) aargs[i]).java_class()).javaClass());
            } else {
                arg_types.add(aargs[i].getClass());
            }
        }

        Map ms = (Map) matchCache.get(methods);
        if (ms == null) {
            ms = new HashMap();
            matchCache.put(methods, ms);
        } else {
            IRubyObject method = (IRubyObject) ms.get(arg_types);
            if (method != null) {
                return method;
            }
        }

        int mlen = ((RubyArray) methods).getLength();
        IRubyObject[] margs = ((RubyArray) methods).toJavaArrayMaybeUnsafe();

        for (int i = 0; i < 2; i++) {
            for (int k = 0; k < mlen; k++) {
                IRubyObject method = margs[k];
                List<Class<?>> types = Arrays.asList(((ParameterTypes) method).getParameterTypes());

                // Compatible (by inheritance)
                if (arg_types.size() == types.size()) {
                    // Exact match
                    if (types.equals(arg_types)) {
                        ms.put(arg_types, method);
                        return method;
                    }

                    boolean match = true;
                    for (int j = 0; j < types.size(); j++) {
                        if (!(JavaClass.assignable(types.get(j), arg_types.get(j)) &&
                                (i > 0 || primitive_match(types.get(j), arg_types.get(j)))) && !JavaUtil.isDuckTypeConvertable(arg_types.get(j), types.get(j))) {
                            match = false;
                            break;
                        }
                    }
                    if (match) {
                        ms.put(arg_types, method);
                        return method;
                    }
                } // Could check for varargs here?

            }
        }

        throw argumentError(recv.getRuntime().getCurrentContext(), margs[0], recv, arg_types);
    }
   
    public static int argsHashCode(Object[] a) {
        if (a == null)
            return 0;
        int result = 1;
        for (Object element : a)
            result = 31 * result + (element == null ? 0 : element.getClass().hashCode());
        return result;
    }
   
    public static int argsHashCode(Class[] a) {
        if (a == null)
            return 0;
        int result = 1;
        for (Class element : a)
            result = 31 * result + (element == null ? 0 : element.hashCode());
        return result;
    }
   
    public static int argsHashCode(IRubyObject a0) {
        return 31 + classHashCode(a0);
    }
   
    public static int argsHashCode(IRubyObject a0, IRubyObject a1) {
        return 31 * (31 + classHashCode(a0)) + classHashCode(a1);
    }
   
    public static int argsHashCode(IRubyObject a0, IRubyObject a1, IRubyObject a2) {
        return 31 * (31 * (31 + classHashCode(a0)) + classHashCode(a1)) + classHashCode(a2);
    }
   
    public static int argsHashCode(IRubyObject a0, IRubyObject a1, IRubyObject a2, IRubyObject a3) {
        return 31 * (31 * (31 * (31 + classHashCode(a0)) + classHashCode(a1)) + classHashCode(a2)) + classHashCode(a3);
    }
   
    private static int classHashCode(IRubyObject o) {
        return o == null ? 0 : o.getJavaClass().hashCode();
    }
   
    public static int argsHashCode(IRubyObject[] a) {
        if (a == null)
            return 0;
        int result = 1;
        for (IRubyObject element : a)
            result = 31 * result + classHashCode(element);
        return result;
    }
   
    public static Class argClass(Object a) {
        if (a == null) return void.class;


        return a.getClass();
    }

    public static Class argClass(IRubyObject a) {
        if (a == null) return void.class;

        return a.getJavaClass();
    }

    public static JavaCallable matching_method_internal(IRubyObject recv, Map cache, JavaCallable[] methods, Object[] args, int len) {
        int signatureCode = argsHashCode(args);
        JavaCallable method = (JavaCallable)cache.get(signatureCode);
        if (method != null) {
            return method;
        }

        int mlen = methods.length;

        mfor:
        for (int k = 0; k < mlen; k++) {
            method = methods[k];
            Class<?>[] types = ((ParameterTypes) method).getParameterTypes();
            // Compatible (by inheritance)
            if (len == types.length) {
                // Exact match
                boolean same = true;
                for (int x = 0, y = len; x < y; x++) {
                    if (!types[x].equals(argClass(args[x]))) {
                        same = false;
                        break;
                    }
                }
                if (same) {
                    cache.put(signatureCode, method);
                    return method;
                }

                for (int j = 0, m = len; j < m; j++) {
                    if (!(JavaClass.assignable(types[j], argClass(args[j])) &&
                            primitive_match(types[j], argClass(args[j])))) {
                        continue mfor;
                    }
                }
                cache.put(signatureCode, method);
                return method;
            }
        }

        mfor:
        for (int k = 0; k < mlen; k++) {
            method = methods[k];
            Class<?>[] types = ((ParameterTypes) method).getParameterTypes();
            // Compatible (by inheritance)
            if (len == types.length) {
                for (int j = 0, m = len; j < m; j++) {
                    if (!JavaClass.assignable(types[j], argClass(args[j])) && !JavaUtil.isDuckTypeConvertable(argClass(args[j]), types[j])) {
                        continue mfor;
                    }
                }
                cache.put(signatureCode, method);
                return method;
            }
        }

        throw argTypesDoNotMatch(recv.getRuntime(), recv, methods, args);
    }

    // A version that just requires ParameterTypes; they will all move toward
    // something similar soon
    public static ParameterTypes matchingCallableArityN(IRubyObject recv, Map cache, ParameterTypes[] methods, IRubyObject[] args, int argsLength) {
        int signatureCode = argsHashCode(args);
        ParameterTypes method = (ParameterTypes)cache.get(signatureCode);
        if (method == null) method = findMatchingCallableForArgs(recv, cache, signatureCode, methods, args);
        return method;
    }
    private static ParameterTypes findMatchingCallableForArgs(IRubyObject recv, Map cache, int signatureCode, ParameterTypes[] methods, IRubyObject... args) {
        ParameterTypes method = findCallable(methods, Exact, args);
        if (method == null) method = findCallable(methods, AssignableAndPrimitivable, args);
        if (method == null) method = findCallable(methods, AssignableOrDuckable, args);
        if (method == null) {
            throw argTypesDoNotMatch(recv.getRuntime(), recv, methods, (Object[]) args);
        } else {
            cache.put(signatureCode, method);
            return method;
        }
    }
    private static ParameterTypes findCallable(ParameterTypes[] callables, CallableAcceptor acceptor, IRubyObject... args) {
        ParameterTypes bestCallable = null;
        int bestScore = -1;
        for (int k = 0; k < callables.length; k++) {
            ParameterTypes callable = callables[k];
            Class<?>[] types = callable.getParameterTypes();

            if (acceptor.accept(types, args)) {
                int currentScore = getExactnessScore(types, args);
                if (currentScore > bestScore) {
                    bestCallable = callable;
                    bestScore = currentScore;
                }
            }
        }
        return bestCallable;
    }
   
    private static int getExactnessScore(Class<?>[] types, IRubyObject[] args) {
        int count = 0;
        for (int i = 0; i < args.length; i++) {
            if (types[i].equals(argClass(args[i]))) count++;
        }
        return count;
    }

   
    // NOTE: The five match methods are arity-split to avoid the cost of boxing arguments
    // when there's already a cached match. Do not condense them into a single
    // method.
    public static JavaCallable matchingCallableArityN(IRubyObject recv, Map cache, JavaCallable[] methods, IRubyObject[] args, int argsLength) {
        int signatureCode = argsHashCode(args);
        JavaCallable method = (JavaCallable)cache.get(signatureCode);
        if (method == null) method = (JavaCallable)findMatchingCallableForArgs(recv, cache, signatureCode, methods, args);
        return method;
    }
   
    public static JavaCallable matchingCallableArityOne(IRubyObject recv, Map cache, JavaCallable[] methods, IRubyObject arg0) {
        int signatureCode = argsHashCode(arg0);
        JavaCallable method = (JavaCallable)cache.get(signatureCode);
        if (method == null) method = (JavaCallable)findMatchingCallableForArgs(recv, cache, signatureCode, methods, arg0);
        return method;
    }
   
    public static JavaCallable matchingCallableArityTwo(IRubyObject recv, Map cache, JavaCallable[] methods, IRubyObject arg0, IRubyObject arg1) {
        int signatureCode = argsHashCode(arg0, arg1);
        JavaCallable method = (JavaCallable)cache.get(signatureCode);
        if (method == null) method = (JavaCallable)findMatchingCallableForArgs(recv, cache, signatureCode, methods, arg0, arg1);
        return method;
    }
   
    public static JavaCallable matchingCallableArityThree(IRubyObject recv, Map cache, JavaCallable[] methods, IRubyObject arg0, IRubyObject arg1, IRubyObject arg2) {
        int signatureCode = argsHashCode(arg0, arg1, arg2);
        JavaCallable method = (JavaCallable)cache.get(signatureCode);
        if (method == null) method = (JavaCallable)findMatchingCallableForArgs(recv, cache, signatureCode, methods, arg0, arg1, arg2);
        return method;
    }
   
    public static JavaCallable matchingCallableArityFour(IRubyObject recv, Map cache, JavaCallable[] methods, IRubyObject arg0, IRubyObject arg1, IRubyObject arg2, IRubyObject arg3) {
        int signatureCode = argsHashCode(arg0, arg1, arg2, arg3);
        JavaCallable method = (JavaCallable)cache.get(signatureCode);
        if (method == null) method = (JavaCallable)findMatchingCallableForArgs(recv, cache, signatureCode, methods, arg0, arg1, arg2, arg3);
        return method;
    }
   
    private static interface CallableAcceptor {
        public boolean accept(Class<?>[] types, IRubyObject[] args);
    }
   
    private static final CallableAcceptor Exact = new CallableAcceptor() {
        public boolean accept(Class<?>[] types, IRubyObject[] args) {
            return exactMatch(types, args);
        }
    };
   
    private static final CallableAcceptor AssignableAndPrimitivable = new CallableAcceptor() {
        public boolean accept(Class<?>[] types, IRubyObject[] args) {
            return assignableAndPrimitivable(types, args);
        }
    };
   
    private static final CallableAcceptor AssignableOrDuckable = new CallableAcceptor() {
        public boolean accept(Class<?>[] types, IRubyObject[] args) {
            return assignableOrDuckable(types, args);
        }
    };
   
    private static boolean exactMatch(Class[] types, IRubyObject... args) {
        for (int i = 0; i < types.length; i++) {
            if (!types[i].equals(argClass(args[i]))) return false;
        }
        return true;
    }
   
    private static boolean assignableAndPrimitivable(Class[] types, IRubyObject... args) {
        for (int i = 0; i < types.length; i++) {
            if (!(assignable(types[i], args[i]) && primitivable(types[i], args[i]))) return false;
        }
        return true;
    }
   
    private static boolean assignableOrDuckable(Class[] types, IRubyObject... args) {
        for (int i = 0; i < types.length; i++) {
            if (!(assignable(types[i], args[i]) || duckable(types[i], args[i]))) return false;
        }
        return true;
    }
   
    private static boolean assignable(Class type, IRubyObject arg) {
        return JavaClass.assignable(type, argClass(arg));
    }
   
    /**
     * This method checks whether an argument can be *directly* converted into
     * the target primitive, i.e. without changing from integral to floating-point.
     *
     * @param type The target type
     * @param arg The argument to convert
     * @return Whether the argument can be directly converted to the target primitive type
     */
    private static boolean primitivable(Class type, IRubyObject arg) {
        Class argClass = argClass(arg);
        if (type.isPrimitive()) {
            // TODO: This is where we would want to do precision checks to see
            // if it's non-destructive to coerce a given type into the target
            // integral primitive
            if (type == Integer.TYPE || type == Long.TYPE || type == Short.TYPE || type == Character.TYPE) {
                return argClass == long.class || // long first because it's what Fixnum claims to be
                        argClass == byte.class ||
                        argClass == short.class ||
                        argClass == char.class ||
                        argClass == int.class ||
                        argClass == Long.class ||
                        argClass == Byte.class ||
                        argClass == Short.class ||
                        argClass == Character.class ||
                        argClass == Integer.class;
            } else if (type == Float.TYPE || type == Double.TYPE) {
                return argClass == double.class || // double first because it's what float claims to be
                        argClass == float.class ||
                        argClass == Float.class ||
                        argClass == Double.class;
            } else if (type == Boolean.TYPE) {
                return argClass == boolean.class ||
                        argClass == Boolean.class;
            }
        }
        return false;
    }
   
    private static boolean duckable(Class type, IRubyObject arg) {
        return JavaUtil.isDuckTypeConvertable(argClass(arg), type);
    }
   
    private static RaiseException argTypesDoNotMatch(Ruby runtime, IRubyObject receiver, Object[] methods, Object... args) {
        ArrayList<Class<?>> argTypes = new ArrayList<Class<?>>(args.length);
       
        for (Object o : args) argTypes.add(argClassTypeError(o));

        return argumentError(runtime.getCurrentContext(), methods[0], receiver, argTypes);
    }

    private static Class argClassTypeError(Object object) {
        if (object == null) return void.class;
        if (object instanceof ConcreteJavaProxy) return ((ConcreteJavaProxy) object).getJavaClass();

        return object.getClass();
    }
   
    private static RaiseException argumentError(ThreadContext context, Object method, IRubyObject receiver, List<Class<?>> argTypes) {
        String methodName = (method instanceof JavaConstructor || method instanceof JavaProxyConstructor) ?
            "constructor" : ((JavaMethod) method).name().toString();

        return context.getRuntime().newNameError("no " + methodName + " with arguments matching " +
                argTypes + " on object " + receiver.callMethod(context, "inspect"), null);
    }
   
    public static IRubyObject access(IRubyObject recv, IRubyObject java_type) {
        int modifiers = ((JavaClass) java_type).javaClass().getModifiers();
        return recv.getRuntime().newString(Modifier.isPublic(modifiers) ? "public" : (Modifier.isProtected(modifiers) ? "protected" : "private"));
    }

    public static IRubyObject valid_constant_name_p(IRubyObject recv, IRubyObject name) {
        RubyString sname = name.convertToString();
        if (sname.getByteList().length() == 0) {
            return recv.getRuntime().getFalse();
        }
        return Character.isUpperCase(sname.getByteList().charAt(0)) ? recv.getRuntime().getTrue() : recv.getRuntime().getFalse();
    }

    public static boolean primitive_match(Object v1, Object v2) {
        if (((Class) v1).isPrimitive()) {
            if (v1 == Integer.TYPE || v1 == Long.TYPE || v1 == Short.TYPE || v1 == Character.TYPE) {
                return v2 == Integer.class ||
                        v2 == Long.class ||
                        v2 == Short.class ||
                        v2 == Character.class;
            } else if (v1 == Float.TYPE || v1 == Double.TYPE) {
                return v2 == Float.class ||
                        v2 == Double.class;
            } else if (v1 == Boolean.TYPE) {
                return v2 == Boolean.class;
            }
            return false;
        }
        return true;
    }

    public static IRubyObject primitive_match(IRubyObject recv, IRubyObject t1, IRubyObject t2) {
        if (((JavaClass) t1).primitive_p().isTrue()) {
            Object v1 = ((JavaObject) t1).getValue();
            Object v2 = ((JavaObject) t2).getValue();
            return primitive_match(v1, v2) ? recv.getRuntime().getTrue() : recv.getRuntime().getFalse();
        }
        return recv.getRuntime().getTrue();
    }

    public static IRubyObject wrap(IRubyObject recv, IRubyObject java_object) {
        return getInstance(recv.getRuntime(), ((JavaObject) java_object).getValue());
    }

    public static IRubyObject wrap(Ruby runtime, IRubyObject java_object) {
        return getInstance(runtime, ((JavaObject) java_object).getValue());
    }

    // Java methods
    @JRubyMethod(required = 1, optional = 1, frame = true, module = true, visibility = Visibility.PRIVATE)
    public static IRubyObject define_exception_handler(IRubyObject recv, IRubyObject[] args, Block block) {
        String name = args[0].toString();
        RubyProc handler = null;
        if (args.length > 1) {
            handler = (RubyProc) args[1];
        } else {
            handler = recv.getRuntime().newProc(Block.Type.PROC, block);
        }
        recv.getRuntime().getJavaSupport().defineExceptionHandler(name, handler);

        return recv;
    }

    @JRubyMethod(frame = true, module = true, visibility = Visibility.PRIVATE)
    public static IRubyObject primitive_to_java(IRubyObject recv, IRubyObject object, Block unusedBlock) {
        return JavaUtil.primitive_to_java(recv, object, unusedBlock);
    }

    /**
     * High-level object conversion utility function 'java_to_primitive' is the low-level version
     */
    @JRubyMethod(frame = true, module = true, visibility = Visibility.PRIVATE)
    public static IRubyObject java_to_ruby(IRubyObject recv, IRubyObject object, Block unusedBlock) {
        return JavaUtil.java_to_ruby(recv.getRuntime(), object);
    }

    // TODO: Formalize conversion mechanisms between Java and Ruby
    /**
     * High-level object conversion utility.
     */
    @JRubyMethod(frame = true, module = true, visibility = Visibility.PRIVATE)
    public static IRubyObject ruby_to_java(final IRubyObject recv, IRubyObject object, Block unusedBlock) {
        return JavaUtil.ruby_to_java(recv, object, unusedBlock);
    }

    @JRubyMethod(frame = true, module = true, visibility = Visibility.PRIVATE)
    public static IRubyObject java_to_primitive(IRubyObject recv, IRubyObject object, Block unusedBlock) {
        return JavaUtil.java_to_primitive(recv, object, unusedBlock);
    }

    @JRubyMethod(required = 1, rest = true, frame = true, module = true, visibility = Visibility.PRIVATE)
    @Deprecated
    public static IRubyObject new_proxy_instance(final IRubyObject recv, IRubyObject[] args, Block block) {
        int size = Arity.checkArgumentCount(recv.getRuntime(), args, 1, -1) - 1;
        final RubyProc proc;

        // Is there a supplied proc argument or do we assume a block was supplied
        if (args[size] instanceof RubyProc) {
            proc = (RubyProc) args[size];
        } else {
            proc = recv.getRuntime().newProc(Block.Type.PROC, block);
            size++;
        }

        // Create list of interfaces to proxy (and make sure they really are interfaces)
        Class[] interfaces = new Class[size];
        for (int i = 0; i < size; i++) {
            if (!(args[i] instanceof JavaClass) || !((JavaClass) args[i]).interface_p().isTrue()) {
                throw recv.getRuntime().newArgumentError("Java interface expected. got: " + args[i]);
            }
            interfaces[i] = ((JavaClass) args[i]).javaClass();
        }

        return JavaObject.wrap(recv.getRuntime(), Proxy.newProxyInstance(recv.getRuntime().getJRubyClassLoader(), interfaces, new InvocationHandler() {

            private Map parameterTypeCache = new ConcurrentHashMap();

            public Object invoke(Object proxy, Method method, Object[] nargs) throws Throwable {
                Class[] parameterTypes = (Class[]) parameterTypeCache.get(method);
                if (parameterTypes == null) {
                    parameterTypes = method.getParameterTypes();
                    parameterTypeCache.put(method, parameterTypes);
                }
                int methodArgsLength = parameterTypes.length;
                String methodName = method.getName();

                if (methodName.equals("toString") && methodArgsLength == 0) {
                    return proxy.getClass().getName();
                } else if (methodName.equals("hashCode") && methodArgsLength == 0) {
                    return new Integer(proxy.getClass().hashCode());
                } else if (methodName.equals("equals") && methodArgsLength == 1 && parameterTypes[0].equals(Object.class)) {
                    return Boolean.valueOf(proxy == nargs[0]);
                }
                Ruby runtime = recv.getRuntime();
                int length = nargs == null ? 0 : nargs.length;
                IRubyObject[] rubyArgs = new IRubyObject[length + 2];
                rubyArgs[0] = JavaObject.wrap(runtime, proxy);
                rubyArgs[1] = new JavaMethod(runtime, method);
                for (int i = 0; i < length; i++) {
                    rubyArgs[i + 2] = JavaObject.wrap(runtime, nargs[i]);
                }
                return JavaUtil.convertArgument(runtime, proc.call(runtime.getCurrentContext(), rubyArgs), method.getReturnType());
            }
        }));
    }

    @JRubyMethod(required = 2, frame = true, module = true, visibility = Visibility.PRIVATE)
    public static IRubyObject new_proxy_instance2(IRubyObject recv, final IRubyObject wrapper, IRubyObject ifcs, Block block) {
        IRubyObject[] javaClasses = ((RubyArray)ifcs).toJavaArray();
        final Ruby runtime = recv.getRuntime();

        // Create list of interface names to proxy (and make sure they really are interfaces)
        // Also build a hashcode from all classes to use for retrieving previously-created impl
        Class[] interfaces = new Class[javaClasses.length];
        for (int i = 0; i < javaClasses.length; i++) {
            if (!(javaClasses[i] instanceof JavaClass) || !((JavaClass) javaClasses[i]).interface_p().isTrue()) {
                throw recv.getRuntime().newArgumentError("Java interface expected. got: " + javaClasses[i]);
            }
            interfaces[i] = ((JavaClass) javaClasses[i]).javaClass();
        }

        // hashcode is a combination of the interfaces and the Ruby class we're using
        // to implement them
        if (!Boolean.getBoolean("jruby.interfaces.useProxy")) {
            int interfacesHashCode = argsHashCode(interfaces);
            // if it's a singleton class and the real class is proc, we're doing closure conversion
            // so just use Proc's hashcode
            if (wrapper.getMetaClass().isSingleton() && wrapper.getMetaClass().getRealClass() == runtime.getProc()) {
                interfacesHashCode = 31 * interfacesHashCode + runtime.getProc().hashCode();
            } else {
                // normal new class implementing interfaces
                interfacesHashCode = 31 * interfacesHashCode + wrapper.getMetaClass().hashCode();
            }
            String implClassName = "org.jruby.gen.InterfaceImpl" + Math.abs(interfacesHashCode);
            Class proxyImplClass;
            try {
                proxyImplClass = Class.forName(implClassName, true, runtime.getJRubyClassLoader());
            } catch (ClassNotFoundException cnfe) {
                proxyImplClass = MiniJava.createOldStyleImplClass(interfaces, wrapper.getMetaClass(), runtime, implClassName);
            }

            try {
                Constructor proxyConstructor = proxyImplClass.getConstructor(IRubyObject.class);
                return JavaObject.wrap(recv.getRuntime(), proxyConstructor.newInstance(wrapper));
            } catch (NoSuchMethodException nsme) {
                throw runtime.newTypeError("Exception instantiating generated interface impl:\n" + nsme);
            } catch (InvocationTargetException ite) {
                throw runtime.newTypeError("Exception instantiating generated interface impl:\n" + ite);
            } catch (InstantiationException ie) {
                throw runtime.newTypeError("Exception instantiating generated interface impl:\n" + ie);
            } catch (IllegalAccessException iae) {
                throw runtime.newTypeError("Exception instantiating generated interface impl:\n" + iae);
            }
        } else {
            return JavaObject.wrap(recv.getRuntime(), Proxy.newProxyInstance(recv.getRuntime().getJRubyClassLoader(), interfaces, new InvocationHandler() {
                private Map parameterTypeCache = new ConcurrentHashMap();

                public Object invoke(Object proxy, Method method, Object[] nargs) throws Throwable {
                    String methodName = method.getName();
                    int length = nargs == null ? 0 : nargs.length;

                    // FIXME: wtf is this? Why would these use the class?
                    if (methodName == "toString" && length == 0) {
                        return proxy.getClass().getName();
                    } else if (methodName == "hashCode" && length == 0) {
                        return new Integer(proxy.getClass().hashCode());
                    } else if (methodName == "equals" && length == 1) {
                        Class[] parameterTypes = (Class[]) parameterTypeCache.get(method);
                        if (parameterTypes == null) {
                            parameterTypes = method.getParameterTypes();
                            parameterTypeCache.put(method, parameterTypes);
                        }
                        if (parameterTypes[0].equals(Object.class)) {
                            return Boolean.valueOf(proxy == nargs[0]);
                        }
                    }

                    IRubyObject[] rubyArgs = JavaUtil.convertJavaArrayToRuby(runtime, nargs);
                    try {
                        return JavaUtil.convertRubyToJava(RuntimeHelpers.invoke(runtime.getCurrentContext(), wrapper, methodName, rubyArgs), method.getReturnType());
                    } catch (RuntimeException e) { e.printStackTrace(); throw e; }
                }
            }));
        }
    }
}
TOP

Related Classes of org.jruby.javasupport.Java

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.