FileDocCategorySizeDatePackage
ClassCache.javaAPI DocAndroid 1.5 API23527Wed May 06 22:41:04 BST 2009java.lang

ClassCache.java

/*
 * 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 org.apache.harmony.kernel.vm.LangAccess;
import org.apache.harmony.kernel.vm.ReflectionAccess;

import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.HashSet;

/**
 * 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 declared methods */
    private volatile Method[] declaredMethods;

    /** null-ok; list of all public declared methods */
    private volatile Method[] declaredPublicMethods;

    /** null-ok; list of all methods, both direct and inherited */
    private volatile Method[] allMethods;

    /** null-ok; list of all public methods, both direct and inherited */
    private volatile Method[] allPublicMethods;

    /** 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;
        this.declaredMethods = null;
        this.declaredPublicMethods = null;
        this.allMethods = null;
        this.allPublicMethods = null;
        this.enumValuesInOrder = null;
        this.enumValuesByName = null;
        this.declaredFields = null;
        this.declaredPublicFields = null;
        this.allFields = null;
        this.allPublicFields = null;
    }

    /**
     * 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
     */
    public Method[] getDeclaredPublicMethods() {
        if (declaredPublicMethods == null) {
            declaredPublicMethods = Class.getDeclaredMethods(clazz, true);
        }

        return declaredPublicMethods;
    }

    /**
     * Gets either the list of declared methods or the list of declared
     * public methods.
     * 
     * @param publicOnly whether to only return public methods
     */
    public Method[] getDeclaredMethods(boolean publicOnly) {
        return publicOnly ? getDeclaredPublicMethods() : getDeclaredMethods();
    }

    /**
     * Gets the list of all methods, both directly
     * declared and inherited.
     * 
     * @return non-null; the list of all methods
     */
    public Method[] getAllMethods() {
        if (allMethods == null) {
            allMethods = getFullListOfMethods(false);
        }

        return allMethods;
    }

    /**
     * Gets the list of all public methods, both directly
     * declared and inherited.
     * 
     * @return non-null; the list of all public methods
     */
    public Method[] getAllPublicMethods() {
        if (allPublicMethods == null) {
            allPublicMethods = getFullListOfMethods(true);
        }

        return allPublicMethods;
    }

    /*
     * Returns the list of methods without performing any security checks
     * first. This includes the methods inherited from superclasses. If no
     * methods exist at all, an empty array is returned.
     * 
     * @param publicOnly reflects whether we want only public methods
     * or all of them
     * @return the list of methods 
     */
    private Method[] getFullListOfMethods(boolean publicOnly) {
        ArrayList<Method> methods = new ArrayList<Method>();
        HashSet<String> seen = new HashSet<String>();

        findAllMethods(clazz, methods, seen, publicOnly);
        
        return methods.toArray(new Method[methods.size()]);
    }

    /**
     * Collects the list of methods without performing any security checks
     * first. This includes the methods 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 methods exist at all, an empty array is returned.
     * 
     * @param clazz non-null; class to inspect
     * @param methods 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 methods
     * or all of them
     */
    private static void findAllMethods(Class<?> clazz,
            ArrayList<Method> methods, HashSet<String> seen,
            boolean publicOnly) {
        StringBuilder builder = new StringBuilder();
        Class<?> origClass = clazz;
        
        // Traverse class and superclasses, get rid of dupes by signature
        while (clazz != null) {
            Method[] declaredMethods =
                clazz.getClassCache().getDeclaredMethods(publicOnly);
            int length = declaredMethods.length;
            if (length != 0) {
                for (Method method : declaredMethods) {
                    builder.setLength(0);
                    builder.append(method.getName());
                    builder.append('(');
                    Class<?>[] types = method.getParameterTypes();
                    if (types.length != 0) {
                        builder.append(types[0].getName());
                        for (int j = 1; j < types.length; j++) {
                            builder.append(',');
                            builder.append(types[j].getName());
                        }
                    }
                    builder.append(')');
                    
                    String signature = builder.toString();
                    if (!seen.contains(signature)) {
                        methods.add(method);
                        seen.add(signature);
                    }
                }
            }
            
            clazz = clazz.getSuperclass();
        }
        
        // Traverse all interfaces, and do the same recursively.
        Class<?>[] interfaces = origClass.getInterfaces();
        for (Class<?> intf : interfaces) {
            findAllMethods(intf, methods, seen, publicOnly);
        }
    }

    /**
     * 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 (int i = list.length - 1; i >= 0; i--) {
            Method method = list[i];
            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) {
            Field[] declaredFields =
                    clazz.getClassCache().getDeclaredFields(publicOnly);
            for (Field field : declaredFields) {                    
                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 (int i = 0; i < list.length; i++) {
            Field field = list[i];
            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());
        }
    }
}