Package com.projectnine.csvmapper

Source Code of com.projectnine.csvmapper.CsvToObjectMapper

/**
* Copyright (C) 2008 rweber <quietgenie@users.sourceforge.net>
*
* This file is part of CsvObjectMapper.
*
* CsvObjectMapper 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.
*
* CsvObjectMapper 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with CsvObjectMapper.  If not, see <http://www.gnu.org/licenses/>.
*/

/**
*
*/
package com.projectnine.csvmapper;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
import java.util.Map;

import net.sf.csv4j.CSVReader;
import net.sf.csv4j.ParseException;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.jexl.Expression;
import org.apache.commons.jexl.ExpressionFactory;
import org.apache.commons.jexl.JexlContext;
import org.apache.commons.jexl.JexlHelper;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.io.InputStreamResource;
import org.springframework.core.io.Resource;

/**
* This class is the only class in which users should be directly interested.
*
* First of all, if you don't want to go insane, use a spring application
* context to configure your {@link CsvMappingDefinition}s and add them the
* {@link #csvMappingDefinitions} {@link Map}.
*
* Check out the test cases for examples (src/test).
*
* Not thread safe. May never be. We'll see.
*
* @author robweber
*
*/
public class CsvToObjectMapper {
    private static final Log log = LogFactory.getLog(CsvToObjectMapper.class);

    /**
     * JEXL expressions for property names in the {@link CsvFieldMapping} should
     * include this token somewhere. The token is a placeholder for the adjusted
     * RAW CSV Field String value.
     *
     * So if you want your JEXL expression to do something like this:
     *
     * getFooMap().put('bar', adjustedCsvValue)
     *
     * you really want to type this:
     *
     * getFooMap().put('bar', %ARGUMENT%)
     */
    public static final String ARGUMENT_TOKEN = "%ARGUMENT%";

    /**
     * You don't really care about this.
     *
     * Fine.
     *
     * This is the name in the JEXL context of the Object into which the RAW CSV
     * Field String has been transformed. Happy?
     */
    private static final String ARGUMENT_VALUE = "finalPropertyValue";

    /**
     * All of the mapping definitions that are available to you.
     */
    protected static Map<String, CsvMappingDefinition> csvMappingDefinitions;

    /**
     * The name of the mapping definition in which THIS
     * {@link CsvToObjectMapper} is interested.
     */
    private String mappingDefinition;

    /**
     * This guy does a little of this and a little of that.
     *
     * Mainly, though, I just use this to read a CSV file. Huh?
     */
    private CSVReader csvReader;

    /**
     * A list containing all of the CSV field values in the line we are
     * currently processing.
     */
    private List<String> line;

    /**
     * Default constructor. Mainly, it just does nothing.
     *
     * Do not forget to {@link #init(Resource, boolean, String)}!!!
     */
    public CsvToObjectMapper() {
    }

    /**
     * This constructor is pretty useful. It initializes the object without you
     * having to do it explicitly.
     *
     * @param csvResource
     *            The {@link Resource} containing the CSV file.
     * @param containsHeader
     *            Does this CSV file contain a header? I need to know so that I
     *            can skip the header if it's there. There might be problems
     *            otherwise...
     * @param mappingDefinition
     *            The name of the mapping definition that we are using from the
     *            {@link #csvMappingDefinitions}.
     */
    public CsvToObjectMapper(Resource csvResource, boolean containsHeader,
      String mappingDefinition) {
  init(csvResource, containsHeader, mappingDefinition);
    }

    /**
     * Initializes the object by setting the {@link #mappingDefinition} and
     * instantiating the {@link #csvReader}.
     *
     * @see #CsvToObjectMapper(Resource, boolean, String)
     */
    public void init(Resource csvResource, boolean containsHeader,
      String mappingDefinition) {
  this.mappingDefinition = mappingDefinition;

  try {
      csvReader = new CSVReader(new BufferedReader(new InputStreamReader(
        csvResource.getInputStream())));
      if (containsHeader) {
    csvReader.readLine();
      }
  } catch (Exception e) {
      log.error(
        "Unable to load the specified CSV resource: "
          + (csvResource != null ? csvResource.getFilename()
            : "NULL") + ".", e);
      throw new RuntimeException(e);
  }
    }

    public static void main(String[] args) throws Exception {
  try {
      InputStream in = new FileInputStream("temp");
      Resource resource = new InputStreamResource(in);
      CSVReader csvReader = new CSVReader(new BufferedReader(
        new InputStreamReader(resource.getInputStream())));
      List<String> list = csvReader.readLine();
      while (list.size() > 0) {
    System.out.println(list);
    list = csvReader.readLine();
      }

  } catch (Throwable t) {
      log.warn("Error", t);
      throw new Exception(t);
  }
    }

    // @SuppressWarnings("unchecked")
    // public static String generateCsvLineFromObject(Object objectToConvert,
    // String csvMappingName) throws Exception {
    // CsvMappingDefinition mappingDefinition = csvMappingDefinitions
    // .get(csvMappingName);
    // if (mappingDefinition == null) {
    // return null;
    // }
    //
    // if (!Class.forName(mappingDefinition.beanClassName).isAssignableFrom(
    // objectToConvert.getClass())) {
    // return null;
    // }
    //
    // Map<Integer, String> fieldMap = MapUtils
    // .orderedMap(convertObjectToFieldMap(objectToConvert,
    // mappingDefinition));
    //
    // int numberOfFields = mappingDefinition.getExpectedNumberOfFields();
    // // If the number of fields is not explicitly stated, we guess based on
    // // the column number of the last mapped value.
    // if (numberOfFields <= 0) {
    // numberOfFields = fieldMap.keySet().toArray(
    // new Integer[fieldMap.size()])[fieldMap.size() - 1];
    // }
    //
    // StringBuffer lineBuffer = new StringBuffer();
    // for (int i = 0; i < numberOfFields; i++) {
    // String mapValue = fieldMap.get(new Integer(i));
    // lineBuffer.append((mapValue != null ? mapValue : ""));
    // lineBuffer.append((i < numberOfFields - 1 ? "," : "\n"));
    // }
    //
    // return lineBuffer.toString();
    // }

    // private static Map<Integer, String> convertObjectToFieldMap(
    // Object objectToConvert, CsvMappingDefinition mappingDefinition)
    // throws Exception {
    // Map<Integer, String> map = new HashMap<Integer, String>();
    //
    // List<CsvFieldMapping> list = mappingDefinition.getFieldMappings();
    // for (int i = 0; i < list.size(); i++) {
    // CsvFieldMapping csvFieldMapping = list.get(i);
    // if (csvFieldMapping.getBeanName() == null) {
    // map.put(new Integer(csvFieldMapping.getColumnIndex()),
    // BeanUtils.getProperty(objectToConvert, csvFieldMapping
    // .getPropertyName()));
    // } else if (csvFieldMapping.isComplexProperty()) {
    // map.putAll(convertObjectToFieldMap(CsvPropertyUtil
    // .getComplexProperty(objectToConvert, csvFieldMapping
    // .getPropertyName()), csvMappingDefinitions
    // .get(csvFieldMapping.getBeanName())));
    // } else {
    // map.putAll(convertObjectToFieldMap(CsvPropertyUtil
    // .getSimpleProperty(objectToConvert, csvFieldMapping
    // .getPropertyName()), csvMappingDefinitions
    // .get(csvFieldMapping.getBeanName())));
    // }
    // }
    //
    // return map;
    // }

    /**
     * Generate the next Object from CSV.
     *
     * @return An Object; null if there are no more records; throws an Exception
     *         if there is a problem loading the next record. Note that a thrown
     *         Exception does necessarily indicate a show stopper.
     */
    public Object generateNextObjectFromCsv() throws Exception {
  Object o = null;

  try {
      if (loadNextRecord()) {
    o = generateObjectFromCurrentCsvRecord();
      }
  } catch (Exception e) {
      log
        .warn(
          "An error occurred while loading the CSV line. Maybe the next line is good?",
          e);
      throw e;
  }

  return o;
    }

    /**
     * Assuming that you have called {@link #loadNextRecord()} at least once,
     * this method will... do... something... what?
     */
    private Object generateObjectFromCurrentCsvRecord() {
  CsvMappingDefinition csvMappingDefinition = getCsvMappingDefinition(mappingDefinition);
  Object generatedObject = null;
  if (csvMappingDefinition == null) {
      log.warn("The specified mapping is undefined. Returning null.");
  } else {

      try {
    if (csvMappingDefinition.getExpectedNumberOfFields() == -1
      || csvMappingDefinition.getExpectedNumberOfFields() == line
        .size()) {
        log.debug("The line to parse is " + line);

        generatedObject = populateBean(csvMappingDefinition
          .getNewBeanInstance(), csvMappingDefinition, line);

    } else if (line.size() != 0) {
        throw new ValidationException("The line (#"
          + getCurrentLineNumber() + ") read contains "
          + (line != null ? line.size() : -1) + " items. "
          + csvMappingDefinition.getExpectedNumberOfFields()
          + " fields are expected:\n\t" + line);
    }
      } catch (Exception e) {
    log
      .error(
        "An error occurred while converting the CSV to Object.",
        e);
    throw new RuntimeException(e);
      }
  }

  log.debug("The generated object, "
    + generatedObject
    + ", is of class, "
    + (generatedObject != null ? generatedObject.getClass()
      .getName() : "null"));
  return generatedObject;
    }

    /**
     * Load the next record from the CSV file.
     *
     * @return true if a record is loaded successfully; false if there are no
     *         more records
     * @throws RuntimeException
     *             when an error occurs during record load.
     */
    private boolean loadNextRecord() {
  boolean nextRecordLoaded = false;
  try {
      line = csvReader.readLine();
      if (line.size() > 0) {
    nextRecordLoaded = true;
      }
  } catch (ParseException e) {
      log.fatal("An error occurred while parsing the CSV file.", e);
      throw new RuntimeException(e);
  } catch (IOException e) {
      log.fatal("An error occurred while accessing the CSV file.", e);
      throw new RuntimeException(e);
  }

  return nextRecordLoaded;
    }

    /**
     * Returns a {@link CsvMappingDefinition} that corresponds to the given
     * mapping definition string.
     *
     * @param mappingDefinition
     * @return
     */
    private CsvMappingDefinition getCsvMappingDefinition(
      String mappingDefinition) {
  return csvMappingDefinitions.get(mappingDefinition);
    }

    /**
     * Fills the bean with the good stuff.
     *
     * @param generatedObject
     * @param csvMappingDefinition
     * @param line
     * @return
     * @throws Exception
     */
    @SuppressWarnings("unchecked")
    private Object populateBean(Object generatedObject,
      CsvMappingDefinition csvMappingDefinition, List<String> line)
      throws Exception {
  if (line.size() > 0) {
      List<CsvFieldMapping> csvFieldMappingList = csvMappingDefinition
        .getFieldMappings();
      for (int i = 0; i < csvFieldMappingList.size(); i++) {
    CsvFieldMapping csvFieldMapping = csvFieldMappingList.get(i);
    String propertyName = csvFieldMapping
      .getCsvToObjectExpression();
    Object finalPropertyValue = null;

    if (csvFieldMapping.getBeanName() == null) {
        finalPropertyValue = csvFieldMapping
          .getObjectValueFromCsvField(line
            .get(csvFieldMapping.getColumnIndex()),
            generatedObject, line);
    } else {
        log.debug("Attempting to retrieve a mapping called "
          + csvFieldMapping.getBeanName());

        CsvMappingDefinition newMapping = getCsvMappingDefinition(csvFieldMapping
          .getBeanName());
        Object newBeanInstance = newMapping.getNewBeanInstance();
        finalPropertyValue = populateBean(newBeanInstance,
          newMapping, line);
    }

    log.debug("After bean population, the final property value is "
      + finalPropertyValue
      + " | "
      + (finalPropertyValue != null ? finalPropertyValue
        .getClass().getName() : "null"));

    if (propertyName.contains(ARGUMENT_TOKEN)) {
        JexlContext jexlContext = JexlHelper.createContext();
        jexlContext.getVars().put("generatedObject",
          generatedObject);
        jexlContext.getVars().put(ARGUMENT_VALUE,
          finalPropertyValue);
        String expressionString = propertyName.replace(
          ARGUMENT_TOKEN, ARGUMENT_VALUE);
        Expression expression = ExpressionFactory
          .createExpression("generatedObject."
            + expressionString);
        expression.evaluate(jexlContext);
    } else {
        // TODO Throw an Exception here since not including the
        // ARGUMENT_TOKEN in the expression is a violation of the
        // updated specification.
        log.warn("Using legacy property setting method.");
        doOldPropertySetting(generatedObject, csvFieldMapping,
          propertyName, finalPropertyValue);
    }
      }
  }
  return generatedObject;
    }

    /**
     * This method encapsulates the property setting logic that was incorporated
     * into the {@link CsvToObjectMapper} pre JEXL. It is included here for
     * compatibility only, and it will be removed in a future release.
     *
     * @param generatedObject
     * @param csvFieldMapping
     * @param propertyName
     * @param finalPropertyValue
     * @throws IllegalAccessException
     * @throws InvocationTargetException
     * @throws NoSuchMethodException
     * @throws Exception
     * @deprecated This will be removed in a future release.
     */
    private void doOldPropertySetting(Object generatedObject,
      CsvFieldMapping csvFieldMapping, String propertyName,
      Object finalPropertyValue) throws IllegalAccessException,
      InvocationTargetException, NoSuchMethodException, Exception {
  // if (!csvFieldMapping.isComplexProperty()) {
  // Note that if the property does not exist on the specified
  // object, nothing bad happens when we try to set it using
  // BeanUtils :-O

  // So we try to get the property first!
  BeanUtils.getProperty(generatedObject, propertyName);

  // Then, when no Exception is thrown, we set the property!
  BeanUtils
    .setProperty(generatedObject, propertyName, finalPropertyValue);
  log.debug("Set a property called " + propertyName + " to a value of "
    + finalPropertyValue + " to an object of class "
    + generatedObject.getClass());
  // } else {
  // CsvPropertyUtil.setProperty(generatedObject, propertyName,
  // finalPropertyValue);
  // }
    }

    /**
     * @param csvMappingDefinitions
     *            the csvMappingDefinitions to set
     */
    public void setCsvMappingDefinitions(
      Map<String, CsvMappingDefinition> csvMappingDefinitions) {
  CsvToObjectMapper.csvMappingDefinitions = csvMappingDefinitions;
    }

    /**
     * On what line number is the csvReader?
     *
     * @return
     */
    public synchronized long getCurrentLineNumber() {
  return csvReader.getLineNumber();
    }

    /**
     * Move the cursor of the csvReader to the specified line number.
     *
     * @param parserPosition
     */
    public synchronized void seek(long parserPosition) {
  try {
      if (parserPosition > csvReader.getLineNumber()) {
    long linesToSkip = parserPosition - csvReader.getLineNumber();
    for (int i = 0; i < linesToSkip; i++) {
        List<String> list = csvReader.readLine();
        if (list.size() == 0) {
      // Reached the end of the file.
      break;
        }
    }
      }
  } catch (Exception e) {
      throw new RuntimeException(e);
  }
    }
}
TOP

Related Classes of com.projectnine.csvmapper.CsvToObjectMapper

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.