com.autobizlogic.abl.util.BeanUtil.java Source code

Java tutorial

Introduction

Here is the source code for com.autobizlogic.abl.util.BeanUtil.java

Source

package com.autobizlogic.abl.util;

import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.commons.beanutils.BeanMap;

import com.autobizlogic.abl.data.PersistentBean;
import com.autobizlogic.abl.metadata.MetaAttribute;
import com.autobizlogic.abl.metadata.MetaEntity;
import com.autobizlogic.abl.metadata.MetaRole;

/**
 * Utility methods to manipulate Java beans.
 */
public class BeanUtil {

    /**
     * Remember which get methods or fields have already been seen
     */
    private static final Map<String, AccessibleObject> getAccessibles = new ConcurrentHashMap<String, AccessibleObject>();

    /**
     * Remember which set methods or fields have already been seen
     */
    private static final Map<String, AccessibleObject> setAccessibles = new ConcurrentHashMap<String, AccessibleObject>();

    /**
     * Get a short string describing the given bean, in the form EntityName[primary-key]
     */
    @SuppressWarnings("rawtypes")
    public static String getBeanDescription(MetaEntity metaEntity, Object bean) {

        Map map = null;
        if (bean instanceof Map)
            map = (Map) bean;
        else
            map = new BeanMap(bean);

        String pkPropName = metaEntity.getIdentifierName();
        if (pkPropName != null) {
            Object pk = map.get(pkPropName);
            return metaEntity.getEntityName() + "[" + pk.toString() + "]";
        }

        return metaEntity.getEntityName() + "[composite key]";
    }

    /**
     * Get a string showing the given bean's full value (primary key and all attributes)
     */
    @SuppressWarnings("rawtypes")
    public static String getBeanLongDescription(MetaEntity metaEntity, Object bean) {

        StringBuffer result = new StringBuffer();

        result.append(getBeanDescription(metaEntity, bean));

        Map map = null;
        if (bean instanceof Map)
            map = (Map) bean;
        else
            map = new BeanMap(bean);

        result.append(" = [");
        Set<MetaAttribute> metaAttributes = metaEntity.getMetaAttributes();
        for (MetaAttribute metaAttribute : metaAttributes) {
            Object value = map.get(metaAttribute.getName());
            result.append(metaAttribute.getName());
            result.append("=");
            result.append(value);
            result.append(", ");
        }
        result.append("]");

        return result.toString();
    }

    /**
     * Get the value of the given property for the given bean.
     * @param bean The bean in question
     * @param propertyName The name of the property
     * @return The value of the property. If not found, an exception is thrown.
     */
    public static Object getBeanProperty(Object bean, String propertyName) {
        // First, try to find a getter method.
        Class<?> beanClass = ProxyUtil.getNonProxyClass(bean);

        String propertyKey = beanClass.getName() + "/" + propertyName;
        AccessibleObject getter = getAccessibles.get(propertyKey);
        if (getter != null) {
            if (getter instanceof Method) {
                return getValueWithMethod((Method) getter, bean);
            }

            return getValueWithField((Field) getter, bean);
        }

        String getterName = "get" + propertyName.substring(0, 1).toUpperCase();
        if (propertyName.length() > 1)
            getterName += propertyName.substring(1);
        String isName = "is" + propertyName.substring(0, 1).toUpperCase();
        if (propertyName.length() > 1)
            isName += propertyName.substring(1);

        Method getterMethod = getMethodFromClassWithInheritance(beanClass, getterName, null);
        if (getterMethod == null)
            getterMethod = getMethodFromClassWithInheritance(beanClass, isName, null);
        if (getterMethod != null) {
            Object value = getValueWithMethod(getterMethod, bean);
            getAccessibles.put(propertyKey, getterMethod);
            return value;
        }

        // No getter method found: go straight for the field
        Field field = getFieldFromClassWithInheritance(beanClass, propertyName, null);
        if (field == null) {
            String msg = "Unable to get property " + propertyName + " of bean of type " + beanClass.getName()
                    + " because no accessible get/is method was found, and no accessible field was found either";
            if (beanClass.getSimpleName().contains("Hash"))
                msg += "\nfrom bean: " + ObjectUtil.safeToString(bean);
            throw new RuntimeException(msg);
        }
        Object value = getValueWithField(field, bean);
        getAccessibles.put(propertyKey, field);
        return value;
    }

