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

java.lang.reflect.Method Maven / Gradle / Ivy

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache 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.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/*
 * Copyright (C) 2008 The Android Open Source Project
 *
 * Licensed under the Apache 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.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/*
 * Copyright (C) 2012 Trillian Mobile AB
 *
 * Licensed under the Apache 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.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package java.lang.reflect;

import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.Comparator;
import libcore.util.EmptyArray;
import libcore.reflect.GenericSignatureParser;
import libcore.reflect.ListOfTypes;
import libcore.reflect.Types;
import org.robovm.rt.VM;

/**
 * This class represents a method. Information about the method can be accessed,
 * and the method can be invoked dynamically.
 */
public final class Method extends AccessibleObject implements GenericDeclaration, Member {

    /**
     * Orders methods by their name, parameters and return type.
     *
     * @hide
     */
    public static final Comparator ORDER_BY_SIGNATURE = new Comparator() {
        public int compare(Method a, Method b) {
            int comparison = a.name.compareTo(b.name);
            if (comparison != 0) {
                return comparison;
            }
    
            Class[] aParameters = a.parameterTypes;
            Class[] bParameters = b.parameterTypes;
            int length = Math.min(aParameters.length, bParameters.length);
            for (int i = 0; i < length; i++) {
                comparison = aParameters[i].getName().compareTo(bParameters[i].getName());
                if (comparison != 0) {
                    return comparison;
                }
            }
    
            if (aParameters.length != bParameters.length) {
                return aParameters.length - bParameters.length;
            }

            // this is necessary for methods that have covariant return types.
            return a.getReturnType().getName().compareTo(b.getReturnType().getName());
        }
    };

    /*
     * The RoboVM Method* object
     */
    private final long method;

    /*
     * Cached fields
     */
    private int modifiers = -1;
    private Class declaringClass;
    private String name;
    private Class[] parameterTypes;
    private Class[] exceptionTypes;
    private Class returnType;
    private Object defaultValue;
    private Annotation[] declaredAnnotations;
    private Annotation[][] parameterAnnotations;

    private ListOfTypes genericExceptionTypes;
    private ListOfTypes genericParameterTypes;
    private Type genericReturnType;
    private TypeVariable[] formalTypeParameters;
    private volatile boolean genericTypesAreInitialized = false;
    
    @SuppressWarnings("unchecked")
    private synchronized void initGenericTypes() {
        if (!genericTypesAreInitialized) {
            String signatureAttribute = getSignatureAttribute();
            GenericSignatureParser parser = new GenericSignatureParser(
                    getDeclaringClass().getClassLoader());
            parser.parseForMethod(this, signatureAttribute, getExceptionTypes());
            formalTypeParameters = parser.formalTypeParameters;
            genericParameterTypes = parser.parameterTypes;
            genericExceptionTypes = parser.exceptionTypes;
            genericReturnType = parser.returnType;
            genericTypesAreInitialized = true;
        }
    }
    
    Method(long method) {
        this.method = method;
    }
    
    /**
     * Construct a clone of the given instance.
     *
     * @param orig non-null; the original instance to clone
     */
    /*package*/ Method(Method orig) {

        this.method = orig.method;
        this.modifiers = orig.modifiers;
        this.declaringClass = orig.declaringClass;
        this.name = orig.name;
        this.parameterTypes = orig.parameterTypes;
        this.exceptionTypes = orig.exceptionTypes;
        this.returnType = orig.returnType;
        this.defaultValue = orig.defaultValue;
        this.genericExceptionTypes = orig.genericExceptionTypes;
        this.genericParameterTypes = orig.genericParameterTypes;
        this.genericReturnType = orig.genericReturnType;
        this.formalTypeParameters = orig.formalTypeParameters;
        this.genericTypesAreInitialized = orig.genericTypesAreInitialized;
        this.declaredAnnotations = orig.declaredAnnotations;
        this.parameterAnnotations = orig.parameterAnnotations;
        
        // Copy the accessible flag.
        if (orig.flag) {
            this.flag = true;
        }
    }
    
    public TypeVariable[] getTypeParameters() {
        initGenericTypes();
        return formalTypeParameters.clone();        
    }

    @Override
    protected String getSignatureAttribute() {
        return getSignatureAttribute(method);
    }
    
    final native static String getSignatureAttribute(long method);
    
