org.hibernate.internal.util.ReflectHelper.java Source code

Java tutorial

Introduction

Here is the source code for org.hibernate.internal.util.ReflectHelper.java

Source

/*
 * Hibernate, Relational Persistence for Idiomatic Java
 *
 * License: GNU Lesser General Public License (LGPL), version 2.1 or later.
 * See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
 */
package org.hibernate.internal.util;

import java.beans.Introspector;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Locale;
import java.util.regex.Pattern;
import javax.persistence.Transient;

import org.hibernate.AssertionFailure;
import org.hibernate.MappingException;
import org.hibernate.PropertyNotFoundException;
import org.hibernate.boot.registry.classloading.spi.ClassLoaderService;
import org.hibernate.boot.registry.classloading.spi.ClassLoadingException;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.property.access.internal.PropertyAccessStrategyMixedImpl;
import org.hibernate.property.access.spi.Getter;
import org.hibernate.type.PrimitiveType;
import org.hibernate.type.Type;

/**
 * Utility class for various reflection operations.
 *
 * @author Gavin King
 * @author Steve Ebersole
 * @author Chris Cranford
 */
@SuppressWarnings("unchecked")
public final class ReflectHelper {

    private static final Pattern JAVA_CONSTANT_PATTERN = Pattern.compile(
            "[a-z\\d]+\\.([A-Z]{1}[a-z\\d]+)+\\$?([A-Z]{1}[a-z\\d]+)*\\.[A-Z_\\$]+",
            Pattern.UNICODE_CHARACTER_CLASS);

    public static final Class[] NO_PARAM_SIGNATURE = new Class[0];
    public static final Object[] NO_PARAMS = new Object[0];

    public static final Class[] SINGLE_OBJECT_PARAM_SIGNATURE = new Class[] { Object.class };

    private static final Method OBJECT_EQUALS;
    private static final Method OBJECT_HASHCODE;

    static {
        Method eq;
        Method hash;
        try {
            eq = extractEqualsMethod(Object.class);
            hash = extractHashCodeMethod(Object.class);
        } catch (Exception e) {
            throw new AssertionFailure("Could not find Object.equals() or Object.hashCode()", e);
        }
        OBJECT_EQUALS = eq;
        OBJECT_HASHCODE = hash;
    }

    /**
     * Disallow instantiation of ReflectHelper.
     */
    private ReflectHelper() {
    }

    /**
     * Encapsulation of getting hold of a class's {@link Object#equals equals}  method.
     *
     * @param clazz The class from which to extract the equals method.
     * @return The equals method reference
     * @throws NoSuchMethodException Should indicate an attempt to extract equals method from interface.
     */
    public static Method extractEqualsMethod(Class clazz) throws NoSuchMethodException {
        return clazz.getMethod("equals", SINGLE_OBJECT_PARAM_SIGNATURE);
    }

    /**
     * Encapsulation of getting hold of a class's {@link Object#hashCode hashCode} method.
     *
     * @param clazz The class from which to extract the hashCode method.
     * @return The hashCode method reference
     * @throws NoSuchMethodException Should indicate an attempt to extract hashCode method from interface.
     */
    public static Method extractHashCodeMethod(Class clazz) throws NoSuchMethodException {
        return clazz.getMethod("hashCode", NO_PARAM_SIGNATURE);
    }

    /**
     * Determine if the given class defines an {@link Object#equals} override.
     *
     * @param clazz The class to check
     * @return True if clazz defines an equals override.
     */
    public static boolean overridesEquals(Class clazz) {
        Method equals;
        try {
            equals = extractEqualsMethod(clazz);
        } catch (NoSuchMethodException nsme) {
            return false; //its an interface so we can't really tell anything...
        }
        return !OBJECT_EQUALS.equals(equals);
    }

    /**
     * Determine if the given class defines a {@link Object#hashCode} override.
     *
     * @param clazz The class to check
     * @return True if clazz defines an hashCode override.
     */
    public static boolean overridesHashCode(Class clazz) {
        Method hashCode;
        try {
            hashCode = extractHashCodeMethod(clazz);
        } catch (NoSuchMethodException nsme) {
            return false; //its an interface so we can't really tell anything...
        }
        return !OBJECT_HASHCODE.equals(hashCode);
    }

    /**
     * Determine if the given class implements the given interface.
     *
     * @param clazz The class to check
     * @param intf The interface to check it against.
     * @return True if the class does implement the interface, false otherwise.
     */
    public static boolean implementsInterface(Class clazz, Class intf) {
        assert intf.isInterface() : "Interface to check was not an interface";
        return intf.isAssignableFrom(clazz);
    }

    /**
     * Perform resolution of a class name.
     * <p/>
     * Here we first check the context classloader, if one, before delegating to
     * {@link Class#forName(String, boolean, ClassLoader)} using the caller's classloader
     *
     * @param name The class name
     * @param caller The class from which this call originated (in order to access that class's loader).
     * @return The class reference.
     * @throws ClassNotFoundException From {@link Class#forName(String, boolean, ClassLoader)}.
     */
    public static Class classForName(String name, Class caller) throws ClassNotFoundException {
        try {
            ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
            if (classLoader != null) {
                return classLoader.loadClass(name);
            }
        } catch (Throwable ignore) {
        }
        return Class.forName(name, true, caller.getClassLoader());
    }

    /**
     * Perform resolution of a class name.
     * <p/>
     * Same as {@link #classForName(String, Class)} except that here we delegate to
     * {@link Class#forName(String)} if the context classloader lookup is unsuccessful.
     *
     * @param name The class name
     * @return The class reference.
     *
     * @throws ClassNotFoundException From {@link Class#forName(String)}.
     *
     * @deprecated Depending on context, either {@link org.hibernate.boot.registry.classloading.spi.ClassLoaderService}
     * or {@link org.hibernate.boot.spi.ClassLoaderAccess} should be preferred
     */
    @Deprecated
    public static Class classForName(String name) throws ClassNotFoundException {
        try {
            ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
            if (classLoader != null) {
                return classLoader.loadClass(name);
            }
        } catch (Throwable ignore) {
        }
        return Class.forName(name);
    }

    /**
     * Is this member publicly accessible.
     *
     * @param clazz The class which defines the member
     * @param member The memeber.
     * @return True if the member is publicly accessible, false otherwise.
     */
    public static boolean isPublic(Class clazz, Member member) {
        return Modifier.isPublic(member.getModifiers()) && Modifier.isPublic(clazz.getModifiers());
    }

    /**
     * Attempt to resolve the specified property type through reflection.
     *
     * @param className The name of the class owning the property.
     * @param name The name of the property.
     * @param classLoaderService ClassLoader services
     *
     * @return The type of the property.
     *
     * @throws MappingException Indicates we were unable to locate the property.
     */
    public static Class reflectedPropertyClass(String className, String name, ClassLoaderService classLoaderService)
            throws MappingException {
        try {
            Class clazz = classLoaderService.classForName(className);
            return getter(clazz, name).getReturnType();
        } catch (ClassLoadingException e) {
            throw new MappingException("class " + className + " not found while looking for property: " + name, e);
        }
    }

    /**
     * Attempt to resolve the specified property type through reflection.
     *
     * @param clazz The class owning the property.
     * @param name The name of the property.
     * @return The type of the property.
     * @throws MappingException Indicates we were unable to locate the property.
     */
    public static Class reflectedPropertyClass(Class clazz, String name) throws MappingException {
        return getter(clazz, name).getReturnType();
    }

    private static Getter getter(Class clazz, String name) throws MappingException {
        return PropertyAccessStrategyMixedImpl.INSTANCE.buildPropertyAccess(clazz, name).getGetter();
    }

    public static Object getConstantValue(String name, SessionFactoryImplementor factory) {
        boolean conventionalJavaConstants = factory.getSessionFactoryOptions().isConventionalJavaConstants();
        Class clazz;
        try {
            if (conventionalJavaConstants && !JAVA_CONSTANT_PATTERN.matcher(name).find()) {
                return null;
            }
            ClassLoaderService classLoaderService = factory.getServiceRegistry()
                    .getService(ClassLoaderService.class);
            clazz = classLoaderService.classForName(StringHelper.qualifier(name));
        } catch (Throwable t) {
            return null;
        }
        try {
            return clazz.getField(StringHelper.unqualify(name)).get(null);
        } catch (Throwable t) {
            return null;
        }
    }

    /**
     * Retrieve the default (no arg) constructor from the given class.
     *
     * @param clazz The class for which to retrieve the default ctor.
     * @return The default constructor.
     * @throws PropertyNotFoundException Indicates there was not publicly accessible, no-arg constructor (todo : why PropertyNotFoundException???)
     */
    public static <T> Constructor<T> getDefaultConstructor(Class<T> clazz) throws PropertyNotFoundException {
        if (isAbstractClass(clazz)) {
            return null;
        }

        try {
            Constructor<T> constructor = clazz.getDeclaredConstructor(NO_PARAM_SIGNATURE);
            ensureAccessibility(constructor);
            return constructor;
        } catch (NoSuchMethodException nme) {
            throw new PropertyNotFoundException(
                    "Object class [" + clazz.getName() + "] must declare a default (no-argument) constructor");
        }
    }

    /**
     * Determine if the given class is declared abstract.
     *
     * @param clazz The class to check.
     * @return True if the class is abstract, false otherwise.
     */
    public static boolean isAbstractClass(Class clazz) {
        int modifier = clazz.getModifiers();
        return Modifier.isAbstract(modifier) || Modifier.isInterface(modifier);
    }

    /**
     * Determine is the given class is declared final.
     *
     * @param clazz The class to check.
     * @return True if the class is final, flase otherwise.
     */
    public static boolean isFinalClass(Class clazz) {
        return Modifier.isFinal(clazz.getModifiers());
    }

    /**
     * Retrieve a constructor for the given class, with arguments matching the specified Hibernate mapping
     * {@link Type types}.
     *
     * @param clazz The class needing instantiation
     * @param types The types representing the required ctor param signature
     * @return The matching constructor.
     * @throws PropertyNotFoundException Indicates we could not locate an appropriate constructor (todo : again with PropertyNotFoundException???)
     */
    public static Constructor getConstructor(Class clazz, Type[] types) throws PropertyNotFoundException {
        final Constructor[] candidates = clazz.getConstructors();
        Constructor constructor = null;
        int numberOfMatchingConstructors = 0;
        for (final Constructor candidate : candidates) {
            final Class[] params = candidate.getParameterTypes();
            if (params.length == types.length) {
                boolean found = true;
                for (int j = 0; j < params.length; j++) {
                    final boolean ok = types[j] == null || params[j].isAssignableFrom(types[j].getReturnedClass())
                            || (types[j] instanceof PrimitiveType
                                    && params[j] == ((PrimitiveType) types[j]).getPrimitiveClass());
                    if (!ok) {
                        found = false;
                        break;
                    }
                }
                if (found) {
                    numberOfMatchingConstructors++;
                    ensureAccessibility(candidate);
                    constructor = candidate;
                }
            }
        }

        if (numberOfMatchingConstructors == 1) {
            return constructor;
        }
        throw new PropertyNotFoundException("no appropriate constructor in class: " + clazz.getName());

    }

    public static Method getMethod(Class clazz, Method method) {
        try {
            return clazz.getMethod(method.getName(), method.getParameterTypes());
        } catch (Exception e) {
            return null;
        }
    }

    public static Field findField(Class containerClass, String propertyName) {
        if (containerClass == null) {
            throw new IllegalArgumentException(
                    "Class on which to find field [" + propertyName + "] cannot be null");
        } else if (containerClass == Object.class) {
            throw new IllegalArgumentException(
                    "Illegal attempt to locate field [" + propertyName + "] on Object.class");
        }

        Field field = locateField(containerClass, propertyName);

        if (field == null) {
            throw new PropertyNotFoundException(String.format(Locale.ROOT,
                    "Could not locate field name [%s] on class [%s]", propertyName, containerClass.getName()));
        }

        ensureAccessibility(field);

        return field;
    }

    public static void ensureAccessibility(AccessibleObject accessibleObject) {
        if (accessibleObject.isAccessible()) {
            return;
        }

        accessibleObject.setAccessible(true);
    }

    private static Field locateField(Class clazz, String propertyName) {
        if (clazz == null || Object.class.equals(clazz)) {
            return null;
        }

        try {
            Field field = clazz.getDeclaredField(propertyName);
            if (!isStaticField(field)) {
                return field;
            }
            return locateField(clazz.getSuperclass(), propertyName);
        } catch (NoSuchFieldException nsfe) {
            return locateField(clazz.getSuperclass(), propertyName);
        }
    }