    /**
     * Set the given bean's given property to the value in the given PersistentBean. If unsuccessful, an exception
     * is thrown.
     */
    public static void setBeanPropertyToPersistentBean(Object bean, String propertyName, PersistentBean value) {
        if (value.getMetaEntity().isPojo())
            BeanUtil.setBeanProperty(bean, propertyName, value.getBean());
        else if (value.getMetaEntity().isMap())
            BeanUtil.setBeanProperty(bean, propertyName, value.getMap());
        else
            throw new RuntimeException("Internal error: object is neither pojo nor map");
    }

    /**
     * Set the given bean's given property to the given value. If unsuccessful, an exception
     * is thrown.
     */
    public static void setBeanProperty(Object bean, String propertyName, Object value) {

        // First, try to find a setter method.
        Class<?> beanClass = ProxyUtil.getNonProxyClass(bean);

        String propertyKey = beanClass.getName() + "/" + propertyName;
        AccessibleObject setter = setAccessibles.get(propertyKey);
        if (setter != null) {
            if (setter instanceof Method) {
                setValueWithMethod((Method) setter, bean, value);
            } else {
                setValueWithField((Field) setter, bean, value);
            }
            return;
        }

        String setterName = "set" + propertyName.substring(0, 1).toUpperCase();
        if (propertyName.length() > 1)
            setterName += propertyName.substring(1);

        Class<?> valueClass = null;
        if (value != null)
            valueClass = ProxyUtil.getNonProxyClass(value);
        Method setterMethod = getMethodFromClassWithInheritance(beanClass, setterName, valueClass);
        if (setterMethod != null) {
            setValueWithMethod(setterMethod, bean, value);
            setAccessibles.put(propertyKey, setterMethod);
            return;
        }

        // No setter method found: go straight for the field
        Field field = getFieldFromClassWithInheritance(beanClass, propertyName, valueClass);
        if (field == null)
            throw new RuntimeException("Unable to set property " + propertyName + " of bean of type "
                    + beanClass.getName()
                    + " because no accessible set method was found, and no accessible field was found either");
        setValueWithField(field, bean, value);
        setAccessibles.put(propertyKey, field);
    }

    /**
     * Determine whether the given bean has a property with the given name.
     * This does not follow the Javabeans conventions: first we look for a getX or isX
     * method, then if we don't find it, we look for a field.
     */
    public static boolean beanHasProperty(Object bean, String propertyName) {
        Class<?> beanClass = ProxyUtil.getNonProxyClass(bean);

        // Is the accessor already cached?
        String propertyKey = beanClass.getName() + "/" + propertyName;
        AccessibleObject getter = getAccessibles.get(propertyKey);
        if (getter != null)
            return true;

        String getterName = "get" + propertyName.substring(0, 1).toUpperCase();
        if (propertyName.length() > 1)
            getterName += propertyName.substring(1);
        String isName = "is" + propertyName.substring(0, 1).toUpperCase();
        if (propertyName.length() > 1)
            isName += propertyName.substring(1);

        Method getterMethod = getMethodFromClassWithInheritance(beanClass, getterName, null);
        if (getterMethod == null)
            getterMethod = getMethodFromClassWithInheritance(beanClass, isName, null);
        if (getterMethod != null) {
            getAccessibles.put(propertyKey, getterMethod);
            return true;
        }

        // No get/is method found: go straight for the field
        Field field = getFieldFromClassWithInheritance(beanClass, propertyName, null);
        if (field == null) {
            return false;
        }
        getAccessibles.put(propertyKey, field);
        return true;
    }