    /**
     * Returns the string representation of the method's declaration, including
     * the type parameters.
     *
     * @return the string representation of this method
     */
    public String toGenericString() {
        StringBuilder sb = new StringBuilder(80);

        initGenericTypes();

        // append modifiers if any
        int modifier = getModifiers();
        if (modifier != 0) {
            sb.append(Modifier.toString(modifier & ~(Modifier.BRIDGE +
                    Modifier.VARARGS))).append(' ');
        }
        // append type parameters
        if (formalTypeParameters != null && formalTypeParameters.length > 0) {
            sb.append('<');
            for (int i = 0; i < formalTypeParameters.length; i++) {
                appendGenericType(sb, formalTypeParameters[i]);
                if (i < formalTypeParameters.length - 1) {
                    sb.append(",");
                }
            }
            sb.append("> ");
        }
        // append return type
        appendGenericType(sb, Types.getType(genericReturnType));
        sb.append(' ');
        // append method name
        appendTypeName(sb, getDeclaringClass());
        sb.append(".").append(getName());
        // append parameters
        sb.append('(');
        appendArrayGenericType(sb, Types.getTypeArray(genericParameterTypes, false));
        sb.append(')');
        // append exceptions if any
        Type[] genericExceptionTypeArray = Types.getTypeArray(genericExceptionTypes, false);
        if (genericExceptionTypeArray.length > 0) {
            sb.append(" throws ");
            appendArrayGenericType(sb, genericExceptionTypeArray);
        }
        return sb.toString();
    }

    /**
     * Returns the parameter types as an array of {@code Type} instances, in
     * declaration order. If this method has no parameters, an empty array is
     * returned.
     *
     * @return the parameter types
     *
     * @throws GenericSignatureFormatError
     *             if the generic method signature is invalid
     * @throws TypeNotPresentException
     *             if any parameter type points to a missing type
     * @throws MalformedParameterizedTypeException
     *             if any parameter type points to a type that cannot be
     *             instantiated for some reason
     */
    public Type[] getGenericParameterTypes() {
        initGenericTypes();
        return Types.getTypeArray(genericParameterTypes, true);
    }

    /**
     * Returns the exception types as an array of {@code Type} instances. If
     * this method has no declared exceptions, an empty array will be returned.
     *
     * @return an array of generic exception types
     *
     * @throws GenericSignatureFormatError
     *             if the generic method signature is invalid
     * @throws TypeNotPresentException
     *             if any exception type points to a missing type
     * @throws MalformedParameterizedTypeException
     *             if any exception type points to a type that cannot be
     *             instantiated for some reason
     */
    public Type[] getGenericExceptionTypes() {
        initGenericTypes();
        return Types.getTypeArray(genericExceptionTypes, true);
    }

    /**
     * Returns the return type of this method as a {@code Type} instance.
     *
     * @return the return type of this method
     *
     * @throws GenericSignatureFormatError
     *             if the generic method signature is invalid
     * @throws TypeNotPresentException
     *             if the return type points to a missing type
     * @throws MalformedParameterizedTypeException
     *             if the return type points to a type that cannot be
     *             instantiated for some reason
     */
    public Type getGenericReturnType() {
        initGenericTypes();
        return Types.getType(genericReturnType);
    }
    
    @Override
    protected Annotation[] getDeclaredAnnotations(boolean copy) {
        if (declaredAnnotations == null) {
            declaredAnnotations = getDeclaredAnnotations(method);
        }
        return copy ? declaredAnnotations.clone() : declaredAnnotations;
    }
    static final native Annotation[] getDeclaredAnnotations(long method);

    private static final Annotation[] NO_ANNOTATIONS = new Annotation[0];

    /**
     * Creates an array of empty Annotation arrays.
     */
    /*package*/ static Annotation[][] noAnnotations(int size) {
        Annotation[][] annotations = new Annotation[size][];
        for (int i = 0; i < size; i++) {
            annotations[i] = NO_ANNOTATIONS;
        }
        return annotations;
    }
    
    /**
     * Returns an array of arrays that represent the annotations of the formal
     * parameters of this method. If there are no parameters on this method,
     * then an empty array is returned. If there are no annotations set, then
     * and array of empty arrays is returned.
     *
     * @return an array of arrays of {@code Annotation} instances
     */
    public Annotation[][] getParameterAnnotations() {
        if (parameterAnnotations == null) {
            Annotation[][] pa = getParameterAnnotations(method);
            if (pa.length == 0) {
                pa = noAnnotations(getParameterTypes(false).length);
            }
            parameterAnnotations = pa;
        }
        return parameterAnnotations.clone();
    }
    final static native Annotation[][] getParameterAnnotations(long method);
    
    /**
     * Indicates whether or not this method takes a variable number argument.
     *
     * @return {@code true} if a vararg is declared, {@code false} otherwise
     */
    public boolean isVarArgs() {
        return (getModifiers() & Modifier.VARARGS) != 0;
    }