    private static boolean isStaticField(Field field) {
        return field != null && (field.getModifiers() & Modifier.STATIC) == Modifier.STATIC;
    }

    public static Method findGetterMethod(Class containerClass, String propertyName) {
        Class checkClass = containerClass;
        Method getter = null;

        // check containerClass, and then its super types (if any)
        while (getter == null && checkClass != null) {
            if (checkClass.equals(Object.class)) {
                break;
            }

            getter = getGetterOrNull(checkClass, propertyName);

            // if no getter found yet, check all implemented interfaces
            if (getter == null) {
                getter = getGetterOrNull(checkClass.getInterfaces(), propertyName);
            }

            checkClass = checkClass.getSuperclass();
        }

        if (getter == null) {
            throw new PropertyNotFoundException(String.format(Locale.ROOT,
                    "Could not locate getter method for property [%s#%s]", containerClass.getName(), propertyName));
        }

        ensureAccessibility(getter);

        return getter;
    }

    private static Method getGetterOrNull(Class[] interfaces, String propertyName) {
        Method getter = null;
        for (int i = 0; getter == null && i < interfaces.length; ++i) {
            final Class anInterface = interfaces[i];
            getter = getGetterOrNull(anInterface, propertyName);
            if (getter == null) {
                // if no getter found yet, check all implemented interfaces of interface
                getter = getGetterOrNull(anInterface.getInterfaces(), propertyName);
            }
        }
        return getter;
    }

    private static Method getGetterOrNull(Class containerClass, String propertyName) {
        for (Method method : containerClass.getDeclaredMethods()) {
            // if the method has parameters, skip it
            if (method.getParameterCount() != 0) {
                continue;
            }

            // if the method is a "bridge", skip it
            if (method.isBridge()) {
                continue;
            }

            if (method.getAnnotation(Transient.class) != null) {
                continue;
            }

            if (Modifier.isStatic(method.getModifiers())) {
                continue;
            }

            final String methodName = method.getName();

            // try "get"
            if (methodName.startsWith("get")) {
                final String stemName = methodName.substring(3);
                final String decapitalizedStemName = Introspector.decapitalize(stemName);
                if (stemName.equals(propertyName) || decapitalizedStemName.equals(propertyName)) {
                    verifyNoIsVariantExists(containerClass, propertyName, method, stemName);
                    return method;
                }

            }

            // if not "get", then try "is"
            if (methodName.startsWith("is")) {
                final String stemName = methodName.substring(2);
                String decapitalizedStemName = Introspector.decapitalize(stemName);
                if (stemName.equals(propertyName) || decapitalizedStemName.equals(propertyName)) {
                    verifyNoGetVariantExists(containerClass, propertyName, method, stemName);
                    return method;
                }
            }
        }

        return null;
    }

    private static void verifyNoIsVariantExists(Class containerClass, String propertyName, Method getMethod,
            String stemName) {
        // verify that the Class does not also define a method with the same stem name with 'is'
        try {
            final Method isMethod = containerClass.getDeclaredMethod("is" + stemName);
            if (!Modifier.isStatic(isMethod.getModifiers()) && isMethod.getAnnotation(Transient.class) == null) {
                // No such method should throw the caught exception.  So if we get here, there was
                // such a method.
                checkGetAndIsVariants(containerClass, propertyName, getMethod, isMethod);
            }
        } catch (NoSuchMethodException ignore) {
        }
    }

    private static void checkGetAndIsVariants(Class containerClass, String propertyName, Method getMethod,
            Method isMethod) {
        // Check the return types.  If they are the same, its ok.  If they are different
        // we are in a situation where we could not reasonably know which to use.
        if (!isMethod.getReturnType().equals(getMethod.getReturnType())) {
            throw new MappingException(String.format(Locale.ROOT,
                    "In trying to locate getter for property [%s], Class [%s] defined "
                            + "both a `get` [%s] and `is` [%s] variant",
                    propertyName, containerClass.getName(), getMethod.toString(), isMethod.toString()));
        }
    }

