blob: 5588f625648df5213fa730bfdc9ab3ddca2bdd3a [file] [log] [blame]
/*
* 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.
*/
package java.lang;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import org.apache.harmony.kernel.vm.LangAccess;
import org.apache.harmony.kernel.vm.ReflectionAccess;
/**
* Cache of per-class data, meant to help the performance of reflection
* methods.
*
* <p><b>Note:</b> None of the methods perform access checks. It is up
* to the (package internal) clients of this code to perform such
* checks as necessary.</p>
*
* <p><b>Also Note:</b> None of the returned array values are
* protected in any way. It is up to the (again, package internal)
* clients of this code to protect the arrays if they should ever
* escape the package.</p>
*/
/*package*/ class ClassCache<T> {
// TODO: Add caching for constructors and fields.
/** non-null; comparator used for enumerated values */
private static final EnumComparator ENUM_COMPARATOR =
new EnumComparator();
/** non-null; reflection access bridge */
/*package*/ static final ReflectionAccess REFLECT = getReflectionAccess();
/** non-null; class that this instance represents */
private final Class<T> clazz;
/** null-ok; list of all public methods, both direct and inherited */
private volatile Method[] methods;
/** null-ok; list of all declared methods */
private volatile Method[] declaredMethods;
/** null-ok; list of all public declared methods */
private volatile Method[] declaredPublicMethods;
/** null-ok; list of all declared fields */
private volatile Field[] declaredFields;
/** null-ok; list of all public declared fields */
private volatile Field[] declaredPublicFields;
/** null-ok; list of all fields, both direct and inherited */
private volatile Field[] allFields;
/** null-ok; list of all public fields, both direct and inherited */
private volatile Field[] allPublicFields;
/**
* null-ok; array of enumerated values in their original order, if this
* instance's class is an enumeration
*/
private volatile T[] enumValuesInOrder;
/**
* null-ok; array of enumerated values sorted by name, if this
* instance's class is an enumeration
*/
private volatile T[] enumValuesByName;
static {
/*
* Provide access to this package from java.util as part of
* bootstrap. TODO: See if this can be removed in favor of the
* simpler mechanism below. (That is, see if EnumSet will be
* happy calling LangAccess.getInstance().)
*/
Field field;
try {
field = EnumSet.class.getDeclaredField("LANG_BOOTSTRAP");
REFLECT.setAccessibleNoCheck(field, true);
} catch (NoSuchFieldException ex) {
// This shouldn't happen because the field is in fact defined.
throw new AssertionError(ex);
}
try {
field.set(null, LangAccessImpl.THE_ONE);
} catch (IllegalAccessException ex) {
// This shouldn't happen because we made the field accessible.
throw new AssertionError(ex);
}
// Also set up the bootstrap-classpath-wide access mechanism.
LangAccess.setInstance(LangAccessImpl.THE_ONE);
}
/**
* Constructs an instance.
*
* @param clazz non-null; class that this instance represents
*/
/*package*/ ClassCache(Class<T> clazz) {
if (clazz == null) {
throw new NullPointerException("clazz == null");
}
this.clazz = clazz;
}
/**
* Gets the list of all declared methods.
*
* @return non-null; the list of all declared methods
*/
public Method[] getDeclaredMethods() {
if (declaredMethods == null) {
declaredMethods = Class.getDeclaredMethods(clazz, false);
}
return declaredMethods;
}
/**
* Gets the list of all declared public methods.
*
* @return non-null; the list of all declared public methods
*/
private Method[] getDeclaredPublicMethods() {
if (declaredPublicMethods == null) {
declaredPublicMethods = Class.getDeclaredMethods(clazz, true);
}
return declaredPublicMethods;
}
/**
* Returns public methods defined by {@code clazz}, its superclasses and all
* implemented interfaces, not including overridden methods. This method
* performs no security checks.
*/
public Method[] getMethods() {
Method[] cachedResult = methods;
if (cachedResult == null) {
methods = findMethods();
}
return methods;
}
private Method[] findMethods() {
List<Method> allMethods = new ArrayList<Method>();
getMethodsRecursive(clazz, allMethods);
/*
* Remove methods defined by multiple types, preferring to keep methods
* declared by derived types.
*
* Classes may define multiple methods with the same name and parameter
* types due to covariant return types. In this case both are returned,
* with the non-synthetic method first because it is preferred by
* getMethod(String,Class[]).
*/
Collections.sort(allMethods, Method.ORDER_BY_SIGNATURE);
List<Method> natural = new ArrayList<Method>(allMethods.size());
List<Method> synthetic = new ArrayList<Method>(allMethods.size());
Method previous = null;
for (Method method : allMethods) {
if (previous != null
&& Method.ORDER_BY_SIGNATURE.compare(method, previous) == 0
&& method.getDeclaringClass() != previous.getDeclaringClass()) {
continue;
}
if (method.isSynthetic()) {
synthetic.add(method);
} else {
natural.add(method);
}
previous = method;
}
List<Method> result = new ArrayList<Method>(allMethods.size());
result.addAll(natural);
result.addAll(synthetic);
return result.toArray(new Method[result.size()]);
}
/**
* Populates {@code sink} with public methods defined by {@code clazz}, its
* superclasses, and all implemented interfaces, including overridden methods.
* This method performs no security checks.
*/
private static void getMethodsRecursive(Class<?> clazz, List<Method> result) {
for (Class<?> c = clazz; c != null; c = c.getSuperclass()) {
for (Method method : c.getClassCache().getDeclaredPublicMethods()) {
result.add(method);
}
}
for (Class<?> ifc : clazz.getInterfaces()) {
getMethodsRecursive(ifc, result);
}
}
/**
* Finds and returns a method with a given name and signature. Use
* this with one of the method lists returned by instances of this class.
*
* @param list non-null; the list of methods to search through
* @param parameterTypes non-null; the formal parameter list
* @return non-null; the matching method
* @throws NoSuchMethodException thrown if the method does not exist
*/
public static Method findMethodByName(Method[] list, String name,
Class<?>[] parameterTypes) throws NoSuchMethodException {
if (name == null) {
throw new NullPointerException("Method name must not be null.");
}
for (Method method : list) {
if (method.getName().equals(name)
&& compareClassLists(method.getParameterTypes(), parameterTypes)) {
return method;
}
}
throw new NoSuchMethodException(name);
}
/**
* Compares two class lists for equality. Empty and
* <code>null</code> lists are considered equal. This is useful
* for matching methods and constructors.
*
* <p>TODO: Take into account assignment compatibility?</p>
*
* @param a null-ok; the first list of types
* @param b null-ok; the second list of types
* @return true if and only if the lists are equal
*/
public static boolean compareClassLists(Class<?>[] a, Class<?>[] b) {
if (a == null) {
return (b == null) || (b.length == 0);
}
int length = a.length;
if (b == null) {
return (length == 0);
}
if (length != b.length) {
return false;
}
for (int i = length - 1; i >= 0; i--) {
if (a[i] != b[i]) {
return false;
}
}
return true;
}
/**
* Makes a deep copy of the given array of methods. This is useful
* when handing out arrays from the public API.
*
* <p><b>Note:</b> In such cases, it is insufficient to just make
* a shallow copy of the array, since method objects aren't
* immutable due to the existence of {@link
* AccessibleObject#setAccessible}.</p>
*
* @param orig non-null; array to copy
* @return non-null; a deep copy of the given array
*/
public static Method[] deepCopy(Method[] orig) {
int length = orig.length;
Method[] result = new Method[length];
for (int i = length - 1; i >= 0; i--) {
result[i] = REFLECT.clone(orig[i]);
}
return result;
}
/**
* Gets the list of all declared fields.
*
* @return non-null; the list of all declared fields
*/
public Field[] getDeclaredFields() {
if (declaredFields == null) {
declaredFields = Class.getDeclaredFields(clazz, false);
}
return declaredFields;
}
/**
* Gets the list of all declared public fields.
*
* @return non-null; the list of all declared public fields
*/
public Field[] getDeclaredPublicFields() {
if (declaredPublicFields == null) {
declaredPublicFields = Class.getDeclaredFields(clazz, true);
}
return declaredPublicFields;
}
/**
* Gets either the list of declared fields or the list of declared
* public fields.
*
* @param publicOnly whether to only return public fields
*/
public Field[] getDeclaredFields(boolean publicOnly) {
return publicOnly ? getDeclaredPublicFields() : getDeclaredFields();
}
/**
* Gets the list of all fields, both directly
* declared and inherited.
*
* @return non-null; the list of all fields
*/
public Field[] getAllFields() {
if (allFields == null) {
allFields = getFullListOfFields(false);
}
return allFields;
}
/**
* Gets the list of all public fields, both directly
* declared and inherited.
*
* @return non-null; the list of all public fields
*/
public Field[] getAllPublicFields() {
if (allPublicFields == null) {
allPublicFields = getFullListOfFields(true);
}
return allPublicFields;
}
/*
* Returns the list of fields without performing any security checks
* first. This includes the fields inherited from superclasses. If no
* fields exist at all, an empty array is returned.
*
* @param publicOnly reflects whether we want only public fields
* or all of them
* @return the list of fields
*/
private Field[] getFullListOfFields(boolean publicOnly) {
ArrayList<Field> fields = new ArrayList<Field>();
HashSet<String> seen = new HashSet<String>();
findAllfields(clazz, fields, seen, publicOnly);
return fields.toArray(new Field[fields.size()]);
}
/**
* Collects the list of fields without performing any security checks
* first. This includes the fields inherited from superclasses and from
* all implemented interfaces. The latter may also implement multiple
* interfaces, so we (potentially) recursively walk through a whole tree of
* classes. If no fields exist at all, an empty array is returned.
*
* @param clazz non-null; class to inspect
* @param fields non-null; the target list to add the results to
* @param seen non-null; a set of signatures we've already seen
* @param publicOnly reflects whether we want only public fields
* or all of them
*/
private static void findAllfields(Class<?> clazz,
ArrayList<Field> fields, HashSet<String> seen,
boolean publicOnly) {
// Traverse class and superclasses, get rid of dupes by signature
while (clazz != null) {
for (Field field : clazz.getClassCache().getDeclaredFields(publicOnly)) {
String signature = field.toString();
if (!seen.contains(signature)) {
fields.add(field);
seen.add(signature);
}
}
// Traverse all interfaces, and do the same recursively.
Class<?>[] interfaces = clazz.getInterfaces();
for (Class<?> intf : interfaces) {
findAllfields(intf, fields, seen, publicOnly);
}
clazz = clazz.getSuperclass();
}
}
/**
* Finds and returns a field with a given name and signature. Use
* this with one of the field lists returned by instances of this class.
*
* @param list non-null; the list of fields to search through
* @return non-null; the matching field
* @throws NoSuchFieldException thrown if the field does not exist
*/
public static Field findFieldByName(Field[] list, String name)
throws NoSuchFieldException {
if (name == null) {
throw new NullPointerException("Field name must not be null.");
}
for (Field field : list) {
if (field.getName().equals(name)) {
return field;
}
}
throw new NoSuchFieldException(name);
}
/**
* Makes a deep copy of the given array of fields. This is useful
* when handing out arrays from the public API.
*
* <p><b>Note:</b> In such cases, it is insufficient to just make
* a shallow copy of the array, since field objects aren't
* immutable due to the existence of {@link
* AccessibleObject#setAccessible}.</p>
*
* @param orig non-null; array to copy
* @return non-null; a deep copy of the given array
*/
public static Field[] deepCopy(Field[] orig) {
int length = orig.length;
Field[] result = new Field[length];
for (int i = length - 1; i >= 0; i--) {
result[i] = REFLECT.clone(orig[i]);
}
return result;
}
/**
* Gets the enumerated value with a given name.
*
* @param name non-null; name of the value
* @return null-ok; the named enumerated value or <code>null</code>
* if this instance's class doesn't have such a value (including
* if this instance isn't in fact an enumeration)
*/
@SuppressWarnings("unchecked")
public T getEnumValue(String name) {
Enum[] values = (Enum[]) getEnumValuesByName();
if (values == null) {
return null;
}
// Binary search.
int min = 0;
int max = values.length - 1;
while (min <= max) {
/*
* The guessIdx calculation is equivalent to ((min + max)
* / 2) but won't go wonky when min and max are close to
* Integer.MAX_VALUE.
*/
int guessIdx = min + ((max - min) >> 1);
Enum guess = values[guessIdx];
int cmp = name.compareTo(guess.name());
if (cmp < 0) {
max = guessIdx - 1;
} else if (cmp > 0) {
min = guessIdx + 1;
} else {
return (T) guess;
}
}
return null;
}
/**
* Gets the array of enumerated values, sorted by name.
*
* @return null-ok; the value array, or <code>null</code> if this
* instance's class isn't in fact an enumeration
*/
public T[] getEnumValuesByName() {
if (enumValuesByName == null) {
T[] values = getEnumValuesInOrder();
if (values != null) {
values = (T[]) values.clone();
Arrays.sort((Enum<?>[]) values, ENUM_COMPARATOR);
/*
* Note: It's only safe (concurrency-wise) to set the
* instance variable after the array is properly sorted.
*/
enumValuesByName = values;
}
}
return enumValuesByName;
}
/**
* Gets the array of enumerated values, in their original declared
* order.
*
* @return null-ok; the value array, or <code>null</code> if this
* instance's class isn't in fact an enumeration
*/
public T[] getEnumValuesInOrder() {
if ((enumValuesInOrder == null) && clazz.isEnum()) {
enumValuesInOrder = callEnumValues();
}
return enumValuesInOrder;
}
/**
* Calls the static method <code>values()</code> on this
* instance's class, which is presumed to be a properly-formed
* enumeration class, using proper privilege hygiene.
*
* @return non-null; the array of values as reported by
* <code>value()</code>
*/
@SuppressWarnings("unchecked")
private T[] callEnumValues() {
Method method;
try {
Method[] methods = getDeclaredPublicMethods();
method = findMethodByName(methods, "values", (Class[]) null);
method = REFLECT.accessibleClone(method);
} catch (NoSuchMethodException ex) {
// This shouldn't happen if the class is a well-formed enum.
throw new UnsupportedOperationException(ex);
}
try {
return (T[]) method.invoke((Object[]) null);
} catch (IllegalAccessException ex) {
// This shouldn't happen because the method is "accessible."
throw new Error(ex);
} catch (InvocationTargetException ex) {
Throwable te = ex.getTargetException();
if (te instanceof RuntimeException) {
throw (RuntimeException) te;
} else if (te instanceof Error) {
throw (Error) te;
} else {
throw new Error(te);
}
}
}
/**
* Gets the reflection access object. This uses reflection to do
* so. My head is spinning.
*
* @return non-null; the reflection access object
*/
private static ReflectionAccess getReflectionAccess() {
/*
* Note: We can't do AccessibleObject.class.getCache() to
* get the cache, since that would cause a circularity in
* initialization. So instead, we do a direct call into the
* native side.
*/
Method[] methods =
Class.getDeclaredMethods(AccessibleObject.class, false);
try {
Method method = findMethodByName(methods, "getReflectionAccess",
(Class[]) null);
Class.setAccessibleNoCheck(method, true);
return (ReflectionAccess) method.invoke((Object[]) null);
} catch (NoSuchMethodException ex) {
/*
* This shouldn't happen because the method
* AccessibleObject.getReflectionAccess() really is defined
* in this module.
*/
throw new Error(ex);
} catch (IllegalAccessException ex) {
// This shouldn't happen because the method is "accessible."
throw new Error(ex);
} catch (InvocationTargetException ex) {
throw new Error(ex);
}
}
/**
* Comparator class for enumerated values. It compares strictly
* by name.
*/
private static class EnumComparator implements Comparator<Enum<?>> {
public int compare(Enum<?> e1, Enum<?> e2) {
return e1.name().compareTo(e2.name());
}
}
}