All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.x5.util.ObjectDataMap Maven / Gradle / Ivy

There is a newer version: 3.6.2
Show newest version
package com.x5.util;

import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
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.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * ObjectDataMap
 *
 * Box POJO/Bean/DataCapsule inside a Map.  When accessed, pry into object
 * using reflection/introspection/capsule-export and pull out all public
 * member fields/properties.
 * Convert field names from camelCase to lower_case_with_underscores
 * Convert bean properties from getSomeProperty() to some_property
 *  or isVeryHappy() to is_very_happy
 *
 * Values returned are copies, frozen at time of first access.
 *
 */
@SuppressWarnings("rawtypes")
public class ObjectDataMap implements Map
{
    private Map pickle = null;
    private Object object;
    private boolean isBean = false;

    private static BeanIntrospector introspector = null;
    private static final Map EMPTY_MAP = new HashMap();
    private static final HashSet> WRAPPER_TYPES = getWrapperTypes();
    private static final String TRUE = "TRUE";
    private static final Class[] NO_ARGS = new Class[]{};

    private static HashSet> getWrapperTypes()
    {
        HashSet> ret = new HashSet>();
        ret.add(Boolean.class);
        ret.add(Character.class);
        ret.add(Byte.class);
        ret.add(Short.class);
        ret.add(Integer.class);
        ret.add(Long.class);
        ret.add(Float.class);
        ret.add(Double.class);
        ret.add(Void.class);
        return ret;
    }

    public static boolean isWrapperType(Class clazz)
    {
        return WRAPPER_TYPES.contains(clazz);
    }

    public ObjectDataMap(Object pojo)
    {
        this.object = pojo;
    }

    private void init()
    {
        if (pickle == null) {
            pickle = mapify(object);
            // prevent multiple expensive calls to mapify
            // when result is null
            if (pickle == null) {
                pickle = EMPTY_MAP;
            }
        }
    }

    public static ObjectDataMap wrapBean(Object bean)
    {
        if (bean == null) return null;
        ObjectDataMap boxedBean = new ObjectDataMap(bean);
        boxedBean.isBean = true;

        return boxedBean;
    }

    private static Map hasOwnToString = new HashMap();

    public boolean isBean()
    {
        return this.isBean;
    }

    public static String getAsString(Object obj)
    {
        Class objClass = obj.getClass();
        Boolean doStringify = hasOwnToString.get(objClass);
        if (doStringify == null) {
            // perform expensive check... but just this once.
            Method toString = null;
            try {
                toString = obj.getClass().getMethod("toString", NO_ARGS);
            } catch (NoSuchMethodException e) {
            } catch (SecurityException e) {
            }
            doStringify = new Boolean(toString != null && !toString.getDeclaringClass().equals(Object.class));
            hasOwnToString.put(objClass, doStringify);
        }
        if (doStringify.booleanValue()) {
            // class has its own toString method -- safe.
            return obj.toString();
        } else {
            // don't expose Object's toString() info.
            return "OBJECT:" + obj.getClass().getName();
        }
    }

    private Map mapify(Object pojo)
    {
        Map data = null;
        if (pojo instanceof DataCapsule) {
            return mapifyCapsule((DataCapsule)pojo);
        }
        if (!isBean) {
            if (hasNonFinalPublicFields(pojo)) {
                data = mapifyPOJO(pojo);
                if (data == null || data.isEmpty()) {
                    // hmmm, maybe it's a bean?
                    isBean = true;
                } else {
                    return data;
                }
            } else {
                // no public (non-final) fields, treat as bean
                isBean = true;
            }
        }
        if (isBean) {
            if (introspector == null) {
                introspector = pickIntrospector();
                if (introspector == null) {
                    return data;
                }
            }
            try {
                return introspector.mapifyBean(pojo);
            } catch (IntrospectionException e) {
                // hmm, not a bean after all...
            }
        }
        return data;
    }