    /**
     * Given two beans of the same entity, determine whether they are equal. This is done by
     * looking at the value of all the attributes, and the value of all the parent
     * relationships. If there is any difference, false is returned.
     */
    @SuppressWarnings("rawtypes")
    public static boolean beansAreEqual(MetaEntity metaEntity, Object bean1, Object bean2) {

        // First the easy cases
        if (bean1 == null && bean2 == null)
            return true;
        if (bean1 == null && bean2 != null)
            return false;
        if (bean1 != null && bean2 == null)
            return false;
        if (bean1 == bean2) // You never know...
            return true;

        Map beanMap1 = null;
        Map beanMap2 = null;
        if (metaEntity.isMap()) {
            beanMap1 = (Map) bean1;
            beanMap2 = (Map) bean2;
        } else {
            beanMap1 = new BeanMap(bean1);
            beanMap2 = new BeanMap(bean2);
        }

        // Compare the attributes - return at first difference
        Set<MetaAttribute> metaAttributes = metaEntity.getMetaAttributes();
        for (MetaAttribute metaAttribute : metaAttributes) {
            Object val1 = beanMap1.get(metaAttribute.getName());
            Object val2 = beanMap2.get(metaAttribute.getName());
            if (val1 == null && val2 == null)
                continue;
            if (val1 != null && val2 != null && val1 == val2)
                continue;
            if (val1 != null && val2 != null && val1.equals(val2))
                continue;

            return false;
        }

        Set<MetaRole> metaRoles = metaEntity.getRolesFromChildToParents();
        for (MetaRole metaRole : metaRoles) {
            Object val1 = beanMap1.get(metaRole.getRoleName());
            Object val2 = beanMap2.get(metaRole.getRoleName());
            if (val1 == null && val2 == null)
                continue;
            if (val1 != null && val2 != null && val1 == val2)
                continue;
            if (val1 != null && val2 != null && val1.equals(val2))
                continue;

            return false;
        }

        return true;
    }

    /**
     * Reset all caches. This is called internally when the logic classes are updated.
     */
    public static void resetCaches() {
        getAccessibles.clear();
        setAccessibles.clear();
    }

    //////////////////////////////////////////////////////////////////////////////////////
    // Method methods

    private static Object getValueWithMethod(Method getterMethod, Object bean) {

        try {
            getterMethod.setAccessible(true);
            return getterMethod.invoke(bean);
        } catch (Exception ex) {
            throw new RuntimeException("Unable to use get method " + getterMethod.getName() + " on instance of "
                    + bean.getClass().getName(), ex);
        }
    }

    private static void setValueWithMethod(Method setterMethod, Object bean, Object value) {

        try {
            setterMethod.setAccessible(true);
            setterMethod.invoke(bean, value);
        } catch (Exception ex) {
            throw new RuntimeException("Unable to use set method " + setterMethod.getName() + " on instance of "
                    + bean.getClass().getName(), ex);
        }
    }

    /**
     * Get the method with the given name from the given class, provided that it takes one argument
     * of the provided type.
     * @param cls The class who should have (or inherit) the method
     * @param methodName The name of the method
     * @param argClass If provided, the type of the sole argument to the method. If null, no argument is assumed.
     * @param onlyProtectedAndHigher If true, we will ignore private methods in the superclasses.
     * @return The method if found, otherwise null.
     */
    private static Method getMethodFromClass(Class<?> cls, String methodName, Class<?> argClass,
            boolean onlyProtectedAndHigher) {

        Method[] allMethods = cls.getDeclaredMethods();
        for (Method meth : allMethods) {
            if (!meth.getName().equals(methodName))
                continue;

            if (onlyProtectedAndHigher) {
                int modifiers = meth.getModifiers();
                if (Modifier.isPrivate(modifiers))
                    continue;
            }

            if (argClass != null) {
                Class<?>[] paramTypes = meth.getParameterTypes();
                if (paramTypes.length != 1)
                    continue;

                Class<?> genericType = getGenericType(paramTypes[0]);
                if (!genericType.isAssignableFrom(argClass))
                    continue;
            }

            // Note that if we're trying to set a value to null, we obviously cannot check the
            // signature for overloading, and therefore we'll return the first method which takes
            // one parameter. I think that's not that unreasonable, but it could conceivably break
            // if someone does funny things with their bean.

            return meth;
        }

        return null;
    }

    private static Method getMethodFromClassWithInheritance(Class<?> cls, String methodName, Class<?> argClass) {

        Method theMethod = getMethodFromClass(cls, methodName, argClass, false);
        while (theMethod == null && (!cls.getName().equals("java.lang.Object"))) {
            cls = cls.getSuperclass();
            theMethod = getMethodFromClass(cls, methodName, argClass, true);
        }
        return theMethod;
    }

    //////////////////////
    // Field methods

    protected static Object getValueWithField(Field field, Object bean) {

        try {
            field.setAccessible(true);
            return field.get(bean);
        } catch (Exception ex) {
            throw new RuntimeException("Unable to get property " + field.getName() + " on instance of "
                    + bean.getClass().getName() + " using field " + field.getName(), ex);
        }
    }

    protected static void setValueWithField(Field field, Object bean, Object value) {

        try {
            field.setAccessible(true);
            field.set(bean, value);
        } catch (Exception ex) {
            String extraInfo = "";
            if (value != null) {
                // If the two classes have the same name, it's probably a classloader problem,
                // so we generate more informative output to help debug
                if (field.getType().getName().equals(value.getClass().getName())) {
                    extraInfo = ". It looks like the two classes have the same name, so this is "
                            + "probably a classloader issue. The bean field's class comes from "
                            + field.getType().getClassLoader() + ", the bean itself comes from "
                            + bean.getClass().getClassLoader() + ", the value class comes from "
                            + value.getClass().getClassLoader();
                }
            }
            throw new RuntimeException("Unable to set property " + field.getName() + " on instance of "
                    + bean.getClass().getName() + " using field " + field.getName() + extraInfo, ex);
        }
    }

    private static Field getFieldFromClass(Class<?> cls, String fieldName, Class<?> argClass,
            boolean onlyProtectedAndHigher) {
        Field[] allFields = cls.getDeclaredFields();
        for (Field field : allFields) {
            if (!field.getName().equals(fieldName))
                continue;
            int modifiers = field.getModifiers();
            if (onlyProtectedAndHigher && Modifier.isPrivate(modifiers))
                continue;
            if (argClass != null) {
                Class<?> genericType = getGenericType(field.getType());
                if (!genericType.isAssignableFrom(argClass)) {
                    String extraInfo = "";
                    // If the two classes have the same name, it's probably a classloader problem,
                    // so we generate more informative output to help debug
                    if (field.getType().getName().equals(argClass.getName())) {
                        extraInfo = ". It looks like the two classes have the same name, so this is "
                                + "probably a classloader issue. The bean field's class comes from "
                                + field.getType().getClassLoader() + ", the other class comes from "
                                + argClass.getClassLoader();
                    }
                    throw new RuntimeException("Bean field " + fieldName + " of class " + cls.getName()
                            + " is of the wrong type (" + field.getType().getName() + ") for the given argument, "
                            + "which is of type " + argClass.getName() + extraInfo);
                }
            }

            return field;
        }

        return null;
    }

    private static Field getFieldFromClassWithInheritance(Class<?> cls, String fieldName, Class<?> argClass) {
        Field field = getFieldFromClass(cls, fieldName, argClass, false);
        while (field == null && (!cls.getName().equals("java.lang.Object"))) {
            cls = cls.getSuperclass();
            field = getFieldFromClass(cls, fieldName, argClass, true);
        }
        return field;
    }

    /**
     * Unfortunately, int.class.isAssignableFrom(Integer.class) returns false, so we need
     * to convert primitive types to their class equivalent.
     * @param cls Any class
     * @return If the class is a primitive class (e.g. int.class, boolean.class, etc...)
     * return the class equivalent (e.g. Integer.class, Boolean.class, etc...), otherwise
     * return the given class.
     */
    private static Class<?> getGenericType(Class<?> cls) {
        if (cls.equals(byte.class))
            return Byte.class;
        if (cls.equals(short.class))
            return Short.class;
        if (cls.equals(int.class))
            return Integer.class;
        if (cls.equals(long.class))
            return Long.class;
        if (cls.equals(float.class))
            return Float.class;
        if (cls.equals(double.class))
            return Double.class;
        if (cls.equals(boolean.class))
            return Boolean.class;
        if (cls.equals(char.class))
            return Character.class;
        return cls;
    }
}

/*
 * The contents of this file are subject to the Automated Business Logic Public License Version 1.0 (the "License"),
 * which is derived from the Mozilla Public License version 1.1. You may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at http://www.automatedbusinesslogic.com/license/public-license
 *
 * Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, 
 * either express or implied. See the License for the specific language governing rights and limitations under the License.
 */