    private static void verifyNoGetVariantExists(Class containerClass, String propertyName, Method isMethod,
            String stemName) {
        // verify that the Class does not also define a method with the same stem name with 'is'
        try {
            final Method getMethod = containerClass.getDeclaredMethod("get" + stemName);
            // No such method should throw the caught exception.  So if we get here, there was
            // such a method.
            if (!Modifier.isStatic(getMethod.getModifiers()) && getMethod.getAnnotation(Transient.class) == null) {
                checkGetAndIsVariants(containerClass, propertyName, getMethod, isMethod);
            }
        } catch (NoSuchMethodException ignore) {
        }
    }

    public static Method getterMethodOrNull(Class containerJavaType, String propertyName) {
        try {
            return findGetterMethod(containerJavaType, propertyName);
        } catch (PropertyNotFoundException e) {
            return null;
        }
    }

    public static Method setterMethodOrNull(final Class containerClass, final String propertyName,
            final Class propertyType) {
        Class checkClass = containerClass;
        Method setter = null;

        // check containerClass, and then its super types (if any)
        while (setter == null && checkClass != null) {
            if (checkClass.equals(Object.class)) {
                break;
            }

            setter = setterOrNull(checkClass, propertyName, propertyType);

            // if no setter found yet, check all implemented interfaces
            if (setter == null) {
                setter = setterOrNull(checkClass.getInterfaces(), propertyName, propertyType);
            } else {
                ensureAccessibility(setter);
            }

            checkClass = checkClass.getSuperclass();
        }
        return setter; // might be null
    }

    public static Method findSetterMethod(final Class containerClass, final String propertyName,
            final Class propertyType) {
        final Method setter = setterMethodOrNull(containerClass, propertyName, propertyType);
        if (setter == null) {
            throw new PropertyNotFoundException(String.format(Locale.ROOT,
                    "Could not locate setter method for property [%s#%s]", containerClass.getName(), propertyName));
        }
        return setter;
    }

    private static Method setterOrNull(Class[] interfaces, String propertyName, Class propertyType) {
        Method setter = null;
        for (int i = 0; setter == null && i < interfaces.length; ++i) {
            final Class anInterface = interfaces[i];
            setter = setterOrNull(anInterface, propertyName, propertyType);
            if (setter == null) {
                // if no setter found yet, check all implemented interfaces of interface
                setter = setterOrNull(anInterface.getInterfaces(), propertyName, propertyType);
            }
        }
        return setter;
    }

    private static Method setterOrNull(Class theClass, String propertyName, Class propertyType) {
        Method potentialSetter = null;

        for (Method method : theClass.getDeclaredMethods()) {
            final String methodName = method.getName();
            if (method.getParameterCount() == 1 && methodName.startsWith("set")) {
                final String testOldMethod = methodName.substring(3);
                final String testStdMethod = Introspector.decapitalize(testOldMethod);
                if (testStdMethod.equals(propertyName) || testOldMethod.equals(propertyName)) {
                    potentialSetter = method;
                    if (propertyType == null || method.getParameterTypes()[0].equals(propertyType)) {
                        break;
                    }
                }
            }
        }

        return potentialSetter;
    }

    /**
     * Similar to {@link #getterMethodOrNull}, except that here we are just looking for the
     * corresponding getter for a field (defined as field access) if one exists.
     *
     * We do not look at supers, although conceivably the super could declare the method
     * as an abstract - but again, that is such an edge case...
     */
    public static Method findGetterMethodForFieldAccess(Field field, String propertyName) {
        for (Method method : field.getDeclaringClass().getDeclaredMethods()) {
            // if the method has parameters, skip it
            if (method.getParameterCount() != 0) {
                continue;
            }

            if (Modifier.isStatic(method.getModifiers())) {
                continue;
            }

            if (!method.getReturnType().isAssignableFrom(field.getType())) {
                continue;
            }

            final String methodName = method.getName();

            // try "get"
            if (methodName.startsWith("get")) {
                final String stemName = methodName.substring(3);
                final String decapitalizedStemName = Introspector.decapitalize(stemName);
                if (stemName.equals(propertyName) || decapitalizedStemName.equals(propertyName)) {
                    return method;
                }

            }

            // if not "get", then try "is"
            if (methodName.startsWith("is")) {
                final String stemName = methodName.substring(2);
                String decapitalizedStemName = Introspector.decapitalize(stemName);
                if (stemName.equals(propertyName) || decapitalizedStemName.equals(propertyName)) {
                    return method;
                }
            }
        }

        return null;
    }
}