Author: tchemit Date: 2012-09-18 15:50:54 +0200 (Tue, 18 Sep 2012) New Revision: 2421 Url: http://nuiton.org/repositories/revision/nuiton-utils/2421 Log: refs #2315: Add a new Importer with a real context for each row to import refs #2316: Add a new import mode to not stop at the first error refs #2317: Add mode for csv import to ignore undeclared headers Added: trunk/nuiton-csv/src/main/java/org/nuiton/util/csv/AbstractImportErrorInfo.java trunk/nuiton-csv/src/main/java/org/nuiton/util/csv/Import2.java trunk/nuiton-csv/src/main/java/org/nuiton/util/csv/ImportConf.java trunk/nuiton-csv/src/main/java/org/nuiton/util/csv/ImportParseErrorInfo.java trunk/nuiton-csv/src/main/java/org/nuiton/util/csv/ImportReadErrorInfo.java trunk/nuiton-csv/src/main/java/org/nuiton/util/csv/ImportRow.java trunk/nuiton-csv/src/main/java/org/nuiton/util/csv/ImportSetErrorInfo.java trunk/nuiton-csv/src/test/java/org/nuiton/util/csv/Import2Test.java Added: trunk/nuiton-csv/src/main/java/org/nuiton/util/csv/AbstractImportErrorInfo.java =================================================================== --- trunk/nuiton-csv/src/main/java/org/nuiton/util/csv/AbstractImportErrorInfo.java (rev 0) +++ trunk/nuiton-csv/src/main/java/org/nuiton/util/csv/AbstractImportErrorInfo.java 2012-09-18 13:50:54 UTC (rev 2421) @@ -0,0 +1,67 @@ +package org.nuiton.util.csv; + +/* + * #%L + * Nuiton Utils :: Nuiton Csv + * $Id$ + * $HeadURL$ + * %% + * Copyright (C) 2011 - 2012 CodeLutin + * %% + * This program 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. + * + * This program 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 General Lesser Public License for more details. + * + * You should have received a copy of the GNU General Lesser Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/lgpl-3.0.html>. + * #L% + */ + +/** + * Abstract import esxception which contains the {@link ImportRow} object. + * + * @author tchemit <chemit@codelutin.com> + * @since 2.6.3 + */ +public abstract class AbstractImportErrorInfo<E> { + + protected final ImportableColumn<E, Object> field; + + protected final long lineNumber; + + protected final E bean; + + protected final Throwable cause; + + protected AbstractImportErrorInfo(ImportRow<E> row, + ImportableColumn<E, Object> field, + Throwable cause) { + this.cause = cause; + this.field = field; + lineNumber = row.getLineNumber(); + bean = row.getBean(); + } + + public long getLineNumber() { + return lineNumber; + } + + public E getBean() { + return bean; + } + + public ImportableColumn<E, Object> getField() { + return field; + } + + public Throwable getCause() { + return cause; + } +} Property changes on: trunk/nuiton-csv/src/main/java/org/nuiton/util/csv/AbstractImportErrorInfo.java ___________________________________________________________________ Added: svn:keywords + Author Date Id Revision HeadURL Added: svn:eol-style + native Added: trunk/nuiton-csv/src/main/java/org/nuiton/util/csv/Import2.java =================================================================== --- trunk/nuiton-csv/src/main/java/org/nuiton/util/csv/Import2.java (rev 0) +++ trunk/nuiton-csv/src/main/java/org/nuiton/util/csv/Import2.java 2012-09-18 13:50:54 UTC (rev 2421) @@ -0,0 +1,449 @@ +package org.nuiton.util.csv; + +/* + * #%L + * Nuiton Utils :: Nuiton Csv + * $Id$ + * $HeadURL$ + * %% + * Copyright (C) 2011 - 2012 CodeLutin + * %% + * This program 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. + * + * This program 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 General Lesser Public License for more details. + * + * You should have received a copy of the GNU General Lesser Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/lgpl-3.0.html>. + * #L% + */ + +import com.csvreader.CsvReader; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.nuiton.util.StringUtil; + +import java.io.Closeable; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Set; + +import static org.nuiton.i18n.I18n._; + +/** + * Improve the first {@link Import} class with the notion of {@link ImportRow}. + * + * @author tchemit <chemit@codelutin.com> + * @since 2.6.3 + */ +public class Import2<E> implements Iterable<ImportRow<E>>, Closeable { + + /** Logger. */ + private static final Log log = LogFactory.getLog(Import2.class); + + /** Csv import configuration. */ + protected final ImportConf conf; + + /** Csv import model. */ + protected ImportModel<E> model; + + /** Csv reader (this is the input). */ + protected CsvReader reader; + + /** + * A flag to know if model was already validated. + * <p/> + * Save once done to prevent multiple headers read leading to consider + * first lines as headers. + */ + protected boolean validate; + + public static <E> Import2<E> newImport(ImportModel<E> model, + InputStream inputStream) { + return newImport(new ImportConf(), model, inputStream); + } + + public static <E> Import2<E> newImport(ImportModel<E> model, + Reader reader) { + return newImport(new ImportConf(), model, reader); + } + + public static <E> Import2<E> newImport(ImportConf conf, + ImportModel<E> model, + InputStream inputStream) { + return new Import2<E>(conf, model, inputStream); + } + + public static <E> Import2<E> newImport(ImportConf conf, + ImportModel<E> model, + Reader reader) { + return new Import2<E>(conf, model, reader); + } + + /** + * Define iterator over import. First of all, the input stream will be + * validated based on defined model. Iteration will be done on all csv + * rows except first headers line. + * + * @return the Iterator used for csv iteration + * @see #prepareAndValidate() + */ + @Override + public Iterator<ImportRow<E>> iterator() { + + prepareAndValidate(); + + ImportRowIterator itr = new ImportRowIterator(getNonIgnoredHeaders()); + return itr; + } + + @Override + public void close() { + reader.close(); + } + + /** + * Read the first mandatory headers line and validate it with the input + * model. This will check if headers are unique, known by the model and + * if mandatory headers exist in the file. During this phase, the model + * will retrieve headers value with {@link + * ImportModel#pushCsvHeaderNames(List)} call. + * + * @since 2.4.1 + */ + public void prepareAndValidate() { + + if (validate) { + + // was already validated + return; + } + + // mark as validated + validate = true; + + // obtains headers + String[] headers = getHeaders(); + + if (log.isTraceEnabled()) { + log.trace("headers of the CSV file are : " + + Arrays.toString(headers)); + } + + // hook to do some stuff from the model + model.pushCsvHeaderNames(Arrays.asList(headers)); + + // check model columns name are unique + checkUniqueModelColumnNames(); + + if (!conf.isIgnoreUnknownHeader()) { + + // check that given headers from csv file are all known + checkHeaderNamesAreAllKnown(headers); + } + + // check all mandatories column are on csv header + checkAllMandatoryHeadersArePresent(headers); + } + + protected <T> String readValue(ImportableColumn<E, T> field) throws Exception { + String value = reader.get(field.getHeaderName()); + return value; + } + + protected <T> T parseValue(ImportableColumn<E, T> field, + long lineNumber, + String value) { + try { + T parsedValue = field.parseValue(value); + return parsedValue; + } catch (Exception e) { + String message = _("csv.import.error.unableToParseValue", + value, field.getHeaderName(), lineNumber) + + "\n" + e.getMessage(); + throw new ImportRuntimeException(message, e); + } + } + + protected <T> void setValue(ImportableColumn<E, T> field, + long lineNumber, + E element, + T parsedValue) { + try { + field.setValue(element, parsedValue); + } catch (Exception e) { + String message = _("csv.import.error.unableToSetValue", + parsedValue, + element.toString(), + lineNumber, field.getHeaderName()); + if (log.isErrorEnabled()) { + log.error(message); + } + throw new ImportRuntimeException(message, e); + } + } + + protected void checkHeaderNamesAreAllKnown(String[] headers) { + List<String> csvHeaders = new ArrayList<String>(); + Collections.addAll(csvHeaders, headers); + + for (ImportableColumn<E, ?> field : model.getColumnsForImport()) { + csvHeaders.remove(field.getHeaderName()); + } + if (!csvHeaders.isEmpty()) { + List<String> validHeaderNames = new LinkedList<String>(); + for (ImportableColumn<E, ?> importableColumn : + model.getColumnsForImport()) { + validHeaderNames.add(importableColumn.getHeaderName()); + } + String validationMessage = + _("csv.import.error.unrecognizedHeaders", + StringUtil.join(csvHeaders, ", ", true), + StringUtil.join(validHeaderNames, ", ", true)); + throw new ImportRuntimeException(validationMessage); + } + } + + protected void checkUniqueModelColumnNames() { + Set<String> headerNames = new HashSet<String>(); + Set<String> doubleHeaderNames = new HashSet<String>(); + for (ImportableColumn<E, ?> importableColumn : + model.getColumnsForImport()) { + String headerName = importableColumn.getHeaderName(); + boolean alreadyUsed = !headerNames.add(headerName); + if (alreadyUsed) { + doubleHeaderNames.add(headerName); + } + } + if (!doubleHeaderNames.isEmpty()) { + String message = _("csv.import.error.duplicatedHeaders", + StringUtil.join(doubleHeaderNames, ", ", true)); + + throw new ImportRuntimeException( + message); + } + } + + protected void checkAllMandatoryHeadersArePresent(String[] headers) { + + List<String> csvHeaders = new ArrayList<String>(); + Collections.addAll(csvHeaders, headers); + + List<String> mandatoryHeadersNames = new ArrayList<String>(); + for (ImportableColumn<E, ?> field : getAllMandatoryHeaders()) { + mandatoryHeadersNames.add(field.getHeaderName()); + } + mandatoryHeadersNames.removeAll(csvHeaders); + + if (!mandatoryHeadersNames.isEmpty()) { + String validationMessage = + _("csv.import.error.missingMandatoryHeaders", + StringUtil.join(mandatoryHeadersNames, ", ", true)); + throw new ImportRuntimeException(validationMessage); + } + } + + protected String[] getHeaders() throws ImportRuntimeException { + try { + boolean canReadHeaders = reader.readHeaders(); + if (!canReadHeaders) { + throw new ImportRuntimeException( + _("csv.import.error.unableToReadHeaders")); + } + } catch (IOException e) { + throw new ImportRuntimeException( + _("csv.import.error.unableToReadHeaders"), e); + } + + try { + String[] result = reader.getHeaders(); + return result; + } catch (IOException e) { + throw new ImportRuntimeException( + _("csv.import.error.unableToReadHeaders"), e); + } + } + + protected List<ImportableColumn<E, Object>> getNonIgnoredHeaders() { + List<ImportableColumn<E, Object>> nonIgnoredHeaders = + new ArrayList<ImportableColumn<E, Object>>(); + for (ImportableColumn<E, Object> field : model.getColumnsForImport()) { + if (!field.isIgnored()) { + nonIgnoredHeaders.add(field); + } + } + return nonIgnoredHeaders; + } + + protected List<ImportableColumn<E, ?>> getAllMandatoryHeaders() { + List<ImportableColumn<E, ?>> allMandatoryHeaders = + new ArrayList<ImportableColumn<E, ?>>(); + for (ImportableColumn<E, ?> field : model.getColumnsForImport()) { + if (field.isMandatory()) { + allMandatoryHeaders.add(field); + } + } + return allMandatoryHeaders; + } + + protected Import2(ImportConf conf, ImportModel<E> model, InputStream inputStream) { + if (inputStream == null) { + throw new NullPointerException("inputStream is null"); + } + this.conf = conf; + this.model = model; + reader = new CsvReader(inputStream, model.getSeparator(), Charset.forName("UTF-8")); + reader.setTrimWhitespace(true); + this.reader.setSafetySwitch(conf.isSafetySwitch()); + } + + protected Import2(ImportConf conf, ImportModel<E> model, Reader reader) { + if (reader == null) { + throw new NullPointerException("reader is null"); + } + this.conf = conf; + this.model = model; + this.reader = new CsvReader(reader, model.getSeparator()); + this.reader.setTrimWhitespace(true); + this.reader.setSafetySwitch(conf.isSafetySwitch()); + } + + /** + * Read the next row from the reader and return {@code true} if line + * was successfully read. + * + * @return {@code true} if line was successfully read, says in fact there is + * something after this line. + * @throws ImportRuntimeException if could not read line + */ + protected boolean readRow() throws ImportRuntimeException { + try { + boolean hasNext = reader.readRecord(); + return hasNext; + } catch (IOException e) { + reader.close(); + throw new ImportRuntimeException(_("csv.import.error.unableToReadLine", 1), e); + } + } + + private class ImportRowIterator implements Iterator<ImportRow<E>> { + + protected final ImportRow<E> row; + + protected final List<ImportableColumn<E, Object>> columns; + + private ImportRowIterator(List<ImportableColumn<E, Object>> columns) { + + // get once for all columns to import + this.columns = columns; + row = new ImportRow<E>(); + + // read first line since first line is header + boolean hasNext = readRow(); + row.setNext(hasNext); + } + + @Override + public boolean hasNext() { + return row.hasNext(); + } + + @Override + public ImportRow<E> next() + throws NoSuchElementException, ImportRuntimeException { + + if (!hasNext()) { + throw new NoSuchElementException(); + } + + E element = model.newEmptyInstance(); + + row.prepareNextRow(element); + + long lineNumber = row.getLineNumber(); + + boolean strictMode = conf.isStrictMode(); + + for (ImportableColumn<E, Object> field : columns) { + + // read value from csv cell + String value; + try { + value = readValue(field); + } catch (Exception e) { + + + if (strictMode) { + // throw an error + throw new ImportRuntimeException( + _("csv.import.error.unableToReadField", + field.getHeaderName(), lineNumber), e); + } else { + row.addError(new ImportReadErrorInfo<E>( + row, field, e)); + } + continue; + } + + // contravariance ftw + Object parsedValue; + try { + parsedValue = parseValue(field, lineNumber, value); + } catch (Exception e) { + if (strictMode) { + String message = _("csv.import.error.unableToParseValue", + value, field.getHeaderName(), lineNumber) + + "\n" + e.getMessage(); + throw new ImportRuntimeException(message, e); + } else { + row.addError(new ImportParseErrorInfo<E>( + row, field, value, e)); + } + continue; + } + + try { + // set value to element + setValue(field, lineNumber, element, parsedValue); + } catch (Exception e) { + if (strictMode) { + String message = _("csv.import.error.unableToSetValue", + parsedValue, + element.toString(), + lineNumber, field.getHeaderName()); + throw new ImportRuntimeException(message, e); + } else { + row.addError(new ImportSetErrorInfo<E>( + row, field, value, parsedValue, e)); + } + } + } + + // check if there is a next row to read + row.setNext(readRow()); + + return row; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + } +} \ No newline at end of file Property changes on: trunk/nuiton-csv/src/main/java/org/nuiton/util/csv/Import2.java ___________________________________________________________________ Added: svn:keywords + Author Date Id Revision HeadURL Added: svn:eol-style + native Added: trunk/nuiton-csv/src/main/java/org/nuiton/util/csv/ImportConf.java =================================================================== --- trunk/nuiton-csv/src/main/java/org/nuiton/util/csv/ImportConf.java (rev 0) +++ trunk/nuiton-csv/src/main/java/org/nuiton/util/csv/ImportConf.java 2012-09-18 13:50:54 UTC (rev 2421) @@ -0,0 +1,87 @@ +package org.nuiton.util.csv; + +/* + * #%L + * Nuiton Utils :: Nuiton Csv + * $Id$ + * $HeadURL$ + * %% + * Copyright (C) 2011 - 2012 CodeLutin + * %% + * This program 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. + * + * This program 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 General Lesser Public License for more details. + * + * You should have received a copy of the GNU General Lesser Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/lgpl-3.0.html>. + * #L% + */ + +import com.csvreader.CsvReader; + +/** + * To configure an import using the {@link Import2}. + * <p/> + * If you do not give this object to the {@link Import2}, then it will + * instanciate a new one using all default values. + * + * @author tchemit <chemit@codelutin.com> + * @since 2.6.3 + */ +public class ImportConf { + + /** + * Flag to turn or not the safetySwitch (see {@link CsvReader#getSafetySwitch()}). + * <p/> + * By default, not used. + */ + protected boolean safetySwitch = false; + + /** + * Flag to use a strict mode (says import will failed at the first error), + * if setted to {@link false}, then errors for each rows will be stored in + * the current row and import will continue to the end. + * <p/> + * By default, used (strict mode). + */ + protected boolean strictMode = true; + + /** + * Flag to ignore header found in a import file and not declared in the + * import model. + * <p/> + * By default, not used (strict mode). + */ + protected boolean ignoreUnknownHeader = false; + + public boolean isSafetySwitch() { + return safetySwitch; + } + + public void setSafetySwitch(boolean safetySwitch) { + this.safetySwitch = safetySwitch; + } + + public boolean isStrictMode() { + return strictMode; + } + + public void setStrictMode(boolean strictMode) { + this.strictMode = strictMode; + } + + public boolean isIgnoreUnknownHeader() { + return ignoreUnknownHeader; + } + + public void setIgnoreUnknownHeader(boolean ignoreUnknownHeader) { + this.ignoreUnknownHeader = ignoreUnknownHeader; + } +} Property changes on: trunk/nuiton-csv/src/main/java/org/nuiton/util/csv/ImportConf.java ___________________________________________________________________ Added: svn:keywords + Author Date Id Revision HeadURL Added: svn:eol-style + native Added: trunk/nuiton-csv/src/main/java/org/nuiton/util/csv/ImportParseErrorInfo.java =================================================================== --- trunk/nuiton-csv/src/main/java/org/nuiton/util/csv/ImportParseErrorInfo.java (rev 0) +++ trunk/nuiton-csv/src/main/java/org/nuiton/util/csv/ImportParseErrorInfo.java 2012-09-18 13:50:54 UTC (rev 2421) @@ -0,0 +1,48 @@ +package org.nuiton.util.csv; + +/* + * #%L + * Nuiton Utils :: Nuiton Csv + * $Id$ + * $HeadURL$ + * %% + * Copyright (C) 2011 - 2012 CodeLutin + * %% + * This program 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. + * + * This program 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 General Lesser Public License for more details. + * + * You should have received a copy of the GNU General Lesser Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/lgpl-3.0.html>. + * #L% + */ + +/** + * Exception to be thrown when a parse error occurs at import time. + * + * @author tchemit <chemit@codelutin.com> + * @since 2.6.3 + */ +public class ImportParseErrorInfo<E> extends AbstractImportErrorInfo<E> { + + protected final String value; + + public ImportParseErrorInfo(ImportRow<E> row, + ImportableColumn<E, Object> field, + String value, + Throwable cause) { + super(row, field, cause); + this.value = value; + } + + public String getValue() { + return value; + } +} Property changes on: trunk/nuiton-csv/src/main/java/org/nuiton/util/csv/ImportParseErrorInfo.java ___________________________________________________________________ Added: svn:keywords + Author Date Id Revision HeadURL Added: svn:eol-style + native Added: trunk/nuiton-csv/src/main/java/org/nuiton/util/csv/ImportReadErrorInfo.java =================================================================== --- trunk/nuiton-csv/src/main/java/org/nuiton/util/csv/ImportReadErrorInfo.java (rev 0) +++ trunk/nuiton-csv/src/main/java/org/nuiton/util/csv/ImportReadErrorInfo.java 2012-09-18 13:50:54 UTC (rev 2421) @@ -0,0 +1,40 @@ +package org.nuiton.util.csv; + +/* + * #%L + * Nuiton Utils :: Nuiton Csv + * $Id$ + * $HeadURL$ + * %% + * Copyright (C) 2011 - 2012 CodeLutin + * %% + * This program 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. + * + * This program 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 General Lesser Public License for more details. + * + * You should have received a copy of the GNU General Lesser Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/lgpl-3.0.html>. + * #L% + */ + +/** + * Exception to be thrown when a read error occurs at import time. + * + * @author tchemit <chemit@codelutin.com> + * @since 2.6.3 + */ +public class ImportReadErrorInfo<E> extends AbstractImportErrorInfo<E> { + + public ImportReadErrorInfo(ImportRow<E> row, + ImportableColumn<E, Object> field, + Throwable cause) { + super(row, field, cause); + } +} Property changes on: trunk/nuiton-csv/src/main/java/org/nuiton/util/csv/ImportReadErrorInfo.java ___________________________________________________________________ Added: svn:keywords + Author Date Id Revision HeadURL Added: svn:eol-style + native Added: trunk/nuiton-csv/src/main/java/org/nuiton/util/csv/ImportRow.java =================================================================== --- trunk/nuiton-csv/src/main/java/org/nuiton/util/csv/ImportRow.java (rev 0) +++ trunk/nuiton-csv/src/main/java/org/nuiton/util/csv/ImportRow.java 2012-09-18 13:50:54 UTC (rev 2421) @@ -0,0 +1,104 @@ +package org.nuiton.util.csv; + +/* + * #%L + * Nuiton Utils :: Nuiton Csv + * $Id$ + * $HeadURL$ + * %% + * Copyright (C) 2011 - 2012 CodeLutin + * %% + * This program 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. + * + * This program 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 General Lesser Public License for more details. + * + * You should have received a copy of the GNU General Lesser Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/lgpl-3.0.html>. + * #L% + */ + +import com.google.common.collect.Sets; +import org.apache.commons.collections.CollectionUtils; + +import java.util.Set; + +/** + * Object to box a row to import. + * <p/> + * It contains all the context for the current row to import. + * + * @author tchemit <chemit@codelutin.com> + * @since 2.6.3 + */ +public class ImportRow<E> { + + /** Current line number. */ + protected long lineNumber; + + /** Current bean loaded by the import tool for the current row. */ + protected E bean; + + /** Errors found while loading the row into the bean. */ + protected Set<AbstractImportErrorInfo<E>> errors; + + protected boolean next; + + public ImportRow(ImportRow<E> row) { + this.lineNumber = row.getLineNumber(); + this.bean = row.getBean(); + this.errors = Sets.newHashSet(row.getErrors()); + this.setNext(row.hasNext()); + } + + public ImportRow() { + } + + public long getLineNumber() { + return lineNumber; + } + + public E getBean() { + return bean; + } + + public Set<AbstractImportErrorInfo<E>> getErrors() { + return errors; + } + + public boolean isValid() { + return CollectionUtils.isEmpty(errors); + } + + public boolean hasNext() { + return next; + } + + public void setNext(boolean next) { + this.next = next; + } + + public void setLineNumber(long lineNumber) { + this.lineNumber = lineNumber; + } + + public void addError(AbstractImportErrorInfo<E> error) { + if (errors == null) { + errors = Sets.newHashSet(); + } + errors.add(error); + } + + public void prepareNextRow(E bean) { + this.bean = bean; + lineNumber++; + errors = null; + } + +} Property changes on: trunk/nuiton-csv/src/main/java/org/nuiton/util/csv/ImportRow.java ___________________________________________________________________ Added: svn:keywords + Author Date Id Revision HeadURL Added: svn:eol-style + native Added: trunk/nuiton-csv/src/main/java/org/nuiton/util/csv/ImportSetErrorInfo.java =================================================================== --- trunk/nuiton-csv/src/main/java/org/nuiton/util/csv/ImportSetErrorInfo.java (rev 0) +++ trunk/nuiton-csv/src/main/java/org/nuiton/util/csv/ImportSetErrorInfo.java 2012-09-18 13:50:54 UTC (rev 2421) @@ -0,0 +1,56 @@ +package org.nuiton.util.csv; + +/* + * #%L + * Nuiton Utils :: Nuiton Csv + * $Id$ + * $HeadURL$ + * %% + * Copyright (C) 2011 - 2012 CodeLutin + * %% + * This program 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. + * + * This program 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 General Lesser Public License for more details. + * + * You should have received a copy of the GNU General Lesser Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/lgpl-3.0.html>. + * #L% + */ + +/** + * Exception to be thrown when a set error occurs at import time. + * + * @author tchemit <chemit@codelutin.com> + * @since 2.6.3 + */ +public class ImportSetErrorInfo<E> extends AbstractImportErrorInfo<E> { + + protected final String value; + + protected final Object parsedValue; + + public ImportSetErrorInfo(ImportRow<E> row, + ImportableColumn<E, Object> field, + String value, + Object parsedValue, + Throwable cause) { + super(row, field, cause); + this.value = value; + this.parsedValue = parsedValue; + } + + public String getValue() { + return value; + } + + public Object getParsedValue() { + return parsedValue; + } +} Property changes on: trunk/nuiton-csv/src/main/java/org/nuiton/util/csv/ImportSetErrorInfo.java ___________________________________________________________________ Added: svn:keywords + Author Date Id Revision HeadURL Added: svn:eol-style + native Added: trunk/nuiton-csv/src/test/java/org/nuiton/util/csv/Import2Test.java =================================================================== --- trunk/nuiton-csv/src/test/java/org/nuiton/util/csv/Import2Test.java (rev 0) +++ trunk/nuiton-csv/src/test/java/org/nuiton/util/csv/Import2Test.java 2012-09-18 13:50:54 UTC (rev 2421) @@ -0,0 +1,172 @@ +package org.nuiton.util.csv; + +/* + * #%L + * Nuiton Utils :: Nuiton Csv + * $Id$ + * $HeadURL$ + * %% + * Copyright (C) 2011 - 2012 CodeLutin + * %% + * This program 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. + * + * This program 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 General Lesser Public License for more details. + * + * You should have received a copy of the GNU General Lesser Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/lgpl-3.0.html>. + * #L% + */ + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.nuiton.util.DateUtil; +import org.nuiton.util.csv.ext.AbstractImportModel; + +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +/** + * Tests the {@link Import2}. + * + * @author tchemit <chemit@codelutin.com> + * @since 2.6.3 + */ +public class Import2Test { + + protected ImportModel<RowBean> importModel; + + @Before + public void setUp() { + AbstractImportModel<RowBean> importModel = new AbstractImportModel<RowBean>(';') { + @Override + public RowBean newEmptyInstance() { + return new RowBean(); + } + }; + importModel.newMandatoryColumn("NUMBER", "number", Common.INTEGER); + importModel.newMandatoryColumn("TITLE", "title", Common.STRING); + importModel.newMandatoryColumn("DATE", "date", new Common.DateValue("yyyy-MM-dd")); + importModel.newMandatoryColumn("ROWBEANENUM", "rowBeanEnum", Common.newEnumByNameParserFormatter(RowBeanEnum.class)); + + this.importModel = importModel; + } + + @Test + public void testSimpleImportWithNoData() throws Exception { + String content = "DATE;NUMBER;TITLE;ROWBEANENUM"; + + List<RowBean> rows; + rows = importContent(importModel, content); + Assert.assertEquals(0, rows.size()); + + content = "DATE;NUMBER;TITLE;ROWBEANENUM\n"; + rows = importContent(importModel, content); + Assert.assertEquals(0, rows.size()); + } + + @Test + public void testSimpleImportWithOneLine() throws Exception { + String content = "DATE;NUMBER;TITLE;ROWBEANENUM\n2011-12-05;18;\"1ère ligne\";ZERO;"; + + List<RowBean> rows = importContent(importModel, content); + Assert.assertEquals(1, rows.size()); + assertRowEquals(rows.get(0), DateUtil.createDate(5, 12, 2011), 18, "1ère ligne", RowBeanEnum.ZERO); + } + + @Test + public void testSimpleImport() throws Exception { + String content = "DATE;NUMBER;TITLE;ROWBEANENUM\n" + + "2011-12-05;18;\"1ère ligne\";ZERO\n" + + "2011-12-06;19;\"2ème ligne\";ONE\n" + + "2011-12-07;21;\"3ème ligne\";TWO"; + List<RowBean> rows = importContent(importModel, content); + assertRowEquals(rows.get(0), DateUtil.createDate(5, 12, 2011), 18, "1ère ligne", RowBeanEnum.ZERO); + assertRowEquals(rows.get(1), DateUtil.createDate(6, 12, 2011), 19, "2ème ligne", RowBeanEnum.ONE); + assertRowEquals(rows.get(2), DateUtil.createDate(7, 12, 2011), 21, "3ème ligne", RowBeanEnum.TWO); + } + + @Test + public void testSimpleImportWithExtraUnknownHEaders() throws Exception { + String content = "DATE;NUMBER;TITLE;ROWBEANENUM;BLABLA\n" + + "2011-12-05;18;\"1ère ligne\";ZERO;BLABLA\n" + + "2011-12-06;19;\"2ème ligne\";ONE;BLABLA\n" + + "2011-12-07;21;\"3ème ligne\";TWO;BLABLA"; + ImportConf conf = new ImportConf(); + conf.setIgnoreUnknownHeader(true); + List<RowBean> rows = importContent(conf, importModel, content); + assertRowEquals(rows.get(0), DateUtil.createDate(5, 12, 2011), 18, "1ère ligne", RowBeanEnum.ZERO); + assertRowEquals(rows.get(1), DateUtil.createDate(6, 12, 2011), 19, "2ème ligne", RowBeanEnum.ONE); + assertRowEquals(rows.get(2), DateUtil.createDate(7, 12, 2011), 21, "3ème ligne", RowBeanEnum.TWO); + } + + @Test + public void testSimpleImportWithNotStrictMode() throws Exception { + String content = "DATE;NUMBER;TITLE;ROWBEANENUM\n" + + "2011-12-05;18;\"1ère ligne\";BOUH!\n" + + "NOT_A_DATE;19;\"2ème ligne\";ONE\n" + + "2011-12-07;AHAH;\"3ème ligne\";TWO"; + ImportConf conf = new ImportConf(); + conf.setStrictMode(false); + List<RowBean> rows = importContent(conf, importModel, content); + assertRowEquals(rows.get(0), DateUtil.createDate(5, 12, 2011), 18, "1ère ligne", null); + assertRowEquals(rows.get(1), null, 19, "2ème ligne", RowBeanEnum.ONE); + assertRowEquals(rows.get(2), DateUtil.createDate(7, 12, 2011), null, "3ème ligne", RowBeanEnum.TWO); + } + + protected List<RowBean> importContent(ImportConf conf, ImportModel<RowBean> model, + String content) throws IOException { + Reader reader = new StringReader(content); + try { + Import2<RowBean> rowImport = Import2.newImport(conf, model, reader); + try { + List<RowBean> result = new ArrayList<RowBean>(); + for (ImportRow<RowBean> row : rowImport) { + result.add(row.getBean()); + } + return result; + } finally { + rowImport.close(); + } + } finally { + reader.close(); + } + } + + protected List<RowBean> importContent(ImportModel<RowBean> model, + String content) throws IOException { + Reader reader = new StringReader(content); + try { + Import2<RowBean> rowImport = Import2.newImport(model, reader); + try { + List<RowBean> result = new ArrayList<RowBean>(); + for (ImportRow<RowBean> row : rowImport) { + result.add(row.getBean()); + } + return result; + } finally { + rowImport.close(); + } + } finally { + reader.close(); + } + } + + private void assertRowEquals(RowBean row, Date date, Integer number, String title, RowBeanEnum rowBeanEnum) { + Assert.assertEquals(date, row.getDate()); + Assert.assertEquals(number, row.getNumber()); + Assert.assertEquals(title, row.getTitle()); + Assert.assertEquals(rowBeanEnum, row.getRowBeanEnum()); + } +} Property changes on: trunk/nuiton-csv/src/test/java/org/nuiton/util/csv/Import2Test.java ___________________________________________________________________ Added: svn:keywords + Author Date Id Revision HeadURL Added: svn:eol-style + native