/*
* Copyright (c) 2014 Red Hat, Inc. and/or its affiliates.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Cheng Fang - Initial API and implementation
*/
package org.jberet.support.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.SuperCsvReflectionException;
import org.supercsv.io.AbstractCsvReader;
import org.supercsv.io.ICsvBeanReader;
import org.supercsv.prefs.CsvPreference;
import org.supercsv.util.BeanInterfaceProxy;
import org.supercsv.util.MethodCache;
/**
* Copied and modified from supercsv CsvBeanReader to support fast forward.
* 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
*
* @see org.jberet.support.io.FastForwardCsvListReader
* @see org.jberet.support.io.FastForwardCsvMapReader
* @since 1.0.0
*/
final class FastForwardCsvBeanReader 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();
private final int startRowNumber;
/**
* 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
* @param startRowNumber the row number to start reading
* @throws NullPointerException if reader or preferences are null
*/
FastForwardCsvBeanReader(final Reader reader, final CsvPreference preferences, final int startRowNumber) {
super(reader, preferences);
this.startRowNumber = startRowNumber;
}
/**
* 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 org.supercsv.exception.SuperCsvReflectionException if there was a reflection exception when instantiating the bean
*/
private static <T> T instantiateBean(final Class<T> clazz) {
final T bean;
if (clazz.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 org.supercsv.exception.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);
}
}
/**
* Instantiates the bean (or creates a proxy if it's an interface), and maps the processed columns to the fields of
* the bean.
*
* @param clazz the bean class to instantiate (a proxy will be created if an interface is supplied), using the default
* (no argument) constructor
* @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 Class<T> clazz, final String[] nameMapping) {
// instantiate the bean or proxy
final T resultBean = instantiateBean(clazz);
// map each column to its associated field on the bean
for (int 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
if (nameMapping[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... nameMapping) throws IOException {
fastForwardToStartRow();
if (readRow()) {
if (nameMapping.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()));
}
processedColumns.clear();
processedColumns.addAll(getColumns());
return populateBean(clazz, nameMapping);
}
return null; // EOF
}
/**
* {@inheritDoc}
*/
public <T> T read(final Class<T> clazz, final String[] nameMapping, final CellProcessor... processors)
throws IOException {
fastForwardToStartRow();
if (readRow()) {
// execute the processors then populate the bean
executeProcessors(processedColumns, processors);
return populateBean(clazz, nameMapping);
}
return null; // EOF
}
// reading into existing beans are currently not supported in jberet-support CSV reader and writer.
@Override
public <T> T read(final T t, final String... strings) throws IOException {
return null;
}
@Override
public <T> T read(final T t, final String[] strings, final CellProcessor... cellProcessors) throws IOException {
return null;
}
private void fastForwardToStartRow() throws IOException {
while (getRowNumber() < this.startRowNumber) {
readRow();
}
}
}