    /**
     * Indicates whether or not this method is a bridge.
     *
     * @return {@code true} if this method is a bridge, {@code false} otherwise
     */
    public boolean isBridge() {
        return (getModifiers() & Modifier.BRIDGE) != 0;
    }

    /**
     * Indicates whether or not this method is synthetic.
     *
     * @return {@code true} if this method is synthetic, {@code false} otherwise
     */
    public boolean isSynthetic() {
        return (getModifiers() & Modifier.SYNTHETIC) != 0;
    }

    /**
     * Returns the default value for the annotation member represented by this
     * method.
     *
     * @return the default value, or {@code null} if none
     *
     * @throws TypeNotPresentException
     *             if this annotation member is of type {@code Class} and no
     *             definition can be found
     */
    public Object getDefaultValue() {
        if (defaultValue == null) {
            defaultValue = getDefaultValue(method);
        }
        return defaultValue;
    }
    final native static Object getDefaultValue(long method);

    /**
     * Indicates whether or not the specified {@code object} is equal to this
     * method. To be equal, the specified object must be an instance
     * of {@code Method} with the same declaring class and parameter types
     * as this method.
     *
     * @param object
     *            the object to compare
     *
     * @return {@code true} if the specified object is equal to this
     *         method, {@code false} otherwise
     *
     * @see #hashCode
     */
    @Override
    public boolean equals(Object object) {
        if (this == object) {
            return true;
        }
        if (!(object instanceof Method)) {
            return false;
        }
        Method rhs = (Method) object;
        // We don't compare exceptionTypes because two methods
        // can't differ only by their declared exceptions.
        return getDeclaringClass().equals(rhs.getDeclaringClass()) &&
            getName().equals(rhs.getName()) &&
            getReturnType().equals(rhs.getReturnType()) &&
            Arrays.equals(getParameterTypes(false), rhs.getParameterTypes(false));
    }

    /**
     * Returns the class that declares this method.
     *
     * @return the declaring class
     */
    public Class getDeclaringClass() {
        if (declaringClass == null) {
            declaringClass = getDeclaringClass(method);
        }
        return declaringClass;
    }
    final native static  Class getDeclaringClass(long method);

    /**
     * Returns the exception types as an array of {@code Class} instances. If
     * this method has no declared exceptions, an empty array is returned.
     *
     * @return the declared exception classes
     */
    public Class[] getExceptionTypes() {
        return getExceptionTypes(true);
    }
    private Class[] getExceptionTypes(boolean copy) {
        if (exceptionTypes == null) {
            exceptionTypes = getExceptionTypes(method);
        }
        return copy ? exceptionTypes.clone() : exceptionTypes;
    }
    final native static Class[] getExceptionTypes(long method);

    /**
     * Returns the modifiers for this method. The {@link Modifier} class should
     * be used to decode the result.
     *
     * @return the modifiers for this method
     *
     * @see Modifier
     */
    public int getModifiers() {
        if (modifiers == -1) {
            modifiers = getModifiers(method);
        }
        return modifiers;
    }
    final native static int getModifiers(long method);

    /**
     * Returns the name of the method represented by this {@code Method}
     * instance.
     *
     * @return the name of this method
     */
    public String getName() {
        if (name == null) {
            name = getName(method);
        }
        return name;
    }
    private native static String getName(long method);

    /**
     * Returns an array of {@code Class} objects associated with the parameter
     * types of this method. If the method was declared with no parameters, an
     * empty array will be returned.
     *
     * @return the parameter types
     */
    public Class[] getParameterTypes() {
        return getParameterTypes(true);
    }
    final Class[] getParameterTypes(boolean copy) {
        if (parameterTypes == null) {
            parameterTypes = getParameterTypes(method);
        }
        return copy ? parameterTypes.clone() : parameterTypes;
    }
    final native static Class[] getParameterTypes(long method);

    /**
     * Returns the {@code Class} associated with the return type of this
     * method.
     *
     * @return the return type
     */
    public Class getReturnType() {
        if (returnType == null) {
            returnType = getReturnType(method);
        }
        return returnType;
    }
    private native static Class getReturnType(long method);

    /**
     * Returns an integer hash code for this method. Objects which are equal
     * return the same value for this method. The hash code for this Method is
     * the hash code of the name of this method.
     *
     * @return hash code for this method
     *
     * @see #equals
     */
    @Override
    public int hashCode() {
        return getName().hashCode();
    }

