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 RoboVM 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:
*
*
* - modifiers (if any)
*
- return type or 'void'
*
- declaring class name
*
- '('
*
- parameter types, separated by ',' (if any)
*
- ')'
*
- '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();
}
}