DbBeanMapper.java

/*
 * Copyright (c) Steven P. Goldsmith. All rights reserved.
 *
 * Created by Steven P. Goldsmith on November 27, 2011
 * sgoldsmith@com.codeferm
 */
package com.codeferm.dbaccess;

import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Abstract class adding simple bean mapping from database field names with
 * underscore to camelCase bean properties.
 *
 * @see com.codeferm.dbaccess.DbAccess
 *
 * @author sgoldsmith
 * @version 1.0.0
 * @since 1.0.0
 */
public abstract class DbBeanMapper extends DbAccess { //NOPMD, really, I have to call it AbstractXXX, screw that

    /**
     * Convert bean property names from camelCase to underscore and return
     * mapping. This way mapping only occurs one time for the entire
     * {@code ResultSet}.
     *
     * @param fields {@code Array} containing bean field names
     * @return {@code Map} of bean to database field names
     */
    public final Map<String, String> fromCamelCase(final Field[] fields) {
        final Map<String, String> map = new HashMap<String, String>();
        String[] splitArr = null;
        StringBuilder fieldName = null;
        for (Field field : fields) {
            // Split words based on capital letters
            splitArr = field.getName().split("(?=\\p{Upper})");
            fieldName = new StringBuilder(); //NOPMD OK to create new StringBuilder in loop
            for (int i = 0; i < splitArr.length; i += 1) {
                fieldName.append(splitArr[i]);
                // Add underscore if not last word
                if (i < splitArr.length - 1) {
                    fieldName.append("_");
                }
                // Field name to database field name mapping
                map.put(field.getName(), fieldName.toString());
            }
        }
        return map;
    }

    /**
     * Get write method of each property and store in Map by name.
     *
     * @param fields {@code Array} containing bean field names
     * @param clazz {@code Class} of bean
     * @return {@code Map} of bean write methods
     */
    public final Map<String, Method> getWriteMethods(final Field[] fields,
            final Class clazz) {
        final Map<String, Method> map = new HashMap<String, Method>();
        try {
            for (Field field : fields) {
                // Ignore synthetic classes or dynamic proxies.
                if (!field.isSynthetic()) {
                    PropertyDescriptor propertyDescriptor
                            = new PropertyDescriptor(field.getName(), clazz);  //NOPMD OK to create new PropertyDescriptor in loop
                    map.put(field.getName(), propertyDescriptor.
                            getWriteMethod());
                }
            }
        } catch (IntrospectionException e) {
            throw new DbAccessException(e);
        }
        return map;
    }

    /**
     * Return list of beans mapped from {@code ResultSet}.
     *
     * @param <T> Type of beans
     * @param resultSet {@code ResultSet} to process
     * @param clazz {@code Class} of bean
     * @return {@code List} of {@code <T>} type beans
     */
    public final <T> List<T> createObjects(final ResultSet resultSet,
            final Class clazz) {
        final List<T> list = new ArrayList<T>();
        // Get bean fields
        final Field[] fields = clazz.getDeclaredFields();
        // Get bean to database field name mappings
        final Map<String, String> dbMap = fromCamelCase(fields);
        // Get bean write methods
        final Map<String, Method> beanMap = getWriteMethods(fields, clazz);
        try {
            // Process ResultSet
            while (resultSet.next()) {
                // New bean
                final T instance = (T) clazz.newInstance();
                // Map ResultSet to bean properties
                for (Field field : fields) {
                    // Ignore synthetic classes or dynamic proxies.
                    if (!field.isSynthetic()) {
                        beanMap.get(field.getName()).invoke(instance, resultSet.
                                getObject(dbMap.get(field.getName())));
                    }
                }
                list.add(instance);
            }
        } catch (SQLException e) {
            throw new DbAccessException(e);
        } catch (InvocationTargetException e) {
            throw new DbAccessException(e);
        } catch (IllegalAccessException e) {
            throw new DbAccessException(e);
        } catch (InstantiationException e) {
            throw new DbAccessException(e);
        }
        return list;
    }
}