RowReaderImpl.java
/*
* CSVeed (https://github.com/42BV/CSVeed)
*
* Copyright 2013-2023 CSVeed.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of The Apache Software License,
* Version 2.0 which accompanies this distribution, and is available at
* https://www.apache.org/licenses/LICENSE-2.0.txt
*/
package org.csveed.row;
import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
import java.util.List;
import org.csveed.api.Header;
import org.csveed.api.Row;
import org.csveed.report.CsvException;
import org.csveed.report.RowError;
import org.csveed.token.ParseException;
import org.csveed.token.ParseStateMachine;
/**
* Builds up a List of cells (String) per read row. Note that this class is stateful, so it can support a per-row parse
* approach as well.
*/
public class RowReaderImpl implements RowReader {
/** The state machine. */
private ParseStateMachine stateMachine = new ParseStateMachine();
/** The row instructions. */
private RowInstructionsImpl rowInstructions;
/** The max number of columns. */
private int maxNumberOfColumns = -1;
/** The header. */
private HeaderImpl header;
/** The reader. */
private Reader reader;
/**
* Instantiates a new row reader impl.
*
* @param reader
* the reader
*/
public RowReaderImpl(Reader reader) {
this(reader, new RowInstructionsImpl());
}
/**
* Instantiates a new row reader impl.
*
* @param reader
* the reader
* @param instructionsInterface
* the instructions interface
*/
public RowReaderImpl(Reader reader, RowInstructions instructionsInterface) {
this.reader = reader;
this.rowInstructions = (RowInstructionsImpl) instructionsInterface;
stateMachine.setSymbolMapping(rowInstructions.getSymbolMapping());
}
@Override
public List<Row> readRows() {
List<Row> allRows = new ArrayList<>();
while (!isFinished()) {
Row row = readRow();
if (row != null && row.size() > 0) {
allRows.add(row);
}
}
return allRows;
}
@Override
public Row readRow() {
getHeader();
Line unmappedLine = readBareLine();
if (unmappedLine == null) {
return null;
}
checkNumberOfColumns(unmappedLine);
return new RowImpl(unmappedLine, getHeader());
}
@Override
public int getCurrentLine() {
return this.stateMachine.getCurrentLine();
}
@Override
public Header getHeader() {
return header == null && rowInstructions.isUseHeader() ? readHeader() : header;
}
/**
* Gets the max number of columns.
*
* @return the max number of columns
*/
public int getMaxNumberOfColumns() {
return this.maxNumberOfColumns;
}
@Override
public Header readHeader() {
if (header != null) {
return header;
}
Line unmappedLine = readBareLine();
if (unmappedLine == null) {
return null;
}
header = new HeaderImpl(unmappedLine);
return header;
}
/**
* Check number of columns.
*
* @param unmappedLine
* the unmapped line
*/
private void checkNumberOfColumns(Line unmappedLine) {
if (maxNumberOfColumns == -1) {
maxNumberOfColumns = header == null ? unmappedLine.size() : header.size();
}
if (unmappedLine.size() != maxNumberOfColumns) {
throw new CsvException(new RowError("The expected number of columns is " + maxNumberOfColumns
+ ", whereas it was " + unmappedLine.size(), unmappedLine.reportOnEndOfLine(), getCurrentLine()));
}
}
@Override
public boolean isFinished() {
return stateMachine.isFinished();
}
/**
* Log settings.
*/
protected void logSettings() {
rowInstructions.logSettings();
this.stateMachine.getSymbolMapping().logSettings();
}
/**
* Read bare line.
*
* @return the line
*/
protected Line readBareLine() {
logSettings();
LineWithInfo line = null;
while (line == null && !stateMachine.isFinished()) {
line = new LineWithInfo();
while (!stateMachine.isFinished()) {
final String token;
final int symbol;
try {
symbol = reader.read();
} catch (IOException err) {
throw new RuntimeException(err);
}
try {
token = stateMachine.offerSymbol(symbol);
} catch (ParseException e) {
throw new CsvException(new RowError(e.getMessage(), line.reportOnEndOfLine(), getCurrentLine()));
}
if (stateMachine.isTrash()) {
continue;
}
if (stateMachine.isTokenStart()) {
line.markStartOfColumn();
}
if (token != null) {
line.addCell(token);
}
line.addCharacter(symbol);
if (stateMachine.isLineFinished()) {
break;
}
}
line = stateMachine.ignoreLine() && rowInstructions.isSkipEmptyLines() ? null : line;
}
return line;
}
@Override
public RowInstructions getRowInstructions() {
return this.rowInstructions;
}
}