    /**
     * Returns the result of dynamically invoking this method. Equivalent to
     * {@code receiver.methodName(arg1, arg2, ... , argN)}.
     *
     * 

If the method is static, the receiver argument is ignored (and may be null). * *

If the method takes no arguments, you can pass {@code (Object[]) null} instead of * allocating an empty array. * *

If you're calling a varargs method, you need to pass an {@code Object[]} for the * varargs parameter: that conversion is usually done in {@code javac}, not the VM, and * the reflection machinery does not do this for you. (It couldn't, because it would be * ambiguous.) * *

Reflective method invocation follows the usual process for method lookup. * *

If an exception is thrown during the invocation it is caught and * wrapped in an InvocationTargetException. This exception is then thrown. * *

If the invocation completes normally, the return value itself is * returned. If the method is declared to return a primitive type, the * return value is boxed. If the return type is void, null is returned. * * @param receiver * the object on which to call this method (or null for static methods) * @param args * the arguments to the method * @return the result * * @throws NullPointerException * if {@code receiver == null} for a non-static method * @throws IllegalAccessException * if this method is not accessible (see {@link AccessibleObject}) * @throws IllegalArgumentException * if the number of arguments doesn't match the number of parameters, the receiver * is incompatible with the declaring class, or an argument could not be unboxed * or converted by a widening conversion to the corresponding parameter type * @throws InvocationTargetException * if an exception was thrown by the invoked method */ public Object invoke(Object receiver, Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { if (!Modifier.isStatic(getModifiers())) { Class expectedClass = getDeclaringClass(); if (receiver == null) { StringBuilder sb = new StringBuilder(); sb.append("expected receiver of type "); appendTypeName(sb, expectedClass); sb.append(", but got null"); throw new NullPointerException(sb.toString()); } if (!getDeclaringClass().isInstance(receiver)) { StringBuilder sb = new StringBuilder(); sb.append("expected receiver of type "); appendTypeName(sb, expectedClass); sb.append(", but got "); appendTypeName(sb, receiver.getClass()); throw new IllegalArgumentException(sb.toString()); } } if (args == null) { args = EmptyArray.OBJECT; } Class[] pTypes = getParameterTypes(false); if (args.length != pTypes.length) { throw new IllegalArgumentException("wrong number of arguments; " + "expected " + pTypes.length + ", got " + args.length); } if (!flag) { // Check access if (!checkAccessibleFast(this)) { Class caller = VM.getStackClasses(0, 1)[0]; if (!checkAccessible(caller, this)) { throw new IllegalAccessException(String.format("Attempt to access method %s.%s(%s) from class %s", getDeclaringClass().getName(), getName(), toString(pTypes), caller.getName())); } } } return internalInvoke(method, pTypes, receiver, args); } private static native Object internalInvoke(long method, Class[] pTypes, Object receiver, Object[] args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException; /** * Returns a string containing a concise, human-readable description of this * method. The format of the string is: * *

    *
  1. modifiers (if any) *
  2. return type or 'void' *
  3. declaring class name *
  4. '(' *
  5. parameter types, separated by ',' (if any) *
  6. ')' *
  7. 'throws' plus exception types, separated by ',' (if any) *
* * For example: {@code public native Object * java.lang.Method.invoke(Object,Object) throws * IllegalAccessException,IllegalArgumentException * ,InvocationTargetException} * * @return a printable representation for this method */ @Override public String toString() { Class returnType = getReturnType(); Class declaringClass = getDeclaringClass(); String name = getName(); Class[] parameterTypes = getParameterTypes(false); Class[] exceptionTypes = getExceptionTypes(false); StringBuilder result = new StringBuilder(Modifier.toString(getModifiers())); if (result.length() != 0) result.append(' '); appendTypeName(result, returnType); result.append(' '); result.append(declaringClass.getName()); result.append('.'); result.append(name); result.append("("); result.append(toString(parameterTypes)); result.append(")"); if (exceptionTypes != null && exceptionTypes.length != 0) { result.append(" throws "); result.append(toString(exceptionTypes)); } return result.toString(); } /** * Returns the constructor's signature in non-printable form. This is called * (only) from IO native code and needed for deriving the serialVersionUID * of the class * * @return The constructor's signature. */ @SuppressWarnings("unused") private String getSignature() { Class returnType = getReturnType(); Class[] parameterTypes = getParameterTypes(false); StringBuilder result = new StringBuilder(); result.append('('); for (int i = 0; i < parameterTypes.length; i++) { result.append(getSignature(parameterTypes[i])); } result.append(')'); result.append(getSignature(returnType)); return result.toString(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy