/** * VMware Continuent Tungsten Replicator * Copyright (C) 2015 VMware, Inc. All rights reserved. * * 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. * * Initial developer(s): Robert Hodges * Contributor(s): */ package com.continuent.tungsten.common.csv; import java.io.BufferedReader; import java.io.IOException; import java.io.Reader; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * Reads CSV format output with appropriate conversions to Java data types. * * @author <a href="mailto:robert.hodges@continuent.com">Robert Hodges</a> * @version 1.0 */ public class CsvReader { // Properties. private String fieldSeparator = ","; private String recordSeparator = "\n"; private boolean collapseSeparators = false; private boolean useHeaders = true; // State. private Map<String, Integer> names = new HashMap<String, Integer>(); private List<String> row; private BufferedReader reader; private int rowCount = 0; /** * Instantiate a new instance with input from provided reader. */ public CsvReader(Reader reader) { this(new BufferedReader(reader)); } /** * Instantiate a new instance with input from provided buffered reader. This * call allows clients to set buffering parameters themselves. */ public CsvReader(BufferedReader reader) { this.reader = new BufferedReader(reader); } /** * Sets the field separator character. */ public void setFieldSeparator(String inputSeparator) { this.fieldSeparator = inputSeparator; } /** * Returns field separator character. */ public String getFieldSeparator() { return fieldSeparator; } /** * Returns true if successive input separators should be treated as a single * separator. */ public boolean isCollapseSeparators() { return collapseSeparators; } /** * If set to true treat successive input separators as a single separator. */ public void setCollapseSeparators(boolean collapseSeparators) { this.collapseSeparators = collapseSeparators; } /** * Sets the record separator character. */ public void setRecordSeparator(String inputSeparator) { this.recordSeparator = inputSeparator; } /** * Returns record separator character. */ public String getRecordSeparator() { return recordSeparator; } /** * Returns true if input should contain column headers in first row. */ public boolean isUseHeaders() { return useHeaders; } /** * If set to true first row must contain column headers. */ public void setUseHeaders(boolean useHeaders) { this.useHeaders = useHeaders; } /** * Returns the current count of rows read. */ public int getRowCount() { return rowCount; } /** * Return names in column order. */ public List<String> getNames() { // Create null-filled array. List<String> nameList = new ArrayList<String>(names.size()); for (int i = 0; i < names.size(); i++) nameList.add(null); // Add names to correct positions in array. for (String name : names.keySet()) { int index = names.get(name); nameList.set(index - 1, name); } return nameList; } /** * Return the number of columns. */ public int getWidth() { return names.size(); } /** * Positions to next row and returns true if there are data to be read. * * @throws IOException Thrown if there is an IO error. */ public boolean next() throws IOException { // If we are on row 1 and headers are enabled, read and store // column names. if (rowCount == 0 && useHeaders) { List<String> row1 = this.read(); if (row1 == null) return false; for (int i = 0; i < row1.size(); i++) { String name = row1.get(i); names.put(name, i + 1); } rowCount++; } // Read the next row. row = this.read(); if (row == null) return false; else { rowCount++; return true; } } /** * Gets a value from the current row. * * @param index Column index where indexes are numbered 1,2,3,...,N with N * being the width of the row in columns * @throws CsvException Thrown if read is invalid */ public String getString(int index) throws CsvException { // Ensure we have a row. if (row == null) { throw new CsvException("Attempt to read when no row is available"); } // Ensure the index is within bounds. if (index > names.size() || index < 1) throw new CsvException("Attempt to read non-existent index: row=" + rowCount + " index=" + index + " columns=" + names.size()); return row.get(index - 1); } /** * Gets a string from the current row. */ public String getString(String key) throws CsvException { Integer index = names.get(key); if (index == null) throw new CsvException("Attempt to read non-existent key: row=" + rowCount + " key=" + key); return getString(index); } // Reads a single row. private List<String> read() throws IOException { String regex = "[" + fieldSeparator + "]"; String s = reader.readLine(); if (s != null && s.length() != 0) { s = s.trim(); return getList(s, regex); } else return null; } // Splits a CSV separated list. private List<String> getList(String s, String regex) { String[] values = s.split(regex); ArrayList<String> list = new ArrayList<String>(); for (String value : values) { if (value.length() > 0 || !collapseSeparators) list.add(value); } return list; } }