/*
* Copyright 2015 Oliver Siegmar
*
* 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 de.siegmar.fastcsv.reader;
import java.io.Closeable;
import java.io.IOException;
import java.io.Reader;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* This class is responsible for parsing CSV data from the passed-in Reader.
*
* @author Oliver Siegmar
*/
public final class CsvParser implements Closeable {
private final RowReader rowReader;
private final boolean ;
private final boolean skipEmptyRows;
private final boolean errorOnDifferentFieldCount;
private Map<String, Integer> ;
private List<String> ;
private long lineNo;
private int firstLineFieldCount = -1;
CsvParser(final Reader reader, final char fieldSeparator, final char textDelimiter,
final boolean containsHeader, final boolean skipEmptyRows,
final boolean errorOnDifferentFieldCount) {
rowReader = new RowReader(reader, fieldSeparator, textDelimiter);
this.containsHeader = containsHeader;
this.skipEmptyRows = skipEmptyRows;
this.errorOnDifferentFieldCount = errorOnDifferentFieldCount;
}
/**
* Returns the header fields - {@code null} if no header exists. The returned list is
* unmodifiable. Use {@link CsvReader#setContainsHeader(boolean)} to enable header parsing.
* Also note, that the header is only available <strong>after</strong> first invocation of
* {@link #nextRow()}.
*
* @return the header fields
* @throws IllegalStateException if header parsing is not enabled or {@link #nextRow()} wasn't
* called before.
*/
public List<String> () {
if (!containsHeader) {
throw new IllegalStateException("No header available - header parsing is disabled");
}
if (lineNo == 0) {
throw new IllegalStateException("No header available - call nextRow() first");
}
return headerList;
}
/**
* Reads a complete {@link CsvRow} that might be made up of multiple lines in the underlying
* CSV file.
*
* @return a CsvRow or {@code null} if end of file reached
* @throws IOException if an error occurred while reading data
*/
public CsvRow nextRow() throws IOException {
while (!rowReader.isFinished()) {
final long startingLineNo = lineNo + 1;
final RowReader.Line line = rowReader.readLine();
final String[] currentFields = line.getFields();
lineNo += line.getLines();
final int fieldCount = currentFields.length;
// reached end of data in a new line?
if (fieldCount == 0) {
break;
}
// skip empty rows
if (skipEmptyRows && fieldCount == 1 && currentFields[0].isEmpty()) {
continue;
}
// check the field count consistency on every row
if (errorOnDifferentFieldCount) {
if (firstLineFieldCount == -1) {
firstLineFieldCount = fieldCount;
} else if (fieldCount != firstLineFieldCount) {
throw new IOException(
String.format("Line %d has %d fields, but first line has %d fields",
lineNo, fieldCount, firstLineFieldCount));
}
}
final List<String> fieldList = Arrays.asList(currentFields);
// initialize header
if (containsHeader && headerList == null) {
initHeader(fieldList);
continue;
}
return new CsvRow(startingLineNo, headerMap, fieldList);
}
return null;
}
private void (final List<String> currentFields) {
headerList = Collections.unmodifiableList(currentFields);
final Map<String, Integer> localHeaderMap = new LinkedHashMap<>(currentFields.size());
for (int i = 0; i < currentFields.size(); i++) {
final String field = currentFields.get(i);
if (field != null && !field.isEmpty() && !localHeaderMap.containsKey(field)) {
localHeaderMap.put(field, i);
}
}
headerMap = Collections.unmodifiableMap(localHeaderMap);
}
@Override
public void close() throws IOException {
rowReader.close();
}
}