All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jruby.javasupport.JavaClass Maven / Gradle / Ivy

/***** BEGIN LICENSE BLOCK *****
 * Version: EPL 2.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Eclipse Public
 * License Version 2.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/epl-v20.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-2004 Anders Bengtsson 
 * Copyright (C) 2002-2004 Jan Arne Petersen 
 * Copyright (C) 2004-2005 Thomas E Enebo 
 * Copyright (C) 2004 Stefan Matthias Aust 
 * Copyright (C) 2004 David Corbin 
 * Copyright (C) 2005 Charles O Nutter 
 * Copyright (C) 2006 Kresten Krab Thorup 
 * Copyright (C) 2007 Miguel Covarrubias 
 * Copyright (C) 2007 William N Dortch 
 * Copyright (C) 2011 David Pollak 
 *
 * 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 EPL, 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 EPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/

package org.jruby.javasupport;

import org.jruby.Ruby;
import org.jruby.RubyArray;
import org.jruby.RubyBoolean;
import org.jruby.RubyClass;
import org.jruby.RubyInteger;
import org.jruby.RubyModule;
import org.jruby.RubyString;
import org.jruby.RubySymbol;
import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyMethod;
import org.jruby.exceptions.RaiseException;
import org.jruby.java.addons.ClassJavaAddons;
import org.jruby.java.proxies.ArrayJavaProxy;
import org.jruby.java.proxies.ConcreteJavaProxy;
import org.jruby.java.proxies.JavaProxy;
import org.jruby.java.util.ArrayUtils;
import org.jruby.runtime.Helpers;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.util.ByteList;
import org.jruby.util.CodegenUtils;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

import static org.jruby.RubyModule.undefinedMethodMessage;
import static org.jruby.util.RubyStringBuilder.ids;

@JRubyClass(name="Java::JavaClass", parent="Java::JavaObject", include = "Comparable")
public class JavaClass extends JavaObject {

    public static final Class[] EMPTY_CLASS_ARRAY = new Class[0];

    public JavaClass(final Ruby runtime, final Class klass) {
        this(runtime, runtime.getJavaSupport().getJavaClassClass(), klass);
    }

    JavaClass(final Ruby runtime, final RubyClass javaClassProxy, final Class klass) {
        super(runtime, javaClassProxy, klass);
    }

    @Override
    public final boolean equals(Object other) {
        if ( this == other ) return true;
        return other instanceof JavaClass && this.getValue() == ((JavaClass) other).getValue();
    }

    @Override
    public final int hashCode() {
        return getValue().hashCode();
    }

    public final RubyModule getProxyModule() {
        return Java.getProxyClass(getRuntime(), javaClass());
    }

    public final RubyClass getProxyClass() {
        return (RubyClass) Java.getProxyClass(getRuntime(), javaClass());
    }

    private IRubyObject addProxyExtender(final ThreadContext context, final IRubyObject extender) {
        if ( ! extender.respondsTo("extend_proxy") ) {
            throw context.runtime.newTypeError("proxy extender must have an extend_proxy method");
        }
        RubyModule proxy = Java.getProxyClass(context.runtime, javaClass());
        return extender.callMethod(context, "extend_proxy", proxy);
    }

    @JRubyMethod(required = 1)
    public IRubyObject extend_proxy(final ThreadContext context, IRubyObject extender) {
        addProxyExtender(context, extender);
        return context.nil;
    }

    public static JavaClass get(final Ruby runtime, final Class klass) {
        return runtime.getJavaSupport().getJavaClassFromCache(klass);
    }

    @Deprecated // only been used package internally - a bit poorly named
    public static RubyArray getRubyArray(Ruby runtime, Class[] classes) {
        return toRubyArray(runtime, classes);
    }

    public static RubyArray toRubyArray(final Ruby runtime, final Class[] classes) {
        IRubyObject[] javaClasses = new IRubyObject[classes.length];
        for ( int i = classes.length; --i >= 0; ) {
            javaClasses[i] = get(runtime, classes[i]);
        }
        return RubyArray.newArrayMayCopy(runtime, javaClasses);
    }

    public static RubyClass createJavaClassClass(final Ruby runtime, final RubyModule Java) {
        return createJavaClassClass(runtime, Java, Java.getClass("JavaObject"));
    }

    static RubyClass createJavaClassClass(final Ruby runtime, final RubyModule Java, final RubyClass JavaObject) {
        // TODO: Determine if a real allocator is needed here. Do people want to extend
        // JavaClass? Do we want them to do that? Can you Class.new(JavaClass)? Should you be able to?
        // NOTE: NOT_ALLOCATABLE_ALLOCATOR is probably OK here, since we don't intend for people to monkey with
        // this type and it can't be marshalled. Confirm. JRUBY-415
        RubyClass JavaClass = Java.defineClassUnder("JavaClass", JavaObject, ObjectAllocator.NOT_ALLOCATABLE_ALLOCATOR);

        JavaClass.includeModule(runtime.getModule("Comparable"));

        JavaClass.defineAnnotatedMethods(JavaClass.class);

        JavaClass.getMetaClass().undefineMethod("new");
        JavaClass.getMetaClass().undefineMethod("allocate");

        return JavaClass;
    }

    public final Class javaClass() {
        return (Class) getValue();
    }

    /**
     * Get the associated JavaClass for a proxy module.
     *
     * The passed module/class is assumed to be a Java proxy module/class!
     * @param context
     * @param proxy
     * @return class
     */
    public static Class getJavaClass(final ThreadContext context, final RubyModule proxy) {
        return ((JavaClass) java_class(context, proxy)).javaClass();
    }

    /**
     * Retieve a JavaClass if the passed module/class is a Java proxy.
     * @param context
     * @param proxy
     * @return class or null if not a Java proxy
     *
     * @note Class objects have a java_class method but they're not considered Java proxies!
     */
    public static Class getJavaClassIfProxy(final ThreadContext context, final RubyModule proxy) {
        JavaClass javaClass = getJavaClassIfProxyImpl(context, proxy);
        return javaClass == null ? null : javaClass.javaClass();
    }

    private static JavaClass getJavaClassIfProxyImpl(final ThreadContext context, final RubyModule proxy) {
        final IRubyObject java_class = java_class(context, proxy);
        return ( java_class instanceof JavaClass ) ? (JavaClass) java_class : null;
    }

    // expected to handle Java proxy (Ruby) sub-classes as well
    public static boolean isProxyType(final ThreadContext context, final RubyModule proxy) {
        return getJavaClassIfProxyImpl(context, proxy) != null;
        //IRubyObject java_class = proxy.getInstanceVariable("@java_class");
        //return (java_class != null && java_class.isTrue()) ||
        //        proxy.respondsTo("java_class"); // not all proxy types have @java_class set
    }

    /**
     * Returns the (reified or proxied) Java class if the passed Ruby module/class has one.
     * @param context
     * @param type
     * @return Java proxy class, Java reified class or nil
     */
    public static IRubyObject java_class(final ThreadContext context, final RubyModule type) {
        IRubyObject java_class = type.getInstanceVariable("@java_class");
        if ( java_class == null ) { // || java_class.isNil()
            if ( type.respondsTo("java_class") ) { // NOTE: quite bad since built-in Ruby classes will return
                // a Ruby Java proxy for java.lang.Class while Java proxies will return a JavaClass instance !
                java_class = Helpers.invoke(context, type, "java_class");
            }
            else java_class = context.nil; // we return != null (just like callMethod would)
        }
        return java_class;
    }

    /*
    public static Class getJavaClass(final ThreadContext context, final RubyModule type) {
        IRubyObject java_class = java_class(context, type);
        if ( java_class == context.nil ) return null;
        return resolveClassType(context, java_class).javaClass();
    } */

    /**
     * Resolves a Java class from a passed type parameter.
     *
     * Uisng the rules accepted by `to_java(type)` in Ruby land.
     * @param context
     * @param type
     * @return resolved type or null if resolution failed
     */
    public static JavaClass resolveType(final ThreadContext context, final IRubyObject type) {
        if (type instanceof RubyString || type instanceof RubySymbol) {
            final Ruby runtime = context.runtime;
            final String className = type.toString();
            JavaClass targetType = runtime.getJavaSupport().getNameClassMap().get(className);
            if ( targetType == null ) targetType = JavaClass.forNameVerbose(runtime, className);
            return targetType;
        }
        return resolveClassType(context, type);
    }

    // this should handle the type returned from Class#java_class
    private static JavaClass resolveClassType(final ThreadContext context, final IRubyObject type) {
        if (type instanceof JavaProxy) { // due Class#java_class wrapping
            final Object wrapped = ((JavaProxy) type).getObject();
            if ( wrapped instanceof Class ) return JavaClass.get(context.runtime, (Class) wrapped);
            return null;
        }
        if (type instanceof JavaClass) {
            return (JavaClass) type;
        }
        if (type instanceof RubyModule) { // assuming a proxy module/class e.g. to_java(java.lang.String)
            return getJavaClassIfProxyImpl(context, (RubyModule) type);
        }
        return null;
    }

    static boolean isPrimitiveName(final String name) {
        return JavaUtil.getPrimitiveClass(name) != null;
    }

    public static JavaClass forNameVerbose(Ruby runtime, String className) {
        Class klass = null; // "boolean".length() == 7
        if (className.length() < 8 && Character.isLowerCase(className.charAt(0))) {
            // one word type name that starts lower-case...it may be a primitive type
            klass = JavaUtil.getPrimitiveClass(className);
        }
        synchronized (JavaClass.class) {
            if (klass == null) {
                klass = runtime.getJavaSupport().loadJavaClassVerbose(className);
            }
            return JavaClass.get(runtime, klass);
        }
    }

    public static JavaClass forNameQuiet(Ruby runtime, String className) {
        synchronized (JavaClass.class) {
            Class klass = runtime.getJavaSupport().loadJavaClassQuiet(className);
            return JavaClass.get(runtime, klass);
        }
    }

    @JRubyMethod(name = "for_name", required = 1, meta = true)
    public static JavaClass for_name(IRubyObject recv, IRubyObject name) {
        return for_name(recv, name.asJavaString());
    }

    static JavaClass for_name(IRubyObject recv, String name) {
        return forNameVerbose(recv.getRuntime(), name);
    }

    @JRubyMethod
    public RubyModule ruby_class() {
        // Java.getProxyClass deals with sync issues, so we won't duplicate the logic here
        return Java.getProxyClass(getRuntime(), javaClass());
    }

    @JRubyMethod(name = "public?")
    public RubyBoolean public_p() {
        return getRuntime().newBoolean(Modifier.isPublic(javaClass().getModifiers()));
    }

    @JRubyMethod(name = "protected?")
    public RubyBoolean protected_p() {
        return getRuntime().newBoolean(Modifier.isProtected(javaClass().getModifiers()));
    }

    @JRubyMethod(name = "private?")
    public RubyBoolean private_p() {
        return getRuntime().newBoolean(Modifier.isPrivate(javaClass().getModifiers()));
    }

    @JRubyMethod(name = "final?")
    public RubyBoolean final_p() {
        return getRuntime().newBoolean(Modifier.isFinal(javaClass().getModifiers()));
    }

    @JRubyMethod(name = "interface?")
    public RubyBoolean interface_p() {
        return getRuntime().newBoolean(javaClass().isInterface());
    }

    @JRubyMethod(name = "array?")
    public RubyBoolean array_p() {
        return getRuntime().newBoolean(javaClass().isArray());
    }

    @JRubyMethod(name = "enum?")
    public RubyBoolean enum_p() {
        return getRuntime().newBoolean(javaClass().isEnum());
    }

    @JRubyMethod(name = "annotation?")
    public RubyBoolean annotation_p() {
        return getRuntime().newBoolean(javaClass().isAnnotation());
    }

    @JRubyMethod(name = "anonymous_class?")
    public RubyBoolean anonymous_class_p() {
        return getRuntime().newBoolean(javaClass().isAnonymousClass());
    }

    @JRubyMethod(name = "local_class?")
    public RubyBoolean local_class_p() {
        return getRuntime().newBoolean(javaClass().isLocalClass());
    }

    @JRubyMethod(name = "member_class?")
    public RubyBoolean member_class_p() {
        return getRuntime().newBoolean(javaClass().isMemberClass());
    }

    @JRubyMethod(name = "synthetic?")
    public IRubyObject synthetic_p() {
        return getRuntime().newBoolean(javaClass().isSynthetic());
    }

    @JRubyMethod(name = {"name", "to_s"})
    public RubyString name() {
        return getRuntime().newString(javaClass().getName());
    }

    @Override
    @JRubyMethod
    public RubyString inspect() {
        return getRuntime().newString("class " + javaClass().getName());
    }

    @JRubyMethod
    public IRubyObject canonical_name() {
        String canonicalName = javaClass().getCanonicalName();
        if (canonicalName != null) {
            return getRuntime().newString(canonicalName);
        }
        return getRuntime().getNil();
    }

    @JRubyMethod(name = "package")
    public IRubyObject get_package() {
        return Java.getInstance(getRuntime(), javaClass().getPackage());
    }

    @JRubyMethod
    public IRubyObject class_loader() {
        return Java.getInstance(getRuntime(), javaClass().getClassLoader());
    }

    @JRubyMethod
    public IRubyObject protection_domain() {
        return Java.getInstance(getRuntime(), javaClass().getProtectionDomain());
    }

    @JRubyMethod(required = 1)
    public IRubyObject resource(IRubyObject name) {
        return Java.getInstance(getRuntime(), javaClass().getResource(name.asJavaString()));
    }

    @JRubyMethod(required = 1)
    public IRubyObject resource_as_stream(IRubyObject name) {
        return Java.getInstance(getRuntime(), javaClass().getResourceAsStream(name.asJavaString()));
    }

    @JRubyMethod(required = 1)
    public IRubyObject resource_as_string(IRubyObject name) {
        InputStream in = javaClass().getResourceAsStream(name.asJavaString());
        if (in == null) return getRuntime().getNil();
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        try {
            int len;
            byte[] buf = new byte[4096];
            while ((len = in.read(buf)) >= 0) {
                out.write(buf, 0, len);
            }
        } catch (IOException e) {
            throw getRuntime().newIOErrorFromException(e);
        } finally {
            try {in.close();} catch (IOException ioe) {}
        }
        return getRuntime().newString(new ByteList(out.toByteArray(), false));
    }

    @SuppressWarnings("unchecked")
    @JRubyMethod(required = 1)
    public IRubyObject annotation(final IRubyObject annoClass) {
        final Ruby runtime = getRuntime();
        if ( ! ( annoClass instanceof JavaClass ) ) {
            throw runtime.newTypeError(annoClass, runtime.getJavaSupport().getJavaClassClass());
        }
        final Class annotation = ((JavaClass) annoClass).javaClass();
        return Java.getInstance(runtime, javaClass().getAnnotation(annotation));
    }

    @JRubyMethod
    public IRubyObject annotations() {
        // note: intentionally returning the actual array returned from Java, rather
        // than wrapping it in a RubyArray. wave of the future, when java_class will
        // return the actual class, rather than a JavaClass wrapper.
        return Java.getInstance(getRuntime(), javaClass().getAnnotations());
    }

    @JRubyMethod(name = "annotations?")
    public RubyBoolean annotations_p() {
        return getRuntime().newBoolean(javaClass().getAnnotations().length > 0);
    }

    @JRubyMethod
    public IRubyObject declared_annotations() {
        // see note above re: return type
        return Java.getInstance(getRuntime(), javaClass().getDeclaredAnnotations());
    }

    @JRubyMethod(name = "declared_annotations?")
    public RubyBoolean declared_annotations_p() {
        return getRuntime().newBoolean(javaClass().getDeclaredAnnotations().length > 0);
    }

    @SuppressWarnings("unchecked")
    @JRubyMethod(name = "annotation_present?", required = 1)
    public IRubyObject annotation_present_p(final IRubyObject annoClass) {
        final Ruby runtime = getRuntime();
        if ( ! ( annoClass instanceof JavaClass ) ) {
            throw runtime.newTypeError(annoClass, runtime.getJavaSupport().getJavaClassClass());
        }
        final Class annotation = ((JavaClass) annoClass).javaClass();
        return runtime.newBoolean( javaClass().isAnnotationPresent(annotation) );
    }

    @JRubyMethod
    public IRubyObject modifiers() {
        return getRuntime().newFixnum(javaClass().getModifiers());
    }

    @JRubyMethod
    public IRubyObject declaring_class() {
        Class clazz = javaClass().getDeclaringClass();
        if (clazz != null) {
            return JavaClass.get(getRuntime(), clazz);
        }
        return getRuntime().getNil();
    }

    @JRubyMethod
    public IRubyObject enclosing_class() {
        return Java.getInstance(getRuntime(), javaClass().getEnclosingClass());
    }

    @JRubyMethod
    public IRubyObject enclosing_constructor() {
        Constructor ctor = javaClass().getEnclosingConstructor();
        if (ctor != null) {
            return new JavaConstructor(getRuntime(), ctor);
        }
        return getRuntime().getNil();
    }

    @JRubyMethod
    public IRubyObject enclosing_method() {
        Method meth = javaClass().getEnclosingMethod();
        if (meth != null) {
            return new JavaMethod(getRuntime(), meth);
        }
        return getRuntime().getNil();
    }

    @JRubyMethod
    public IRubyObject enum_constants() {
        return Java.getInstance(getRuntime(), javaClass().getEnumConstants());
    }

    @JRubyMethod
    public IRubyObject generic_interfaces() {
        return Java.getInstance(getRuntime(), javaClass().getGenericInterfaces());
    }

    @JRubyMethod
    public IRubyObject generic_superclass() {
        return Java.getInstance(getRuntime(), javaClass().getGenericSuperclass());
    }

    @JRubyMethod
    public IRubyObject type_parameters() {
        return Java.getInstance(getRuntime(), javaClass().getTypeParameters());
    }

    @JRubyMethod
    public IRubyObject signers() {
        return Java.getInstance(getRuntime(), javaClass().getSigners());
    }

    public static String getSimpleName(Class clazz) {
 		if (clazz.isArray()) {
 			return getSimpleName(clazz.getComponentType()) + "[]";
 		}

 		String className = clazz.getName();
 		int len = className.length();
        int i = className.lastIndexOf('$');
 		if (i != -1) {
            do {
 				i++;
 			} while (i < len && Character.isDigit(className.charAt(i)));
 			return className.substring(i);
 		}

 		return className.substring(className.lastIndexOf('.') + 1);
 	}

    @JRubyMethod
    public RubyString simple_name() {
        return getRuntime().newString(getSimpleName(javaClass()));
    }

    @JRubyMethod
    public IRubyObject superclass() {
        Class superclass = javaClass().getSuperclass();
        if (superclass == null) {
            return getRuntime().getNil();
        }
        return JavaClass.get(getRuntime(), superclass);
    }

    @JRubyMethod(name = "<=>", required = 1)
    public IRubyObject op_cmp(IRubyObject other) {
        final Class thisClass = javaClass();
        Class otherClass = null;

        // dig out the other class
        if (other instanceof JavaClass) {
            otherClass = ( (JavaClass) other ).javaClass();
        }
        else if (other instanceof ConcreteJavaProxy) {
            ConcreteJavaProxy proxy = (ConcreteJavaProxy) other;
            final Object wrapped = proxy.getObject();
            if ( wrapped instanceof Class ) {
                otherClass = (Class) wrapped;
            }
        }

        if ( otherClass != null ) {
            if ( thisClass == otherClass ) {
                return getRuntime().newFixnum(0);
            }
            if ( otherClass.isAssignableFrom(thisClass) ) {
                return getRuntime().newFixnum(-1);
            }
            if ( thisClass.isAssignableFrom(otherClass) ) {
                return getRuntime().newFixnum(1);
            }
        }

        // can't do a comparison
        return getRuntime().getNil();
    }

    @JRubyMethod
    public RubyArray java_instance_methods() {
        return toJavaMethods(javaClass().getMethods(), false);
    }

    @JRubyMethod
    public RubyArray declared_instance_methods() {
        return toJavaMethods(javaClass().getDeclaredMethods(), false);
    }

    @JRubyMethod
    public RubyArray java_class_methods() {
        return toJavaMethods(javaClass().getMethods(), true);
    }

    @JRubyMethod
    public RubyArray declared_class_methods() {
        return toJavaMethods(javaClass().getDeclaredMethods(), true);
    }

    private RubyArray toJavaMethods(final Method[] methods, final boolean isStatic) {
        final Ruby runtime = getRuntime();
        final RubyArray result = runtime.newArray(methods.length);
        for ( int i = 0; i < methods.length; i++ ) {
            final Method method = methods[i];
            if ( isStatic == Modifier.isStatic(method.getModifiers()) ) {
                result.append( new JavaMethod(runtime, method) );
            }
        }
        return result;
    }

    @JRubyMethod(required = 1, rest = true)
    public JavaMethod java_method(IRubyObject[] args) {
        final Ruby runtime = getRuntime();
        if ( args.length < 1 ) throw runtime.newArgumentError(args.length, 1);

        final String methodName = args[0].asJavaString();
        try {
            Class[] argumentTypes = getArgumentTypes(runtime, args, 1);
            @SuppressWarnings("unchecked")
            final Method method = javaClass().getMethod(methodName, argumentTypes);
            return new JavaMethod(runtime, method);
        }
        catch (NoSuchMethodException e) {
            throw runtime.newNameError(undefinedMethodMessage(runtime, ids(runtime, methodName), ids(runtime, javaClass().getName()), false), methodName);
        }
    }

    @JRubyMethod(required = 1, rest = true)
    public JavaMethod declared_method(final IRubyObject[] args) {
        final Ruby runtime = getRuntime();
        if ( args.length < 1 ) throw runtime.newArgumentError(args.length, 1);

        final String methodName = args[0].asJavaString();
        try {
            Class[] argumentTypes = getArgumentTypes(runtime, args, 1);
            @SuppressWarnings("unchecked")
            final Method method = javaClass().getDeclaredMethod(methodName, argumentTypes);
            return new JavaMethod(runtime, method);
        }
        catch (NoSuchMethodException e) {
            throw runtime.newNameError(undefinedMethodMessage(runtime, ids(runtime, methodName), ids(runtime, javaClass().getName()), false), methodName);
        }
    }

    @JRubyMethod(required = 1, rest = true)
    public JavaCallable declared_method_smart(final IRubyObject[] args) {
        final Ruby runtime = getRuntime();
        if ( args.length < 1 ) throw runtime.newArgumentError(args.length, 1);

        final String methodName = args[0].asJavaString();

        Class[] argumentTypes = getArgumentTypes(runtime, args, 1);

        JavaCallable callable = getMatchingCallable(runtime, javaClass(), methodName, argumentTypes);

        if ( callable != null ) return callable;

        throw runtime.newNameError(undefinedMethodMessage(runtime, ids(runtime, methodName), ids(runtime, javaClass().getName()), false), methodName);
    }

    public static JavaCallable getMatchingCallable(Ruby runtime, Class javaClass, String methodName, Class[] argumentTypes) {
        if ( methodName.length() == 6 && "".equals(methodName) ) {
            return JavaConstructor.getMatchingConstructor(runtime, javaClass, argumentTypes);
        }
        // FIXME: do we really want 'declared' methods?  includes private/protected, and does _not_
        // include superclass methods
        return JavaMethod.getMatchingDeclaredMethod(runtime, javaClass, methodName, argumentTypes);
    }

    private static Class[] getArgumentTypes(final Ruby runtime, final IRubyObject[] args, final int offset) {
        final int length = args.length; // offset == 0 || 1
        if ( length == offset ) return EMPTY_CLASS_ARRAY;
        final Class[] argumentTypes = new Class[length - offset];
        for ( int i = offset; i < length; i++ ) {
            final IRubyObject arg = args[i];
            final JavaClass type;
            if ( arg instanceof JavaClass ) {
                type = (JavaClass) arg;
            } else if ( arg.respondsTo("java_class") ) {
                type = (JavaClass) arg.callMethod(runtime.getCurrentContext(), "java_class");
            } else {
                type = forNameVerbose(runtime, arg.asJavaString());
            }
            argumentTypes[ i - offset ] = type.javaClass();
        }
        return argumentTypes;
    }

    // caching constructors, as they're accessed for each new instance
    private RubyArray constructors; // TODO seems not used that often?

    @JRubyMethod
    public RubyArray constructors() {
        final RubyArray constructors = this.constructors;
        if ( constructors != null) return constructors;
        return this.constructors = buildConstructors(getRuntime(), javaClass().getConstructors());
    }

    @JRubyMethod
    public RubyArray classes() {
        return toRubyArray(getRuntime(), javaClass().getClasses());
    }

    @JRubyMethod
    public RubyArray declared_classes() {
        final Ruby runtime = getRuntime();
        final Class javaClass = javaClass();
        try {
            Class[] classes = javaClass.getDeclaredClasses();
            final RubyArray result = runtime.newArray(classes.length);
            for (int i = 0; i < classes.length; i++) {
                if (Modifier.isPublic(classes[i].getModifiers())) {
                    result.append( get(runtime, classes[i]) );
                }
            }
            return result;
        }
        catch (SecurityException e) {
            // restrictive security policy; no matter, we only want public
            // classes anyway
            try {
                Class[] classes = javaClass.getClasses();
                final RubyArray result = runtime.newArray(classes.length);
                for (int i = 0; i < classes.length; i++) {
                    if (javaClass == classes[i].getDeclaringClass()) {
                        result.append( get(runtime, classes[i]) );
                    }
                }
                return result;
            }
            catch (SecurityException e2) {
                // very restrictive policy (disallows Member.PUBLIC)
                // we'd never actually get this far in that case
            }
        }
        return RubyArray.newEmptyArray(runtime);
    }

    @JRubyMethod
    public RubyArray declared_constructors() {
        return buildConstructors(getRuntime(), javaClass().getDeclaredConstructors());
    }

    private static RubyArray buildConstructors(final Ruby runtime, Constructor[] constructors) {
        RubyArray result = RubyArray.newArray(runtime, constructors.length);
        for ( int i = 0; i < constructors.length; i++ ) {
            result.append( new JavaConstructor(runtime, constructors[i]) );
        }
        return result;
    }

    @JRubyMethod(rest = true)
    public JavaConstructor constructor(IRubyObject[] args) {
        final Ruby runtime = getRuntime();
        try {
            Class[] parameterTypes = getArgumentTypes(runtime, args, 0);
            @SuppressWarnings("unchecked")
            Constructor constructor = javaClass().getConstructor(parameterTypes);
            return new JavaConstructor(runtime, constructor);
        }
        catch (NoSuchMethodException nsme) {
            throw runtime.newNameError("no matching java constructor", null);
        }
    }

    @JRubyMethod(rest = true)
    public JavaConstructor declared_constructor(IRubyObject[] args) {
        final Ruby runtime = getRuntime();
        try {
            Class[] parameterTypes = getArgumentTypes(runtime, args, 0);
            @SuppressWarnings("unchecked")
            Constructor constructor = javaClass().getDeclaredConstructor(parameterTypes);
            return new JavaConstructor(runtime, constructor);
        }
        catch (NoSuchMethodException nsme) {
            throw runtime.newNameError("no matching java constructor", null);
        }
    }

    @JRubyMethod
    public JavaClass array_class() {
        final Class arrayClass = Array.newInstance(javaClass(), 0).getClass();
        return JavaClass.get(getRuntime(), arrayClass);
    }

    @JRubyMethod(required = 1)
    public JavaObject new_array(IRubyObject lengthArgument) {
        if (lengthArgument instanceof RubyInteger) {
            // one-dimensional array
            int length = ((RubyInteger) lengthArgument).getIntValue();
            return new JavaArray(getRuntime(), Array.newInstance(javaClass(), length));
        }
        else if (lengthArgument instanceof RubyArray) {
            // n-dimensional array
            IRubyObject[] aryLengths = ((RubyArray)lengthArgument).toJavaArrayMaybeUnsafe();
            final int length = aryLengths.length;
            if (length == 0) {
                throw getRuntime().newArgumentError("empty dimensions specifier for java array");
            }
            final int[] dimensions = new int[length];
            for (int i = length; --i >= 0; ) {
                IRubyObject dimLength = aryLengths[i];
                if ( ! ( dimLength instanceof RubyInteger ) ) {
                    throw getRuntime().newTypeError(dimLength, getRuntime().getInteger());
                }
                dimensions[i] = ((RubyInteger) dimLength).getIntValue();
            }
            return new JavaArray(getRuntime(), Array.newInstance(javaClass(), dimensions));
        }
        else {
            throw getRuntime().newArgumentError(
                "invalid length or dimensions specifier for java array - must be Integer or Array of Integer");
        }
    }

    public IRubyObject emptyJavaArray(ThreadContext context) {
        return ArrayUtils.emptyJavaArrayDirect(context, javaClass());
    }

    public IRubyObject javaArraySubarray(ThreadContext context, JavaArray fromArray, int index, int size) {
        return ArrayUtils.javaArraySubarrayDirect(context, getValue(), index, size);
    }

    /**
     * Contatenate two Java arrays into a new one. The component type of the
     * additional array must be assignable to the component type of the
     * original array.
     *
     * @param context
     * @param original
     * @param additional
     * @return
     */
    public IRubyObject concatArrays(ThreadContext context, JavaArray original, JavaArray additional) {
        return ArrayUtils.concatArraysDirect(context, original.getValue(), additional.getValue());
    }

    /**
     * The slow version for when concatenating a Java array of a different type.
     *
     * @param context
     * @param original
     * @param additional
     * @return
     */
    public IRubyObject concatArrays(ThreadContext context, JavaArray original, IRubyObject additional) {
        return ArrayUtils.concatArraysDirect(context, original.getValue(), additional);
    }

    @Deprecated // no-longer-used
    public IRubyObject javaArrayFromRubyArray(ThreadContext context, IRubyObject fromArray) {
        if ( ! ( fromArray instanceof RubyArray ) ) {
            final Ruby runtime = context.runtime;
            throw runtime.newTypeError(fromArray, runtime.getArray());
        }
        return javaArrayFromRubyArray(context, (RubyArray) fromArray);
    }

    public final IRubyObject javaArrayFromRubyArray(ThreadContext context, RubyArray fromArray) {
        final Ruby runtime = context.runtime;

        Object newArray = javaArrayFromRubyArrayDirect(context, fromArray);

        return new ArrayJavaProxy(runtime, Java.getProxyClassForObject(runtime, newArray), newArray, JavaUtil.getJavaConverter(javaClass()));
    }

    public final Object javaArrayFromRubyArrayDirect(ThreadContext context, RubyArray fromArray) {
        final Ruby runtime = context.runtime;
        final Class type = javaClass();

        final Object newArray = Array.newInstance(type, fromArray.size());

        if ( type.isArray() ) {
            // if it's an array of arrays, recurse with the component type
            for ( int i = 0; i < fromArray.size(); i++ ) {
                final Class nestedType = type.getComponentType();
                final IRubyObject element = fromArray.eltInternal(i);
                final Object nestedArray;
                if ( element instanceof RubyArray ) { // recurse
                    JavaClass componentType = JavaClass.get(runtime, nestedType);
                    nestedArray = componentType.javaArrayFromRubyArrayDirect(context, element);
                }
                else if ( type.isInstance(element) ) {
                    nestedArray = element;
                }
                else { // still try (nested) toJava conversion :
                    nestedArray = element.toJava(type);
                }
                ArrayUtils.setWithExceptionHandlingDirect(runtime, newArray, i, nestedArray);
            }
        } else {
            ArrayUtils.copyDataToJavaArrayDirect(fromArray, newArray);
        }

        return newArray;
    }

    public final Object javaArrayFromRubyArrayDirect(ThreadContext context, IRubyObject fromArray) {
        if ( ! ( fromArray instanceof RubyArray ) ) {
            final Ruby runtime = context.runtime;
            throw runtime.newTypeError(fromArray, runtime.getArray());
        }
        return javaArrayFromRubyArrayDirect(context, (RubyArray) fromArray);
    }

    @JRubyMethod
    public RubyArray fields() {
        return buildFieldResults(getRuntime(), javaClass().getFields());
    }

    @JRubyMethod
    public RubyArray declared_fields() {
        return buildFieldResults(getRuntime(), javaClass().getDeclaredFields());
    }

    private static RubyArray buildFieldResults(final Ruby runtime, Field[] fields) {
        RubyArray result = runtime.newArray( fields.length );
        for ( int i = 0; i < fields.length; i++ ) {
            result.append( new JavaField(runtime, fields[i]) );
        }
        return result;
    }

    @JRubyMethod(required = 1)
    public JavaField field(ThreadContext context, IRubyObject name) {
        Class javaClass = javaClass();
        Ruby runtime = context.runtime;
        String stringName = name.asJavaString();

        try {
            return new JavaField(runtime, javaClass.getField(stringName));
        } catch (NoSuchFieldException nsfe) {
            String newName = JavaUtil.getJavaCasedName(stringName);
            if(newName != null) {
                try {
                    return new JavaField(runtime, javaClass.getField(newName));
                } catch (NoSuchFieldException nsfe2) {}
            }
            throw undefinedFieldError(runtime, javaClass.getName(), stringName);
         }
    }

    @JRubyMethod(required = 1)
    public JavaField declared_field(ThreadContext context, IRubyObject name) {
        Class javaClass = javaClass();
        Ruby runtime = context.runtime;
        String stringName = name.asJavaString();

        try {
            return new JavaField(runtime, javaClass.getDeclaredField(stringName));
        } catch (NoSuchFieldException nsfe) {
            String newName = JavaUtil.getJavaCasedName(stringName);
            if(newName != null) {
                try {
                    return new JavaField(runtime, javaClass.getDeclaredField(newName));
                } catch (NoSuchFieldException nsfe2) {}
            }
            throw undefinedFieldError(runtime, javaClass.getName(), stringName);
        }
    }

    public static RaiseException undefinedFieldError(Ruby runtime, String javaClassName, String name) {
        return runtime.newNameError("undefined field '" + name + "' for class '" + javaClassName + "'", name);
    }

    @JRubyMethod
    public RubyArray interfaces() {
        return toRubyArray(getRuntime(), javaClass().getInterfaces());
    }

    @JRubyMethod(name = "primitive?")
    public RubyBoolean primitive_p() {
        return getRuntime().newBoolean( isPrimitive() );
    }

    boolean isPrimitive() { return javaClass().isPrimitive(); }

    @JRubyMethod(name = "assignable_from?", required = 1)
    public RubyBoolean assignable_from_p(IRubyObject other) {
        if ( ! (other instanceof JavaClass) ) {
            throw getRuntime().newTypeError("assignable_from requires JavaClass (" + other.getType() + " given)");
        }

        Class otherClass = ((JavaClass) other).javaClass();
        return isAssignableFrom(otherClass) ? getRuntime().getTrue() : getRuntime().getFalse();
    }

    public final boolean isAssignableFrom(final Class clazz) {
        return assignable(javaClass(), clazz);
    }

    public static boolean assignable(Class target, Class from) {
        if ( target.isPrimitive() ) target = CodegenUtils.getBoxType(target);
        else if ( from == Void.TYPE || target.isAssignableFrom(from) ) {
            return true;
        }
        if ( from.isPrimitive() ) from = CodegenUtils.getBoxType(from);

        if ( target.isAssignableFrom(from) ) return true;

        if ( Number.class.isAssignableFrom(target) ) {
            if ( Number.class.isAssignableFrom(from) ) {
                return true;
            }
            if ( from == Character.class ) {
                return true;
            }
        }
        else if ( target == Character.class ) {
            if ( Number.class.isAssignableFrom(from) ) {
                return true;
            }
        }
        return false;
    }

    @JRubyMethod
    public JavaClass component_type() {
        if ( ! javaClass().isArray() ) {
            throw getRuntime().newTypeError("not a java array-class");
        }
        return JavaClass.get(getRuntime(), javaClass().getComponentType());
    }

    public static Constructor[] getConstructors(final Class clazz) {
        try {
            return clazz.getConstructors();
        }
        catch (SecurityException e) { return new Constructor[0]; }
    }

    public static Class[] getDeclaredClasses(final Class clazz) {
        try {
            return clazz.getDeclaredClasses();
        }
        catch (SecurityException e) { return new Class[0]; }
        catch (NoClassDefFoundError cnfe) {
            // This is a Scala-specific hack, since Scala uses peculiar
            // naming conventions and class attributes that confuse Java's
            // reflection logic and cause a blow up in getDeclaredClasses.
            // See http://lampsvn.epfl.ch/trac/scala/ticket/2749
            return new Class[0];
        }
    }

    public static Field[] getDeclaredFields(final Class clazz) {
        try {
            return clazz.getDeclaredFields();
        }
        catch (SecurityException e) {
            return getFields(clazz);
        }
    }

    public static Field[] getFields(final Class clazz) {
        try {
            return clazz.getFields();
        }
        catch (SecurityException e) { return new Field[0]; }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy