Open Source Repository

Home /supercsv/super-csv-2.3.1 | Repository Home



org/supercsv/io/CsvBeanReader.java
/*
 * Copyright 2007 Kasper B. Graversen
 
 * 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.
 */
package org.supercsv.io;

import java.io.IOException;
import java.io.Reader;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

import org.supercsv.cellprocessor.ift.CellProcessor;
import org.supercsv.exception.SuperCsvConstraintViolationException;
import org.supercsv.exception.SuperCsvException;
import org.supercsv.exception.SuperCsvReflectionException;
import org.supercsv.prefs.CsvPreference;
import org.supercsv.util.BeanInterfaceProxy;
import org.supercsv.util.MethodCache;

/**
 * CsvBeanReader reads a CSV file by instantiating a bean for every row and mapping each column to a field on the bean
 * (using the supplied name mapping). The bean to populate can be either a class or interface. If a class is used, it
 * must be a valid Javabean, i.e. it must have a default no-argument constructor and getter/setter methods. An interface
 * may also be used if it defines getters/setters - a proxy object will be created that implements the interface.
 
 @author Kasper B. Graversen
 @author James Bassett
 */
public class CsvBeanReader extends AbstractCsvReader implements ICsvBeanReader {
  
  // temporary storage of processed columns to be mapped to the bean
  private final List<Object> processedColumns = new ArrayList<Object>();
  
  // cache of methods for mapping from columns to fields
  private final MethodCache cache = new MethodCache();
  
  /**
   * Constructs a new <tt>CsvBeanReader</tt> with the supplied Reader and CSV preferences. Note that the
   <tt>reader</tt> will be wrapped in a <tt>BufferedReader</tt> before accessed.
   
   @param reader
   *            the reader
   @param preferences
   *            the CSV preferences
   @throws NullPointerException
   *             if reader or preferences are null
   */
  public CsvBeanReader(final Reader reader, final CsvPreference preferences) {
    super(reader, preferences);
  }
  
  /**
   * Constructs a new <tt>CsvBeanReader</tt> with the supplied (custom) Tokenizer and CSV preferences. The tokenizer
   * should be set up with the Reader (CSV input) and CsvPreference beforehand.
   
   @param tokenizer
   *            the tokenizer
   @param preferences
   *            the CSV preferences
   @throws NullPointerException
   *             if tokenizer or preferences are null
   */
  public CsvBeanReader(final ITokenizer tokenizer, final CsvPreference preferences) {
    super(tokenizer, preferences);
  }
  
  /**
   * Instantiates the bean (or creates a proxy if it's an interface).
   
   @param clazz
   *            the bean class to instantiate (a proxy will be created if an interface is supplied), using the default
   *            (no argument) constructor
   @return the instantiated bean
   @throws SuperCsvReflectionException
   *             if there was a reflection exception when instantiating the bean
   */
  private static <T> T instantiateBean(final Class<T> clazz) {
    final T bean;
    ifclazz.isInterface() ) {
      bean = BeanInterfaceProxy.createProxy(clazz);
    else {
      try {
        bean = clazz.newInstance();
      }
      catch(InstantiationException e) {
        throw new SuperCsvReflectionException(String.format(
          "error instantiating bean, check that %s has a default no-args constructor", clazz.getName()), e);
      }
      catch(IllegalAccessException e) {
        throw new SuperCsvReflectionException("error instantiating bean", e);
      }
    }
    
    return bean;
  }
  
  /**
   * Invokes the setter on the bean with the supplied value.
   
   @param bean
   *            the bean
   @param setMethod
   *            the setter method for the field
   @param fieldValue
   *            the field value to set
   @throws SuperCsvException
   *             if there was an exception invoking the setter
   */
  private static void invokeSetter(final Object bean, final Method setMethod, final Object fieldValue) {
    try {
      setMethod.invoke(bean, fieldValue);
    }
    catch(final Exception e) {
      throw new SuperCsvReflectionException(String.format("error invoking method %s()", setMethod.getName()), e);
    }
  }
  
  /**
   * Populates the bean by mapping the processed columns to the fields of the bean.
   
   @param resultBean
   *            the bean to populate
   @param nameMapping
   *            the name mappings
   @return the populated bean
   @throws SuperCsvReflectionException
   *             if there was a reflection exception while populating the bean
   */
  private <T> T populateBean(final T resultBean, final String[] nameMapping) {
    
    // map each column to its associated field on the bean
    forint i = 0; i < nameMapping.length; i++ ) {
      
      final Object fieldValue = processedColumns.get(i);
      
      // don't call a set-method in the bean if there is no name mapping for the column or no result to store
      ifnameMapping[i== null || fieldValue == null ) {
        continue;
      }
      
      // invoke the setter on the bean
      Method setMethod = cache.getSetMethod(resultBean, nameMapping[i], fieldValue.getClass());
      invokeSetter(resultBean, setMethod, fieldValue);
      
    }
    
    return resultBean;
  }
  
  /**
   * {@inheritDoc}
   */
  public <T> T read(final Class<T> clazz, final String... nameMappingthrows IOException {
    
    ifclazz == null ) {
      throw new NullPointerException("clazz should not be null");
    else ifnameMapping == null ) {
      throw new NullPointerException("nameMapping should not be null");
    }
    
    return readIntoBean(instantiateBean(clazz), nameMapping, null);
  }
  
  /**
   * {@inheritDoc}
   */
  public <T> T read(final Class<T> clazz, final String[] nameMapping, final CellProcessor... processors)
    throws IOException {
    
    ifclazz == null ) {
      throw new NullPointerException("clazz should not be null");
    else ifnameMapping == null ) {
      throw new NullPointerException("nameMapping should not be null");
    else ifprocessors == null ) {
      throw new NullPointerException("processors should not be null");
    }
    
    return readIntoBean(instantiateBean(clazz), nameMapping, processors);
  }
  
  /**
   * {@inheritDoc}
   */
  public <T> T read(final T bean, final String... nameMappingthrows IOException {
    
    ifbean == null ) {
      throw new NullPointerException("bean should not be null");
    else ifnameMapping == null ) {
      throw new NullPointerException("nameMapping should not be null");
    }
    
    return readIntoBean(bean, nameMapping, null);
  }
  
  /**
   * {@inheritDoc}
   */
  public <T> T read(final T bean, final String[] nameMapping, final CellProcessor... processorsthrows IOException {
    ifbean == null ) {
      throw new NullPointerException("bean should not be null");
    else ifnameMapping == null ) {
      throw new NullPointerException("nameMapping should not be null");
    else ifprocessors == null ) {
      throw new NullPointerException("processors should not be null");
    }
    
    return readIntoBean(bean, nameMapping, processors);
  }
  
  /**
   * Reads a row of a CSV file and populates the bean, using the supplied name mapping to map column values to the
   * appropriate fields. If processors are supplied then they are used, otherwise the raw String values will be used.
   
   @param bean
   *            the bean to populate
   @param nameMapping
   *            the name mapping array
   @param processors
   *            the (optional) cell processors
   @return the populated bean, or null if EOF was reached
   @throws IllegalArgumentException
   *             if nameMapping.length != number of CSV columns read
   @throws IOException
   *             if an I/O error occurred
   @throws NullPointerException
   *             if bean or nameMapping are null
   @throws SuperCsvConstraintViolationException
   *             if a CellProcessor constraint failed
   @throws SuperCsvException
   *             if there was a general exception while reading/processing
   @throws SuperCsvReflectionException
   *             if there was an reflection exception while mapping the values to the bean
   */
  private <T> T readIntoBean(final T bean, final String[] nameMapping, final CellProcessor[] processors)
    throws IOException {
    
    ifreadRow() ) {
      ifnameMapping.length != length() ) {
        throw new IllegalArgumentException(String.format(
          "the nameMapping array and the number of columns read "
            "should be the same size (nameMapping length = %d, columns = %d)", nameMapping.length,
          length()));
      }
      
      ifprocessors == null ) {
        processedColumns.clear();
        processedColumns.addAll(getColumns());
      else {
        executeProcessors(processedColumns, processors);
      }
      
      return populateBean(bean, nameMapping);
    }
    
    return null// EOF
  }
  
}