io.lightlink.dao.mapping.BeanMapper.java Source code

Java tutorial

Introduction

Here is the source code for io.lightlink.dao.mapping.BeanMapper.java

Source

package io.lightlink.dao.mapping;

/*
 * #%L
 * lightlink-core
 * %%
 * Copyright (C) 2015 Vitaliy Shevchuk
 * %%
 * This program 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 3 of the
 * License, or (at your option) any later version.
 * 
 * This program 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 General Lesser Public License for more details.
 * 
 * You should have received a copy of the GNU General Lesser Public
 * License along with this program.  If not, see
 * <http://www.gnu.org/licenses/lgpl-3.0.html>.
 * #L%
 */

import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.collections.map.CaseInsensitiveMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class BeanMapper {

    public static final Logger LOG = LoggerFactory.getLogger(BeanMapper.class);

    Class paramClass;

    List<String> ownFields = new ArrayList<String>();

    Map<String, BeanMapper> childMappers = new CaseInsensitiveMap();
    Map<String, BeanMapper> childListMappers = new CaseInsensitiveMap();

    Map<MappingEntry, Object> pool = new HashMap<MappingEntry, Object>();

    private final Map<String, PropertyDescriptor> descriptorMap;

    public BeanMapper(Class paramClass, List<String> fieldsOfLine) {

        PropertyDescriptor[] descriptors = PropertyUtils.getPropertyDescriptors(paramClass);
        descriptorMap = new CaseInsensitiveMap();
        for (PropertyDescriptor descriptor : descriptors) {
            descriptorMap.put(normalizePropertyName(descriptor.getName()), descriptor);
        }

        List<String> ownFields = new ArrayList<String>();
        Map<String, List<String>> fieldsByChild = new CaseInsensitiveMap();

        groupFields(fieldsOfLine, ownFields, fieldsByChild);

        this.ownFields = ownFields;
        for (Map.Entry<String, List<String>> entry : fieldsByChild.entrySet()) {
            String childProperty = normalizePropertyName(entry.getKey());
            PropertyDescriptor descriptor = descriptorMap.get(childProperty);
            if (descriptor != null) {
                List<String> properties = entry.getValue();
                if (descriptor.getPropertyType().isAssignableFrom(ArrayList.class)) {
                    Type[] typeArguments = ((ParameterizedType) descriptor.getReadMethod().getGenericReturnType())
                            .getActualTypeArguments();
                    if (typeArguments.length == 0)
                        throw new RuntimeException("Cannot define Generic list type of " + entry.getKey()
                                + " property of " + paramClass);
                    Type firstType = typeArguments[0];
                    if (firstType instanceof Class) {
                        childListMappers.put(childProperty, new BeanMapper((Class) firstType, properties));
                    }
                } else {
                    childMappers.put(childProperty, new BeanMapper(descriptor.getPropertyType(), properties));
                }
            } else {
                throw new RuntimeException(
                        "cannot define mapping for class:" + paramClass.getName() + " property:" + childProperty);
            }
        }

        this.paramClass = paramClass;
    }

    public static void groupFields(List<String> allFieldsOfLine, List<String> ownFields,
            Map<String, List<String>> fieldsByChild) {
        for (String f : allFieldsOfLine) {
            if (f.contains(".")) {
                int dotPos = f.indexOf('.');
                String childName = f.substring(0, dotPos);
                List<String> list = fieldsByChild.get(childName);
                if (list == null)
                    fieldsByChild.put(childName, list = new ArrayList<String>());
                list.add(f.substring(dotPos + 1));
            } else {
                ownFields.add(f);
            }
        }
    }

    public List convert(List<Map<String, Object>> maps)
            throws IllegalAccessException, InstantiationException, InvocationTargetException {
        if (maps == null) {
            return new ArrayList();
        }
        List beanList = new ArrayList(maps.size());
        for (Map<String, Object> map : maps) {
            Object object = convertObject(map, true);
            if (object != null) // object is already present il the result as a parent containing multiple child objects
                beanList.add(object);
        }
        return beanList;
    }

    public Object convertObject(Map<String, Object> map, boolean returnNullIfAlreadyPresent)
            throws IllegalAccessException, InstantiationException, InvocationTargetException {

        Object bean = paramClass.newInstance();

        populate(bean, map);

        List<String> fieldsToCompare = new ArrayList<String>(ownFields);
        fieldsToCompare.addAll(childMappers.keySet());

        MappingEntry mappingEntry = new MappingEntry(paramClass, fieldsToCompare, bean);

        Object pooledEntry = pool.get(mappingEntry);
        boolean fromPool;
        if (pooledEntry != null) {
            bean = pooledEntry;
            fromPool = true;
        } else {
            pool.put(mappingEntry, bean);
            fromPool = false;
        }

        boolean lineAdded = populateListObjects(bean, map);

        if (fromPool && lineAdded && returnNullIfAlreadyPresent)
            return null;
        else
            return bean;

    }

    private void populate(Object bean, Map<String, Object> map)
            throws InvocationTargetException, IllegalAccessException {

        Map<String, Object> data = new CaseInsensitiveMap(map.size());
        for (Map.Entry<String, Object> entry : map.entrySet()) {
            data.put(normalizePropertyName(entry.getKey()), entry.getValue());
        }

        populateOwnFields(bean, data);

        populateChildObjects(bean, map);

    }

    private boolean populateListObjects(Object bean, Map<String, Object> map) {
        boolean lineAdded = false;

        for (Map.Entry<String, BeanMapper> entry : childListMappers.entrySet()) {
            String originalKey = entry.getKey();
            String key = normalizePropertyName(entry.getKey());
            PropertyDescriptor propertyDescriptor = descriptorMap.get(key);
            if (propertyDescriptor == null) {

                LOG.info("Cannot find property for " + entry.getKey() + " in class "
                        + paramClass.getCanonicalName());

            } else {

                Map<String, Object> childData = prepareChildData(originalKey, map);
                try {
                    Object childObject = entry.getValue().convertObject(childData, true);

                    List list = (List) propertyDescriptor.getReadMethod().invoke(bean);

                    if (list == null)
                        propertyDescriptor.getWriteMethod().invoke(bean, list = new ArrayList());

                    if (childObject != null)
                        list.add(childObject);

                    lineAdded = true;

                } catch (Exception e) {
                    LOG.error(e.getMessage(), e);
                }
            }

        }
        return lineAdded;
    }

    private Map<String, Object> prepareChildData(String originalKey, Map<String, Object> map) {

        Map<String, Object> childData = new CaseInsensitiveMap(map.size());

        for (Map.Entry<String, Object> childEntry : map.entrySet()) {
            String childEntryKey = childEntry.getKey();
            if (childEntryKey.startsWith(originalKey + ".")) {
                childData.put(childEntryKey.substring(originalKey.length() + 1), childEntry.getValue());
            }
        }

        return childData;

    }

    private void populateChildObjects(Object bean, Map<String, Object> map) {
        for (Map.Entry<String, BeanMapper> entry : childMappers.entrySet()) {
            String originalKey = entry.getKey();
            String key = normalizePropertyName(originalKey);
            PropertyDescriptor propertyDescriptor = descriptorMap.get(key);
            if (propertyDescriptor == null) {
                LOG.info("Cannot find property for " + key + " in class " + paramClass.getCanonicalName());
            } else {
                Map<String, Object> childData = prepareChildData(originalKey, map);
                try {
                    Object childObject = entry.getValue().convertObject(childData, false);
                    propertyDescriptor.getWriteMethod().invoke(bean, childObject);
                } catch (Exception e) {
                    LOG.error(e.getMessage(), e);
                }
            }

        }
    }

    private void populateOwnFields(Object bean, Map<String, Object> data) {
        for (String field : ownFields) {
            String key = normalizePropertyName(field);
            PropertyDescriptor propertyDescriptor = descriptorMap.get(key);
            if (propertyDescriptor == null) {
                LOG.info("Cannot find property for " + field + " in class " + paramClass.getCanonicalName());
            } else {
                try {
                    propertyDescriptor.getWriteMethod().invoke(bean, MappingUtils.convert(
                            propertyDescriptor.getPropertyType(), data.get(key), propertyDescriptor.getName()));
                } catch (Exception e) {
                    LOG.error(e.getMessage(), e);
                }
            }
        }
    }

    private String normalizePropertyName(String key) {
        return key.replaceAll("_", "");
    }

}