org.jaffa.util.BeanHelper.java Source code

Java tutorial

Introduction

Here is the source code for org.jaffa.util.BeanHelper.java

Source

/*
 * ====================================================================
 * JAFFA - Java Application Framework For All
 *
 * Copyright (C) 2002 JAFFA Development Group
 *
 *     This library is free software; you can redistribute it and/or
 *     modify it under the terms of the GNU Lesser General Public
 *     License as published by the Free Software Foundation; either
 *     version 2.1 of the License, or (at your option) any later version.
 *
 *     This library is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *     Lesser General Public License for more details.
 *
 *     You should have received a copy of the GNU Lesser General Public
 *     License along with this library; if not, write to the Free Software
 *     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * Redistribution and use of this software and associated documentation ("Software"),
 * with or without modification, are permitted provided that the following conditions are met:
 * 1.  Redistributions of source code must retain copyright statements and notices.
 *     Redistributions must also contain a copy of this document.
 * 2.  Redistributions in binary form must reproduce the above copyright notice,
 *     this list of conditions and the following disclaimer in the documentation
 *     and/or other materials provided with the distribution.
 * 3.  The name "JAFFA" must not be used to endorse or promote products derived from
 *     this Software without prior written permission. For written permission,
 *     please contact mail to: jaffagroup@yahoo.com.
 * 4.  Products derived from this Software may not be called "JAFFA" nor may "JAFFA"
 *     appear in their names without prior written permission.
 * 5.  Due credit should be given to the JAFFA Project (http://jaffa.sourceforge.net).
 *
 * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 */

package org.jaffa.util;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import org.apache.commons.beanutils.DynaBean;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.log4j.Logger;
import org.jaffa.datatypes.Currency;
import org.jaffa.datatypes.DataTypeMapper;
import org.jaffa.datatypes.IDateBase;
import org.jaffa.flexfields.FlexBean;
import org.jaffa.flexfields.IFlexFields;

/** This has convenience methods for dealing with Java Beans.
 */
public class BeanHelper {
    private static Logger log = Logger.getLogger(BeanHelper.class);

    /** This will inspect the specified java bean, and extract an Object from the beans
     * getXxx() method, where 'xxx' is the field name passed in.
     * A null will be returned in case there is any error in invoking the getter.
     * @param bean The Java Bean.
     * @param field The field.
     * @throws NoSuchMethodException if there is no getter for the input field.
     * @return the output of the getter for the field.
     */
    public static Object getField(Object bean, String field) throws NoSuchMethodException {
        java.lang.reflect.Method method = null;
        try {
            if (bean instanceof DynaBean) {
                try {
                    return PropertyUtils.getProperty(bean, field);
                } catch (NoSuchMethodException e) {
                    // If the bean is a FlexBean instance, then the field could exist on the associated persistentObject
                    if (bean instanceof FlexBean && ((FlexBean) bean).getPersistentObject() != null)
                        return PropertyUtils.getProperty(((FlexBean) bean).getPersistentObject(), field);
                    else
                        throw e;
                }
            } else {
                // Get the Java Bean Info
                java.beans.BeanInfo info = java.beans.Introspector.getBeanInfo(bean.getClass());
                if (info != null) {
                    // Get all the properties
                    java.beans.PropertyDescriptor[] pds = info.getPropertyDescriptors();
                    if (pds != null) {
                        // Loop for a matching method
                        for (PropertyDescriptor pd : pds) {
                            if (StringHelper.equalsIgnoreCaseFirstChar(pd.getName(), field)) {
                                // Match found....
                                method = pd.getReadMethod();
                                break;
                            }
                        }
                    }
                }
                if (method != null) {
                    return method.invoke(bean, new Object[] {});
                }

                // Finally, check the FlexBean
                if (bean instanceof IFlexFields) {
                    FlexBean flexBean = ((IFlexFields) bean).getFlexBean();
                    if (flexBean != null && flexBean.get(field) != null) {
                        return flexBean.get(field);
                    } else {
                        throw new NoSuchMethodException();
                    }
                }
            }
        } catch (NoSuchMethodException ex) {
            throw ex;
        } catch (Exception ex) {
            log.error("Introspection of Property " + field + " on Bean " + bean + " failed. Reason : "
                    + ex.getMessage(), ex);
            return null;
        }

        // If we reach here, the method was not found
        throw new NoSuchMethodException("Field Name = " + field);
    }

    /** Get the name of the reader method for the specified string.
     * For example 'hello' will return 'getHello'
     * @param field The field.
     * @return the name of the reader/getter method for the specified string.
     */
    public static String getReaderName(String field) {
        if (field == null)
            return null;
        return "get" + field.substring(0, 1).toUpperCase() + field.substring(1);
    }

    /** This method will introspect the bean & get the setter method for the input propertyName.
     * It will then try & convert the propertyValue to the appropriate datatype.
     * Finally it will invoke the setter.
     * @return A true indicates, the property was succesfully set to the passed value. A false indicates the property doesn't exist or the propertyValue passed is not compatible with the setter.
     * @param bean The bean class to be introspected.
     * @param propertyName The Property being searched for.
     * @param propertyValue The value to be set.
     * @throws IntrospectionException if an exception occurs during introspection.
     * @throws IllegalAccessException if the underlying method is inaccessible.
     * @throws InvocationTargetException if the underlying method throws an exception.
     */
    public static boolean setField(Object bean, String propertyName, String propertyValue)
            throws IntrospectionException, IllegalAccessException, InvocationTargetException {
        if (propertyValue != null && propertyValue.length() == 0)
            propertyValue = null;
        return setField(bean, propertyName, (Object) propertyValue);
    }

    /** This method will introspect the bean & get the setter method for the input propertyName.
     * It will then try & convert the propertyValue to the appropriate datatype.
     * Finally it will invoke the setter.
     * @return A true indicates, the property was succesfully set to the passed value. A false indicates the property doesn't exist or the propertyValue passed is not compatible with the setter.
     * @param bean The bean class to be introspected.
     * @param propertyName The Property being searched for.
     * @param propertyValue The value to be set.
     * @throws IntrospectionException if an exception occurs during introspection.
     * @throws IllegalAccessException if the underlying method is inaccessible.
     * @throws InvocationTargetException if the underlying method throws an exception.
     */
    public static boolean setField(Object bean, String propertyName, Object propertyValue)
            throws IntrospectionException, IllegalAccessException, InvocationTargetException {
        boolean result = false;
        if (bean instanceof DynaBean) {
            try {
                PropertyUtils.setProperty(bean, propertyName, propertyValue);
                result = true;
            } catch (NoSuchMethodException ignore) {
                // If the bean is a FlexBean instance, then the field could exist on the associated persistentObject
                if (bean instanceof FlexBean && ((FlexBean) bean).getPersistentObject() != null) {
                    try {
                        PropertyUtils.setProperty(((FlexBean) bean).getPersistentObject(), propertyName,
                                propertyValue);
                        result = true;
                    } catch (NoSuchMethodException ignore2) {
                    }
                }
            }
        } else {
            BeanInfo beanInfo = Introspector.getBeanInfo(bean.getClass());
            if (beanInfo != null) {
                PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
                if (pds != null) {
                    for (PropertyDescriptor pd : pds) {
                        if (StringHelper.equalsIgnoreCaseFirstChar(pd.getName(), propertyName)) {
                            Method m = pd.getWriteMethod();
                            Object convertedPropertyValue = null;
                            if (propertyValue != null) {
                                if (pd.getPropertyType().isEnum()) {
                                    convertedPropertyValue = findEnum(pd.getPropertyType(),
                                            propertyValue.toString());
                                } else {
                                    try {
                                        convertedPropertyValue = DataTypeMapper.instance().map(propertyValue,
                                                pd.getPropertyType());
                                    } catch (Exception e) {
                                        // do nothing
                                        break;
                                    }
                                }
                            }
                            m.invoke(bean, new Object[] { convertedPropertyValue });
                            result = true;
                            break;
                        }
                    }
                }
            }

            try {
                // Finally, check the FlexBean
                if (!result && bean instanceof IFlexFields && ((IFlexFields) bean).getFlexBean() != null
                        && ((IFlexFields) bean).getFlexBean().getDynaClass()
                                .getDynaProperty(propertyName) != null) {
                    ((IFlexFields) bean).getFlexBean().set(propertyName, propertyValue);
                    result = true;
                }
            } catch (Exception ignore) {
            }
        }
        return result;
    }

    /**
     * This will inspect the input beanClass, and obtain the type for the input property.
     * A null will be returned in case there is no such property.
     * @return the type for the property.
     * @param beanClass The bean class to be introspected.
     * @param propertyName The property name.
     * @throws IntrospectionException if an exception occurs during introspection.
     */
    public static Class getPropertyType(Class beanClass, String propertyName) throws IntrospectionException {
        BeanInfo beanInfo = Introspector.getBeanInfo(beanClass);
        if (beanInfo != null) {
            PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
            if (pds != null) {
                for (PropertyDescriptor pd : pds) {
                    if (StringHelper.equalsIgnoreCaseFirstChar(pd.getName(), propertyName))
                        return pd.getPropertyType();
                }
            }
        }
        return null;
    }

    /** Clones the input bean, performing a deep copy of its properties.
     * @param bean the bean to be cloned.
     * @param deepCloneForeignField if false, then the foreign-fields of a GraphDataObject will not be deep cloned.
     * @return a clone of the input bean.
     * @throws IllegalAccessException if the underlying method is inaccessible.
     * @throws InvocationTargetException if the underlying method throws an exception.
     * @throws InstantiationException if the bean cannot be instantiated.
     * @throws IntrospectionException if an exception occurs during introspection.
     */
    public static Object cloneBean(Object bean, boolean deepCloneForeignField) throws IllegalAccessException,
            InvocationTargetException, InstantiationException, IntrospectionException {
        if (bean == null)
            return bean;

        Class beanClass = bean.getClass();

        // Return the input as-is, if immutable
        if (beanClass == String.class || beanClass == Boolean.class || Number.class.isAssignableFrom(beanClass)
                || IDateBase.class.isAssignableFrom(beanClass) || Currency.class.isAssignableFrom(beanClass)
                || beanClass.isPrimitive()) {
            return bean;
        }

        // Handle an array
        if (beanClass.isArray()) {
            Class componentType = beanClass.getComponentType();
            int length = Array.getLength(bean);
            Object clone = Array.newInstance(componentType, length);
            for (int i = 0; i < length; i++) {
                Object arrayElementClone = cloneBean(Array.get(bean, i), deepCloneForeignField);
                Array.set(clone, i, arrayElementClone);
            }
            return clone;
        }

        // Handle a Collection
        if (bean instanceof Collection) {
            Collection clone = (Collection) bean.getClass().newInstance();
            for (Object collectionElement : (Collection) bean) {
                Object collectionElementClone = cloneBean(collectionElement, deepCloneForeignField);
                clone.add(collectionElementClone);
            }
            return clone;
        }

        // Handle a Map
        if (bean instanceof Map) {
            Map clone = (Map) bean.getClass().newInstance();
            for (Iterator i = ((Map) bean).entrySet().iterator(); i.hasNext();) {
                Map.Entry me = (Map.Entry) i.next();
                Object keyClone = cloneBean(me.getKey(), deepCloneForeignField);
                Object valueClone = cloneBean(me.getValue(), deepCloneForeignField);
                clone.put(keyClone, valueClone);
            }
            return clone;
        }

        // Invoke the 'public Object clone()' method, if available
        if (bean instanceof Cloneable) {
            try {
                Method cloneMethod = beanClass.getMethod("clone");
                return cloneMethod.invoke(bean);
            } catch (NoSuchMethodException e) {
                // do nothing
            }
        }

        // Create a clone using bean introspection
        Object clone = beanClass.newInstance();
        BeanInfo beanInfo = Introspector.getBeanInfo(beanClass);
        if (beanInfo != null) {
            PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
            if (pds != null) {
                // Obtain a GraphMapping; only if foreign-fields are not to be cloned
                //Use reflection to achieve the following:
                //GraphMapping graphMapping = !deepCloneForeignField && bean instanceof GraphDataObject ? MappingFactory.getInstance(bean) : null;
                Object graphMapping = null;
                Method isForeignFieldMethod = null;
                try {
                    if (!deepCloneForeignField
                            && Class.forName("org.jaffa.soa.graph.GraphDataObject").isInstance(bean)) {
                        graphMapping = Class.forName("org.jaffa.soa.dataaccess.MappingFactory")
                                .getMethod("getInstance", Object.class).invoke(null, bean);
                        isForeignFieldMethod = graphMapping.getClass().getMethod("isForeignField", String.class);
                    }
                } catch (Exception e) {
                    // do nothing since JaffaSOA may not be deployed
                    if (log.isDebugEnabled())
                        log.debug("Exception in obtaining the GraphMapping", e);
                }

                for (PropertyDescriptor pd : pds) {
                    if (pd.getReadMethod() != null && pd.getWriteMethod() != null) {
                        // Do not clone a foreign-field
                        Object property = pd.getReadMethod().invoke(bean);

                        //Use reflection to achieve the following:
                        //Object propertyClone = graphMapping != null && graphMapping.isForeignField(pd.getName()) ? property : cloneBean(property, deepCloneForeignField);
                        Object propertyClone = null;
                        boolean propertyCloned = false;
                        if (graphMapping != null && isForeignFieldMethod != null) {
                            try {
                                if ((Boolean) isForeignFieldMethod.invoke(graphMapping, pd.getName())) {
                                    propertyClone = property;
                                    propertyCloned = true;
                                }
                            } catch (Exception e) {
                                // do nothing since JaffaSOA may not be deployed
                                if (log.isDebugEnabled())
                                    log.debug("Exception in invoking GraphMapping.isForeignField()", e);
                            }
                        }
                        if (!propertyCloned)
                            propertyClone = cloneBean(property, deepCloneForeignField);

                        pd.getWriteMethod().invoke(clone, propertyClone);
                    }
                }
            }
        }
        return clone;
    }

    /** This method will introspect the beanClass & get the getter method for the input propertyName.
     * It will then try & convert the propertyValue to the appropriate datatype.
     * @param beanClass The bean class to be introspected.
     * @param propertyName The Property being searched for.
     * @param propertyValue The value to be converted.
     * @throws IntrospectionException if an exception occurs during introspection.
     * @throws IllegalArgumentException if the property cannot be found on the input class.
     * @throws ClassCastException if the input value can't be mapped to target class
     * @return a converted propertyValue compatible with the getter.
     * @deprecated Use DataTypeMapper.instance() instead.
     */
    public static Object convertDataType(Class beanClass, String propertyName, String propertyValue)
            throws IntrospectionException, IllegalArgumentException, ClassCastException {
        Object convertedPropertyValue = null;
        if (propertyValue != null) {
            boolean foundProperty = false;
            BeanInfo beanInfo = Introspector.getBeanInfo(beanClass);
            if (beanInfo != null) {
                PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
                if (pds != null) {
                    for (PropertyDescriptor pd : pds) {
                        if (StringHelper.equalsIgnoreCaseFirstChar(pd.getName(), propertyName)) {
                            foundProperty = true;
                            if (pd.getPropertyType().isEnum()) {
                                convertedPropertyValue = findEnum(pd.getPropertyType(), propertyValue.toString());
                                if (convertedPropertyValue == null)
                                    throw new IllegalArgumentException("Property " + beanClass.getName() + '.'
                                            + propertyName + ", cannot be set to the value " + propertyValue
                                            + ", since it is not defined in the Enum " + pd.getPropertyType());
                            } else {
                                convertedPropertyValue = DataTypeMapper.instance().map(propertyValue,
                                        pd.getPropertyType());
                            }
                            break;
                        }
                    }
                }
            }
            if (!foundProperty)
                throw new IllegalArgumentException(
                        "Could not find the the property: " + beanClass.getName() + '.' + propertyName);
        }
        return convertedPropertyValue;
    }

    /** This is a helper that can be used for debugging, it will return a list of all the public methods
     * on this class.
     */
    public static String listMethods(Class beanClass) {
        try {
            StringBuffer buf = new StringBuffer();
            Method[] methods = beanClass.getMethods();
            if (methods != null) {
                for (Method method : methods) {
                    buf.append("public ").append(method.getReturnType().getName()).append(' ')
                            .append(method.getName()).append("( ");
                    Class[] classes = method.getParameterTypes();
                    if (classes != null) {
                        for (Class clazz : classes)
                            buf.append(clazz.getName()).append(' ');
                    }
                    buf.append(")\n");
                }
            }
            return buf.toString();
        } catch (Exception e) {
            return e.getMessage();
        }
    }

    /** Iterates through the defined constants for the input Enum class.
     * Returns the constant that matches the input value.
     * @param enumClass The Enum class.
     * @param value The value whose corresponding Enum constant is to be returned.
     * @return the Enum constant that matches the input value. A null is returned if no match is found or if the input class is not an Enum.
     */
    private static Object findEnum(Class enumClass, String value) {
        if (enumClass.isEnum()) {
            Object[] enumConstants = enumClass.getEnumConstants();
            if (enumConstants != null) {
                for (Object enumConstant : enumConstants) {
                    if (enumConstant.toString().equals(value))
                        return enumConstant;
                }
            }
        }
        return null;
    }

}