    @SuppressWarnings("unused")
    private static BeanIntrospector pickIntrospector()
    {
        // java.beans.* is missing on android.
        // Test for existence before use...
        try {
            Class beanClass = Class.forName("java.beans.Introspector");
            return new StandardIntrospector();
        } catch (ClassNotFoundException e) {
            try {
                Class madrobotClass = Class.forName("com.madrobot.beans.Introspector");
                return new MadRobotIntrospector();
            } catch (ClassNotFoundException e2) {
                return null;
            }
        }
    }

    private static Map declaredFields = new HashMap();
    private static Map looksLikePojo = new HashMap();

    private boolean hasNonFinalPublicFields(Object pojo)
    {
        Class pojoClass = pojo.getClass();
        if (looksLikePojo.containsKey(pojoClass)) {
            return looksLikePojo.get(pojoClass);
        }

        boolean found = false;

        Field[] fields = grokFields(pojo);
        for (int i=0; i mapifyPOJO(Object pojo)
    {
        Field[] fields = grokFields(pojo);
        Map pickle = null;

        for (int i=0; i();
            // convert isActive to is_active
            paramName = splitCamelCase(paramName);
            storeValue(pickle, paramClass, paramName, paramValue, isBean);
        }

        return pickle;
    }

    private Map mapifyCapsule(DataCapsule capsule)
    {
        DataCapsuleReader reader = DataCapsuleReader.getReader(capsule);

        String[] tags = reader.getColumnLabels(null);
        Object[] data = reader.extractData(capsule);

        pickle = new HashMap();
        for (int i=0; i pickle, Class paramClass,
                            String paramName, Object paramValue, boolean isBean)
    {
        if (paramValue == null) {
            pickle.put(paramName, null);
        } else if (paramClass == String.class) {
            pickle.put(paramName, paramValue);
        } else if (paramClass.isArray() || paramValue instanceof List) {
            pickle.put(paramName, paramValue);
        } else if (paramValue instanceof Boolean) {
            if (((Boolean)paramValue).booleanValue()) {
                pickle.put(paramName, TRUE);
            }
        } else if (paramClass.isPrimitive() || isWrapperType(paramClass)) {
            pickle.put(paramName, paramValue);
        } else {
            // box all non-primitive object member fields
            // in their own ObjectDataMap wrapper.
            // lazy init guarantees no infinite recursion here.
            ObjectDataMap boxedParam = isBean ? wrapBean(paramValue) : new ObjectDataMap(paramValue);
            pickle.put(paramName, boxedParam);
        }

    }

    private static Map snakeCased = new HashMap();

    // splitCamelCase converts SimpleXMLStuff to simple_xml_stuff
    public static String splitCamelCase(String s)
    {
        String cached = snakeCased.get(s);
        if (cached != null) return cached;

        // no regex! single pass through string
        StringBuilder snakeCase = new StringBuilder();
        int m = 0;
        char[] chars = s.toCharArray();
        // ascii lower
        char[] lower = new char[chars.length];
        for (int i=0; i= 'A' && c <= 'Z') ? (char)(c + 32) : c;
        }
        for (int i=1; i 'Z') {
                if (c1 >= 'A' && c1 <= 'Z') {
                    // non-cap, then cap
                    snakeCase.append(lower, m, i-m);
                    snakeCase.append('_');
                    m = i;
                }
            } else if (i-m > 1 && c0 >= 'A' && c0 <= 'Z' && (c1 > 'Z' || c1 < 'A')) {
                snakeCase.append(lower, m, i-1-m);
                snakeCase.append('_');
                m = i-1;
            }
        }
        snakeCase.append(lower, m, lower.length-m);

        cached = snakeCase.toString();
        snakeCased.put(s, cached);
        return cached;
    }

    public int size()
    {
        init();
        return pickle.size();
    }

    public boolean isEmpty()
    {
        init();
        return pickle.isEmpty();
    }

    public boolean containsKey(Object key)
    {
        init();
        return pickle.containsKey(key);
    }

    public boolean containsValue(Object value)
    {
        init();
        return pickle.containsValue(value);
    }

    public Object get(Object key)
    {
        init();
        return pickle.get(key);
    }

    public Object put(Object key, Object value)
    {
        // unsupported
        return null;
    }

    public Object remove(Object key)
    {
        // unsupported
        return null;
    }

    public void putAll(Map m)
    {
        // unsupported
    }

    public void clear()
    {
        // unsupported
    }

    public Set keySet()
    {
        init();
        return pickle.keySet();
    }

    public Collection values()
    {
        init();
        return pickle.values();
    }

    public Set entrySet()
    {
        init();
        return pickle.entrySet();
    }

    private static class Getter
    {
        private static final Object[] NO_ARGS = (Object[])null;

        Method getter;
        Class valueClass;
        String name;

        public Getter(PropertyDescriptor property, Method getter)
        {
            this.getter = getter;
            this.valueClass = property.getPropertyType();

            String paramName = property.getName();
            paramName = splitCamelCase(paramName);
            if (this.valueClass.toString().equalsIgnoreCase("boolean")) {
                paramName = "is_"+paramName;
            }
            this.name = paramName;
        }

        Object invoke(Object target)
        throws InvocationTargetException, IllegalAccessException
        {
            return getter.invoke(target, NO_ARGS);
        }
    }

    private static class IntrospectionException extends Exception
    {
        private static final long serialVersionUID = 8890979383599687484L;
    }

    private static interface BeanIntrospector {
        public Map mapifyBean(Object bean) throws IntrospectionException;
    }

    private static class StandardIntrospector implements BeanIntrospector
    {
        private static Map> beanGetters = new HashMap>();

        public Map mapifyBean(Object bean)
        throws IntrospectionException
        {
            Class beanClass = bean.getClass();
            List getters = beanGetters.get(beanClass);

            if (getters == null) {
                PropertyDescriptor[] properties = null;
                try {
                    BeanInfo beanInfo = Introspector.getBeanInfo(bean.getClass());
                    properties = beanInfo.getPropertyDescriptors();
                } catch (java.beans.IntrospectionException e) {
                    throw new IntrospectionException();
                }

                if (properties == null) return null;

                getters = new ArrayList();
                for (PropertyDescriptor property : properties) {
                    Method getter = property.getReadMethod();
                    if (getter == null) continue;
                    getters.add(new Getter(property, getter));
                }

                beanGetters.put(beanClass, getters);
            }

            Map pickle = null;

            // copy properties into hashtable
            for (Getter getter : getters) {
                try {
                    Object paramValue = getter.invoke(bean);

                    if (paramValue != null) {
                        if (pickle == null) pickle = new HashMap();
                        storeValue(pickle, getter.valueClass, getter.name, paramValue, true);
                    }
                } catch (InvocationTargetException e) {
                } catch (IllegalAccessException e) {
                }
            }

            return pickle;
        }
    }

    // mad robot provides a stopgap introspection library for android projects
    private static class MadRobotIntrospector implements BeanIntrospector
    {
        public Map mapifyBean(Object bean)
        throws IntrospectionException
        {
            com.madrobot.beans.PropertyDescriptor[] properties = null;
            try {
                com.madrobot.beans.BeanInfo beanInfo = com.madrobot.beans.Introspector.getBeanInfo(bean.getClass());
                properties = beanInfo.getPropertyDescriptors();
            } catch (com.madrobot.beans.IntrospectionException e) {
                throw new IntrospectionException();
            }

            if (properties == null) return null;

            Map pickle = null;

            // copy properties into hashtable
            for (com.madrobot.beans.PropertyDescriptor property : properties) {
                Class paramClass = property.getPropertyType();
                Method getter = property.getReadMethod();
                try {
                    Object paramValue = getter.invoke(bean, (Object[])null);

                    if (paramValue != null) {
                        // converts isActive() to is_active
                        // converts getBookTitle() to book_title
                        String paramName = property.getName();
                        paramName = splitCamelCase(paramName);
                        if (paramValue instanceof Boolean) {
                            paramName = "is_"+paramName;
                        }

                        if (pickle == null) pickle = new HashMap();

                        storeValue(pickle, paramClass, paramName, paramValue, true);
                    }
                } catch (InvocationTargetException e) {
                } catch (IllegalAccessException e) {
                }
            }

            return pickle;
        }
    }

    public String toString()
    {
        return getAsString(this.object);
    }

    public Object unwrap()
    {
        return this.object;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy