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

com.opencsv.bean.HeaderColumnNameMappingStrategy Maven / Gradle / Ivy

There is a newer version: 5.9
Show newest version
package com.opencsv.bean;

import com.opencsv.CSVReader;
import com.opencsv.exceptions.CsvBadConverterException;
import org.apache.commons.lang3.StringUtils;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/*
 * Copyright 2007 Kyle Miller.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * Maps data to objects using the column names in the first row of the CSV file
 * as reference. This way the column order does not matter.
 *
 * @param  Type of the bean to be returned
 */
public class HeaderColumnNameMappingStrategy implements MappingStrategy {

    protected String[] header;
    protected Map indexLookup = new HashMap();
    protected Map descriptorMap = null;
    protected Map fieldMap = null;
    protected Class type;
    protected boolean annotationDriven;

    /**
     * Default constructor.
     */
    public HeaderColumnNameMappingStrategy() {
    }

    @Override
    public void captureHeader(CSVReader reader) throws IOException {
        header = reader.readNext();
    }

    /**
     * Creates an index map of column names to column position.
     *
     * @param values Array of header values.
     */
    protected void createIndexLookup(String[] values) {
        if (indexLookup.isEmpty()) {
            for (int i = 0; i < values.length; i++) {
                indexLookup.put(values[i], i);
            }
        }
    }

    /**
     * Resets index map of column names to column position.
     */
    protected void resetIndexMap() {
        indexLookup.clear();
    }

    @Override
    public Integer getColumnIndex(String name) {
        if (null == header) {
            throw new IllegalStateException("The header row hasn't been read yet.");
        }

        createIndexLookup(header);

        return indexLookup.get(name);
    }

    @Override
    public PropertyDescriptor findDescriptor(int col)
            throws IntrospectionException {
        String columnName = getColumnName(col);
        return (StringUtils.isNotBlank(columnName)) ? findDescriptor(columnName) : null;
    }

    @Override
    public BeanField findField(int col) throws CsvBadConverterException {
        String columnName = getColumnName(col);
        return (StringUtils.isNotBlank(columnName)) ? findField(columnName) : null;
    }

    /**
     * Get the column name for a given column position.
     *
     * @param col Column position.
     * @return The column name or null if the position is larger than the
     * header array or there are no headers defined.
     */
    public String getColumnName(int col) {
        return (null != header && col < header.length) ? header[col] : null;
    }

    /**
     * Find the property descriptor for a given column.
     *
     * @param name Column name to look up.
     * @return The property descriptor for the column.
     * @throws IntrospectionException Thrown on error loading the property
     *                                descriptors.
     */
    protected PropertyDescriptor findDescriptor(String name) throws IntrospectionException {
        if (null == descriptorMap) {
            descriptorMap = loadDescriptorMap(); //lazy load descriptors
        }
        return descriptorMap.get(name.toUpperCase().trim());
    }

    /**
     * Find the field for a given column.
     *
     * @param name The column name to look up.
     * @return BeanField containing the field for the column.
     * @throws CsvBadConverterException If a custom converter for a field cannot
     *                                  be initialized
     */
    protected BeanField findField(String name) throws CsvBadConverterException {
        return fieldMap.get(name.toUpperCase().trim());
    }

    /**
     * Determines if the name of a property descriptor matches the column name.
     * Currently only used by unit tests.
     *
     * @param name Name of the column.
     * @param desc Property descriptor to check against
     * @return True if the name matches the name in the property descriptor.
     */
    protected boolean matches(String name, PropertyDescriptor desc) {
        return desc.getName().equals(name.trim());
    }

    /**
     * Builds a map of property descriptors for the bean.
     *
     * @return Map of property descriptors
     * @throws IntrospectionException Thrown on error getting information
     *                                about the bean.
     */
    protected Map loadDescriptorMap() throws IntrospectionException {
        Map map = new HashMap();

        PropertyDescriptor[] descriptors;
        descriptors = loadDescriptors(getType());
        for (PropertyDescriptor descriptor : descriptors) {
            map.put(descriptor.getName().toUpperCase().trim(), descriptor);
        }

        return map;
    }

    /**
     * Builds a map of fields for the bean.
     *
     * @throws CsvBadConverterException If there is a problem instantiating the
     *                                  custom converter for an annotated field
     */
    protected void loadFieldMap() throws CsvBadConverterException {
        fieldMap = new HashMap();

        for (Field field : loadFields(getType())) {
            String columnName, locale;

            // Always check for a custom converter first.
            if (field.isAnnotationPresent(CsvCustomBindByName.class)) {
                columnName = field.getAnnotation(CsvCustomBindByName.class).column().toUpperCase().trim();
                Class converter = field
                        .getAnnotation(CsvCustomBindByName.class)
                        .converter();
                BeanField bean;
                try {
                    bean = converter.newInstance();
                } catch (IllegalAccessException oldEx) {
                    CsvBadConverterException newEx =
                            new CsvBadConverterException(converter,
                                    "There was a problem instantiating the custom converter "
                                            + converter.getCanonicalName());
                    newEx.initCause(oldEx);
                    throw newEx;
                } catch (InstantiationException oldEx) {
                    CsvBadConverterException newEx =
                            new CsvBadConverterException(converter,
                                    "There was a problem instantiating the custom converter "
                                            + converter.getCanonicalName());
                    newEx.initCause(oldEx);
                    throw newEx;
                }
                bean.setField(field);
                fieldMap.put(columnName, bean);
            }

            // Then check for CsvBindByName.
            else if (field.isAnnotationPresent(CsvBindByName.class)) {
                boolean required = field.getAnnotation(CsvBindByName.class).required();
                columnName = field.getAnnotation(CsvBindByName.class).column().toUpperCase().trim();
                locale = field.getAnnotation(CsvBindByName.class).locale();
                if (field.isAnnotationPresent(CsvDate.class)) {
                    String formatString = field.getAnnotation(CsvDate.class).value();
                    if (StringUtils.isEmpty(columnName)) {
                        fieldMap.put(field.getName().toUpperCase().trim(),
                                new BeanFieldDate(field, required, formatString, locale));
                    } else {
                        fieldMap.put(columnName, new BeanFieldDate(field, required, formatString, locale));
                    }
                } else {
                    if (StringUtils.isEmpty(columnName)) {
                        fieldMap.put(field.getName().toUpperCase().trim(),
                                new BeanFieldPrimitiveTypes(field, required, locale));
                    } else {
                        fieldMap.put(columnName, new BeanFieldPrimitiveTypes(field, required, locale));
                    }
                }
            }

            // And only check for CsvBind if nothing else is there, because
            // CsvBind is deprecated.
            else {
                boolean required = field.getAnnotation(CsvBind.class).required();
                fieldMap.put(field.getName().toUpperCase().trim(),
                        new BeanFieldPrimitiveTypes(field, required, null));
            }
        }
    }

    private PropertyDescriptor[] loadDescriptors(Class cls) throws IntrospectionException {
        BeanInfo beanInfo = Introspector.getBeanInfo(cls);
        return beanInfo.getPropertyDescriptors();
    }

    private List loadFields(Class cls) {
        List fields = new ArrayList();
        for (Field field : cls.getDeclaredFields()) {
            if (field.isAnnotationPresent(CsvBind.class)
                    || field.isAnnotationPresent(CsvBindByName.class)
                    || field.isAnnotationPresent(CsvCustomBindByName.class)) {
                fields.add(field);
            }
        }
        annotationDriven = !fields.isEmpty();
        return fields;
    }

    @Override
    public T createBean() throws InstantiationException, IllegalAccessException {
        return type.newInstance();
    }

    /**
     * Get the class type that the Strategy is mapping.
     *
     * @return Class of the object that mapper will create.
     */
    public Class getType() {
        return type;
    }

    /**
     * Sets the class type that is being mapped.
     * Also initializes the mapping between column names and bean fields.
     *
     * @param type Class type.
     * @throws CsvBadConverterException If a field in the bean is annotated
     *                                  with a custom converter that cannot be initialized. If you are not
     *                                  using custom converters that you have written yourself, it should be
     *                                  safe to catch this exception and ignore it.
     */
    public void setType(Class type) throws CsvBadConverterException {
        this.type = type;
        loadFieldMap();
    }

    /**
     * Determines whether the mapping strategy is driven by annotations.
     * For this mapping strategy, the supported annotations are:
     * 
  • {@link com.opencsv.bean.CsvBindByName}
  • *
  • {@link com.opencsv.bean.CsvCustomBindByName}
  • *
  • {@link com.opencsv.bean.CsvBind}
  • *
* * @return Whether the mapping strategy is driven by annotations */ @Override public boolean isAnnotationDriven() { return annotationDriven; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy