/*
 * Created on Oct 13, 2003
 *
 * Works with beans that have private members, arrays, collections, ... whatever.
 *
 * @author Pinkham
 */


import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.List;

/**
 *  Get a debug string from any bean.  Handles arrays and Collections.
 *  Does NOT detect reference cycles (stack will overflow).
 */

public final class DebugPrint {
   
    private DebugPrint() {}

    // setter methods below let you change these to suit your style
    private static String beanPrefix = ":{";
    private static String beanSuffix = "}";
    private static String valueSeparator = "=";
    private static String nullStr = "null";
    private static String elementSeparator = "\n";
    private static String propertySeparator = "\n";      // try ";" or "," for brevity
   
    /**
    * Given a JavaBean (an object with getter methods) this method will
    * find and call the getter methods and prepare a toString
    * that lists the values of all it's properties.  The format looks like this:
    *
    * package.BeanName:{prop=value\nprop2=value2}
    *
    */
   
    public static String toString(Object bean)
    {
        if (bean == null)
            return nullStr;
        // Put classes that need special handling here...
        if (bean instanceof java.util.Calendar)
            return ((java.util.Calendar)bean).getTime().toString();       
        Class c = bean.getClass();
        if (isLeafClass(c))
            return bean.toString();         // bean must be just a primitive type
        BeanInfo bi;
        try
        {
            bi = Introspector.getBeanInfo(c, Object.class);    // don't want Object methods like getClass
        } catch (IntrospectionException e)
        {
            bi = null;
        }
        PropertyDescriptor[] pd = null;
        if (bi != null)
            pd = bi.getPropertyDescriptors();
        if (pd == null || pd.length==0)     // no properties
            return bean.toString();
        String ret = c.getName()+beanPrefix;
        for (int i=0; i<pd.length; ++i)
        {
            if (i>0)
                ret += propertySeparator;
            //ret += pd[i].getName() + valueSeparator;
            Method getter = pd[i].getReadMethod();
            if (getter == null) // skip props with setter only
                continue;
            Object value;
            try
            {
                value = getter.invoke(bean, new Object[0]);
            } catch (Exception e) // invokationTarget, illegalArgument, illegalAccess
            {
                value = e.toString();
            }
            if (getter.getReturnType().isArray())
            {
                if (value == null)
                {
                    ret += pd[i].getName() + "[]" + valueSeparator + nullStr;
                } else
                {
                    Object[] values = (Object[]) value;
                    for (int j=0; j<values.length; ++j)
                    {
                        if (j>0)
                            ret += elementSeparator;
                        ret += pd[i].getName() + "[" + j + "]" + valueSeparator + toString(values[j]);
                    }
                }
            } else
            {
                if (value instanceof Collection)
                {
                    Iterator k = ((Collection)value).iterator();
                    int j=0;
                    boolean first = true;
                    while (k.hasNext())
                    {
                        if (first)
                        {
                            first = false;
                            ret += elementSeparator;
                        }
                        ret += pd[i].getName() + "[" + j + "]" + valueSeparator + toString(k.next());
                    }
                } else
                {
                    ret += pd[i].getName() + valueSeparator + toString(value);
                }
            }
        }
        ret += beanSuffix;
        return ret;
    }
   
    // stop recursion and just do toString for these classes
    private static List LEAF_CLASSES = null;   
    private static boolean isLeafClass(Class c)
    {
        if (LEAF_CLASSES == null)
        {
           LEAF_CLASSES = new ArrayList();
           LEAF_CLASSES.add(String.class);
           LEAF_CLASSES.add(Date.class);
           LEAF_CLASSES.add(Float.class);
           LEAF_CLASSES.add(Double.class);
           LEAF_CLASSES.add(Integer.class);
           LEAF_CLASSES.add(Long.class);
        }
        return LEAF_CLASSES.contains(c);
    }

    // modify as needed to format toString result
    public static void setBeanPrefix(String beanPrefix) {
        DebugPrint.beanPrefix = beanPrefix;
    }
    public static void setBeanSuffix(String beanSuffix) {
        DebugPrint.beanSuffix = beanSuffix;
    }
    public static void setValueSeparator(String valueSeparator) {
        DebugPrint.valueSeparator = valueSeparator;
    }
    public static void setNullStr(String nullStr) {
        DebugPrint.nullStr = nullStr;
    }
    public static void setArrayElementSeparator(String arrayElementSeparator) {
        DebugPrint.elementSeparator = arrayElementSeparator;
    }
    public static void setElementSeparator(String elementSeparator) {
        DebugPrint.elementSeparator = elementSeparator;
    }
    public static void setPropertySeparator(String propertySeparator) {
        DebugPrint.propertySeparator = propertySeparator;
    }
}