package net.thucydides.core.csv;
import au.com.bytecode.opencsv.CSVReader;
import ch.lambdaj.function.convert.Converter;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import net.thucydides.core.steps.StepFactory;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.net.URL;
import java.util.*;
import static ch.lambdaj.Lambda.convert;
/**
* Test data from a CSV file.
*/
public class CSVTestDataSource implements TestDataSource {
private final List<Map<String, String>> testData;
private final char separator;
private final List<String> headers;
private static final Logger LOGGER = LoggerFactory.getLogger(CSVTestDataSource.class);
public CSVTestDataSource(final String path, final char separatorValue) throws IOException {
this.separator = separatorValue;
List<String[]> csvDataRows = getCSVDataFrom(getDataFileFor(path));
String[] titleRow = csvDataRows.get(0);
this.headers = convert(titleRow, new Converter<String, String>() {
@Override
public String convert(String str) {
return StringUtils.strip(str);
}
});
testData = loadTestDataFrom(csvDataRows);
}
public CSVTestDataSource(final String path) throws IOException {
this(path, CSVReader.DEFAULT_SEPARATOR);
}
public static boolean validTestDataPath(final String path) {
if (validFileSystemPath(path) || isAClasspathResource(path)) {
return true;
} else {
URL testDataUrl = CSVTestDataSource.class.getClassLoader().getResource(path);
return (testDataUrl != null ) && new File(testDataUrl.getFile()).exists();
}
}
private Reader getDataFileFor(final String path) throws FileNotFoundException {
Preconditions.checkNotNull(path,"Test data source was not defined");
if (isAClasspathResource(path)) {
return new InputStreamReader(getClass().getClassLoader().getResourceAsStream(path));
} else if (validFileSystemPath(path)){
return new FileReader(new File(path));
}
throw new FileNotFoundException("Could not load test data from " + path);
}
private static boolean isAClasspathResource(final String path) {
if (CSVTestDataSource.class.getClassLoader().getResourceAsStream(path) == null){
return false;
}
return true;
}
private static boolean validFileSystemPath(final String path) {
File file = new File(path);
return file.exists();
}
protected List<String[]> getCSVDataFrom(final Reader testDataReader) throws IOException {
CSVReader reader = new CSVReader(testDataReader, separator);
List<String[]> rows = Lists.newArrayList();
try {
rows = reader.readAll();
} finally {
reader.close();
}
return rows;
}
protected List<Map<String, String>> loadTestDataFrom(List<String[]> rows) throws IOException {
List<Map<String, String>> loadedData = new ArrayList<Map<String, String>>();
String[] titleRow = rows.get(0);
for (int i = 1; i < rows.size(); i++) {
String[] dataRow = rows.get(i);
loadedData.add(dataEntryFrom(titleRow, dataRow));
}
return loadedData;
}
private Map<String, String> dataEntryFrom(final String[] titleRow, final String[] dataRow) {
Map<String, String> dataset = new HashMap<String, String>();
for (int column = 0; column < titleRow.length; column++) {
if (column < dataRow.length) {
String title = titleRow[column].trim();
String value = dataRow[column].trim();
dataset.put(title, value);
}
}
return dataset;
}
public List<Map<String, String>> getData() {
return testData;
}
public List<String> getHeaders() {
return headers;
}
/**
* Returns the test data as a list of JavaBean instances.
*/
public <T> List<T> getDataAsInstancesOf(final Class<T> clazz, final Object... constructorArgs) {
List<Map<String, String>> data = getData();
List<T> resultsList = new ArrayList<T>();
for (Map<String, String> rowData : data) {
resultsList.add(newInstanceFrom(clazz, rowData, constructorArgs));
}
return resultsList;
}
public <T> List<T> getInstanciatedInstancesFrom(final Class<T> clazz, final StepFactory factory) {
List<Map<String, String>> data = getData();
List<T> resultsList = new ArrayList<T>();
for (Map<String, String> rowData : data) {
resultsList.add(newInstanceFrom(clazz, factory, rowData));
}
return resultsList;
}
private <T> T newInstanceFrom(final Class<T> clazz,
final Map<String,String> rowData,
final Object... constructorArgs) {
T newObject = createNewInstanceOf(clazz, constructorArgs);
assignPropertiesFromTestData(clazz, rowData, newObject);
return newObject;
}
private <T> T newInstanceFrom(final Class<T> clazz,
final StepFactory factory,
final Map<String,String> rowData) {
T newObject = factory.getUniqueStepLibraryFor(clazz);
assignPropertiesFromTestData(clazz, rowData, newObject);
return newObject;
}
private <T> void assignPropertiesFromTestData(final Class<T> clazz,
final Map<String, String> rowData,
final T newObject) {
Set<String> propertyNames = rowData.keySet();
boolean validPropertyFound = false;
for (String columnHeading : propertyNames) {
String value = rowData.get(columnHeading);
String property = FieldName.from(columnHeading).inNormalizedForm();
if (assignPropertyValue(newObject, property, value)) {
validPropertyFound = true;
}
}
if (!validPropertyFound) {
throw new FailedToInitializeTestData("No properties or public fields matching the data columns were found "
+ "or could be assigned for the class " + clazz.getName()
+ "using test data: " + rowData);
}
}
protected <T> T createNewInstanceOf(final Class<T> clazz, final Object... constructorArgs) {
try {
return InstanceBuilder.newInstanceOf(clazz, constructorArgs);
} catch (Exception e) {
LOGGER.error("Could not create test data bean", e);
throw new FailedToInitializeTestData("Could not create test data beans", e);
}
}
protected <T> boolean assignPropertyValue(final T newObject, final String property, final String value) {
boolean valueWasAssigned = true;
try {
InstanceBuilder.inObject(newObject).setPropertyValue(property, value);
} catch (FailedToInitializeTestData e) {
valueWasAssigned = false;
}
return valueWasAssigned;
}
}