package com.revolsys.record.io.format.csv; import java.io.IOException; import java.io.Reader; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; import com.revolsys.io.FileUtil; import com.revolsys.util.Exceptions; public class CsvIterator implements Iterator<List<String>>, Iterable<List<String>> { private static final int BUFFER_SIZE = 8096; private final char fieldSeparator; private final char[] buffer = new char[BUFFER_SIZE]; /** The current record. */ private List<String> currentRecord; /** Flag indicating if there are more records to be read. */ private boolean hasNext = true; /** The reader to */ private final Reader in; private int index = 0; private int readCount; private final StringBuilder sb = new StringBuilder(); /** * Constructs CSVReader with supplied separator and quote char. * * @param reader The reader to the CSV file. * @throws IOException */ public CsvIterator(final Reader in) { this(in, Csv.FIELD_SEPARATOR); } public CsvIterator(final Reader in, final char fieldSeparator) { this.in = in; this.fieldSeparator = fieldSeparator; readNextRecord(); } /** * Closes the underlying reader. * * @throws IOException if the close fails */ public void close() { FileUtil.closeSilent(this.in); } /** * Returns <tt>true</tt> if the iteration has more elements. * * @return <tt>true</tt> if the iterator has more elements. */ @Override public boolean hasNext() { return this.hasNext; } @Override public Iterator<List<String>> iterator() { return this; } /** * Return the next record from the iterator. * * @return The record */ @Override public List<String> next() { if (!this.hasNext) { throw new NoSuchElementException("No more elements"); } else { final List<String> object = this.currentRecord; readNextRecord(); return object; } } private List<String> parseRecord() throws IOException { final StringBuilder sb = this.sb; final Reader in = this.in; sb.delete(0, sb.length()); final List<String> fields = new ArrayList<>(); boolean inQuotes = false; boolean hadQuotes = false; while (this.readCount != -1) { if (this.index >= this.readCount) { this.index = 0; this.readCount = in.read(this.buffer, 0, BUFFER_SIZE); if (this.readCount < 0) { if (fields.isEmpty()) { this.hasNext = false; return null; } else { return fields; } } } final char c = this.buffer[this.index++]; switch (c) { case '"': hadQuotes = true; final char nextChar = previewNextChar(); if (inQuotes && nextChar == '"') { sb.append('"'); this.index++; } else { inQuotes = !inQuotes; if (sb.length() > 0 && !(nextChar != this.fieldSeparator || nextChar != '\n' || nextChar != 0)) { sb.append(c); } } break; case '\r': if (previewNextChar() == '\n') { } else { if (inQuotes) { sb.append('\n'); } else { if (hadQuotes || sb.length() > 0) { fields.add(sb.toString()); sb.delete(0, sb.length()); } else { fields.add(null); } return fields; } } break; case '\n': if (previewNextChar() == '\r') { this.index++; } if (inQuotes) { sb.append(c); } else { if (hadQuotes || sb.length() > 0) { fields.add(sb.toString()); sb.delete(0, sb.length()); } else { fields.add(null); } return fields; } break; default: if (c == this.fieldSeparator) { if (inQuotes) { sb.append(c); } else { if (hadQuotes || sb.length() > 0) { fields.add(sb.toString()); sb.delete(0, sb.length()); } else { fields.add(null); } hadQuotes = false; } } else { sb.append(c); } break; } } this.hasNext = false; return null; } private char previewNextChar() throws IOException { if (this.index >= this.readCount) { this.index = 0; this.readCount = this.in.read(this.buffer, 0, BUFFER_SIZE); if (this.readCount < 0) { return 0; } } return this.buffer[this.index]; } /** * Reads the next line from the buffer and converts to a string array. * * @return a string array with each comma-separated element as a separate * entry. * @throws IOException if bad things happen during the read */ private List<String> readNextRecord() { if (this.hasNext) { try { this.currentRecord = parseRecord(); } catch (final IOException e) { Exceptions.throwUncheckedException(e); } return this.currentRecord; } else { return null; } } /** * Removing items from the iterator is not supported. */ @Override public void remove() { throw new UnsupportedOperationException(); } }