package com.revolsys.record.io.format.csv; import java.io.BufferedReader; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.NoSuchElementException; import com.revolsys.geometry.cs.esri.EsriCoordinateSystems; import com.revolsys.geometry.model.GeometryFactory; import com.revolsys.logging.Logs; import com.revolsys.record.ArrayRecord; import com.revolsys.record.Record; import com.revolsys.record.RecordFactory; import com.revolsys.spring.resource.Resource; public class CsvRecordReader extends AbstractRecordReader { private final char fieldSeparator; private BufferedReader in; private Resource resource; public CsvRecordReader(final Resource resource) { this(resource, ArrayRecord.FACTORY, Csv.FIELD_SEPARATOR); } public CsvRecordReader(final Resource resource, final char fieldSeparator) { this(resource, ArrayRecord.FACTORY, fieldSeparator); } public CsvRecordReader(final Resource resource, final RecordFactory<? extends Record> recordFactory) { this(resource, recordFactory, Csv.FIELD_SEPARATOR); } public CsvRecordReader(final Resource resource, final RecordFactory<? extends Record> recordFactory, final char fieldSeparator) { super(recordFactory); this.resource = resource; this.fieldSeparator = fieldSeparator; } private void addValue(final List<String> values, final StringBuilder sb, final boolean hadQuotes) { if (hadQuotes || sb.length() > 0) { values.add(sb.toString()); sb.setLength(0); } else { values.add(null); } } @Override protected void closeDo() { super.closeDo(); final BufferedReader in = this.in; if (in != null) { try { in.close(); } catch (final IOException e) { } this.in = null; } this.resource = null; } @Override protected Record getNext() { try { final List<String> row = readNextRow(); if (row != null && row.size() > 0) { return parseRecord(row); } else { throw new NoSuchElementException(); } } catch (final IOException e) { throw new RuntimeException(e.getMessage(), e); } } @Override protected void initDo() { super.initDo(); try { this.in = this.resource.newBufferedReader(); final List<String> line = readNextRow(); final String baseName = this.resource.getBaseName(); newRecordDefinition(baseName, line); } catch (final IOException e) { Logs.error(this, "Unable to open " + this.resource, e); } catch (final NoSuchElementException e) { } } @Override protected GeometryFactory loadGeometryFactory() { return EsriCoordinateSystems.getGeometryFactory(this.resource); } /** * 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> readNextRow() throws IOException { final char fieldSeparator = this.fieldSeparator; final BufferedReader in = this.in; if (in == null) { throw new NoSuchElementException(); } else { final List<String> values = new ArrayList<>(); final StringBuilder sb = new StringBuilder(); boolean inQuotes = false; boolean hadQuotes = false; while (true) { final int character = in.read(); switch (character) { case -1: return returnEof(values, sb, hadQuotes); case '"': hadQuotes = true; if (inQuotes) { in.mark(1); final int nextCharacter = in.read(); if ('"' == nextCharacter) { sb.append('"'); } else { inQuotes = false; in.reset(); } } else { inQuotes = true; } break; case '\n': if (inQuotes) { sb.append('\n'); } else { if (values.isEmpty()) { if (sb.length() > 0) { values.add(sb.toString()); return values; } else { // skip empty lines } } else { addValue(values, sb, hadQuotes); return values; } } break; case '\r': in.mark(1); final int nextCharacter = in.read(); in.reset(); if (nextCharacter == '\n') { } else { if (inQuotes) { sb.append('\n'); } else { if (values.isEmpty()) { if (sb.length() > 0) { values.add(sb.toString()); return values; } else { // skip empty lines } } else { addValue(values, sb, hadQuotes); return values; } } } break; default: if (character == fieldSeparator) { if (inQuotes) { sb.append(fieldSeparator); } else { addValue(values, sb, hadQuotes); hadQuotes = false; } } else { sb.append((char)character); } break; } } } } private List<String> returnEof(final List<String> values, final StringBuilder sb, final boolean hadQuotes) { if (values.isEmpty()) { if (sb.length() > 0) { values.add(sb.toString()); } else { throw new NoSuchElementException(); } } else { addValue(values, sb, hadQuotes); } return values; } }