Mega Code Archive

 
Categories / Java / Development Class
 

Csv Reader

/*  * Java CSV is a stream based library for reading and writing  * CSV and other delimited data.  *     * Copyright (C) Bruce Dunwiddie bruce@csvreader.com  *  * This library is free software; you can redistribute it and/or  * modify it under the terms of the GNU Lesser General Public  * License as published by the Free Software Foundation; either  * version 2.1 of the License, or (at your option) any later version.  *  * This library is distributed in the hope that it will be useful,  * but WITHOUT ANY WARRANTY; without even the implied warranty of  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  * Lesser General Public License for more details.  *  * You should have received a copy of the GNU Lesser General Public  * License along with this library; if not, write to the Free Software  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA  */ import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.io.StringReader; import java.nio.charset.Charset; import java.text.NumberFormat; import java.util.HashMap; /**  * A stream based parser for parsing delimited text data from a file or a  * stream.  */ public class CsvReader {   private Reader inputStream = null;   private String fileName = null;   // this holds all the values for switches that the user is allowed to set   private UserSettings userSettings = new UserSettings();   private Charset charset = null;   private boolean useCustomRecordDelimiter = false;   // this will be our working buffer to hold data chunks   // read in from the data file   private DataBuffer dataBuffer = new DataBuffer();   private ColumnBuffer columnBuffer = new ColumnBuffer();   private RawRecordBuffer rawBuffer = new RawRecordBuffer();   private boolean[] isQualified = null;   private String rawRecord = "";   private HeadersHolder headersHolder = new HeadersHolder();   // these are all more or less global loop variables   // to keep from needing to pass them all into various   // methods during parsing   private boolean startedColumn = false;   private boolean startedWithQualifier = false;   private boolean hasMoreData = true;   private char lastLetter = '\0';   private boolean hasReadNextLine = false;   private int columnsCount = 0;   private long currentRecord = 0;   private String[] values = new String[StaticSettings.INITIAL_COLUMN_COUNT];   private boolean initialized = false;   private boolean closed = false;   /**    * Double up the text qualifier to represent an occurance of the text    * qualifier.    */   public static final int ESCAPE_MODE_DOUBLED = 1;   /**    * Use a backslash character before the text qualifier to represent an    * occurance of the text qualifier.    */   public static final int ESCAPE_MODE_BACKSLASH = 2;   /**    * Creates a {@link com.csvreader.CsvReader CsvReader} object using a file    * as the data source.    *     * @param fileName    *            The path to the file to use as the data source.    * @param delimiter    *            The character to use as the column delimiter.    * @param charset    *            The {@link java.nio.charset.Charset Charset} to use while    *            parsing the data.    */   public CsvReader(String fileName, char delimiter, Charset charset)       throws FileNotFoundException {     if (fileName == null) {       throw new IllegalArgumentException(           "Parameter fileName can not be null.");     }     if (charset == null) {       throw new IllegalArgumentException(           "Parameter charset can not be null.");     }     if (!new File(fileName).exists()) {       throw new FileNotFoundException("File " + fileName           + " does not exist.");     }     this.fileName = fileName;     this.userSettings.Delimiter = delimiter;     this.charset = charset;     isQualified = new boolean[values.length];   }   /**    * Creates a {@link com.csvreader.CsvReader CsvReader} object using a file    * as the data source.&nbsp;Uses ISO-8859-1 as the    * {@link java.nio.charset.Charset Charset}.    *     * @param fileName    *            The path to the file to use as the data source.    * @param delimiter    *            The character to use as the column delimiter.    */   public CsvReader(String fileName, char delimiter)       throws FileNotFoundException {     this(fileName, delimiter, Charset.forName("ISO-8859-1"));   }   /**    * Creates a {@link com.csvreader.CsvReader CsvReader} object using a file    * as the data source.&nbsp;Uses a comma as the column delimiter and    * ISO-8859-1 as the {@link java.nio.charset.Charset Charset}.    *     * @param fileName    *            The path to the file to use as the data source.    */   public CsvReader(String fileName) throws FileNotFoundException {     this(fileName, Letters.COMMA);   }   /**    * Constructs a {@link com.csvreader.CsvReader CsvReader} object using a    * {@link java.io.Reader Reader} object as the data source.    *     * @param inputStream    *            The stream to use as the data source.    * @param delimiter    *            The character to use as the column delimiter.    */   public CsvReader(Reader inputStream, char delimiter) {     if (inputStream == null) {       throw new IllegalArgumentException(           "Parameter inputStream can not be null.");     }     this.inputStream = inputStream;     this.userSettings.Delimiter = delimiter;     initialized = true;     isQualified = new boolean[values.length];   }   /**    * Constructs a {@link com.csvreader.CsvReader CsvReader} object using a    * {@link java.io.Reader Reader} object as the data source.&nbsp;Uses a    * comma as the column delimiter.    *     * @param inputStream    *            The stream to use as the data source.    */   public CsvReader(Reader inputStream) {     this(inputStream, Letters.COMMA);   }   /**    * Constructs a {@link com.csvreader.CsvReader CsvReader} object using an    * {@link java.io.InputStream InputStream} object as the data source.    *     * @param inputStream    *            The stream to use as the data source.    * @param delimiter    *            The character to use as the column delimiter.    * @param charset    *            The {@link java.nio.charset.Charset Charset} to use while    *            parsing the data.    */   public CsvReader(InputStream inputStream, char delimiter, Charset charset) {     this(new InputStreamReader(inputStream, charset), delimiter);   }   /**    * Constructs a {@link com.csvreader.CsvReader CsvReader} object using an    * {@link java.io.InputStream InputStream} object as the data    * source.&nbsp;Uses a comma as the column delimiter.    *     * @param inputStream    *            The stream to use as the data source.    * @param charset    *            The {@link java.nio.charset.Charset Charset} to use while    *            parsing the data.    */   public CsvReader(InputStream inputStream, Charset charset) {     this(new InputStreamReader(inputStream, charset));   }   public boolean getCaptureRawRecord() {     return userSettings.CaptureRawRecord;   }   public void setCaptureRawRecord(boolean captureRawRecord) {     userSettings.CaptureRawRecord = captureRawRecord;   }   public String getRawRecord() {     return rawRecord;   }   /**    * Gets whether leading and trailing whitespace characters are being trimmed    * from non-textqualified column data. Default is true.    *     * @return Whether leading and trailing whitespace characters are being    *         trimmed from non-textqualified column data.    */   public boolean getTrimWhitespace() {     return userSettings.TrimWhitespace;   }   /**    * Sets whether leading and trailing whitespace characters should be trimmed    * from non-textqualified column data or not. Default is true.    *     * @param trimWhitespace    *            Whether leading and trailing whitespace characters should be    *            trimmed from non-textqualified column data or not.    */   public void setTrimWhitespace(boolean trimWhitespace) {     userSettings.TrimWhitespace = trimWhitespace;   }   /**    * Gets the character being used as the column delimiter. Default is comma,    * ','.    *     * @return The character being used as the column delimiter.    */   public char getDelimiter() {     return userSettings.Delimiter;   }   /**    * Sets the character to use as the column delimiter. Default is comma, ','.    *     * @param delimiter    *            The character to use as the column delimiter.    */   public void setDelimiter(char delimiter) {     userSettings.Delimiter = delimiter;   }   public char getRecordDelimiter() {     return userSettings.RecordDelimiter;   }   /**    * Sets the character to use as the record delimiter.    *     * @param recordDelimiter    *            The character to use as the record delimiter. Default is    *            combination of standard end of line characters for Windows,    *            Unix, or Mac.    */   public void setRecordDelimiter(char recordDelimiter) {     useCustomRecordDelimiter = true;     userSettings.RecordDelimiter = recordDelimiter;   }   /**    * Gets the character to use as a text qualifier in the data.    *     * @return The character to use as a text qualifier in the data.    */   public char getTextQualifier() {     return userSettings.TextQualifier;   }   /**    * Sets the character to use as a text qualifier in the data.    *     * @param textQualifier    *            The character to use as a text qualifier in the data.    */   public void setTextQualifier(char textQualifier) {     userSettings.TextQualifier = textQualifier;   }   /**    * Whether text qualifiers will be used while parsing or not.    *     * @return Whether text qualifiers will be used while parsing or not.    */   public boolean getUseTextQualifier() {     return userSettings.UseTextQualifier;   }   /**    * Sets whether text qualifiers will be used while parsing or not.    *     * @param useTextQualifier    *            Whether to use a text qualifier while parsing or not.    */   public void setUseTextQualifier(boolean useTextQualifier) {     userSettings.UseTextQualifier = useTextQualifier;   }   /**    * Gets the character being used as a comment signal.    *     * @return The character being used as a comment signal.    */   public char getComment() {     return userSettings.Comment;   }   /**    * Sets the character to use as a comment signal.    *     * @param comment    *            The character to use as a comment signal.    */   public void setComment(char comment) {     userSettings.Comment = comment;   }   /**    * Gets whether comments are being looked for while parsing or not.    *     * @return Whether comments are being looked for while parsing or not.    */   public boolean getUseComments() {     return userSettings.UseComments;   }   /**    * Sets whether comments are being looked for while parsing or not.    *     * @param useComments    *            Whether comments are being looked for while parsing or not.    */   public void setUseComments(boolean useComments) {     userSettings.UseComments = useComments;   }   /**    * Gets the current way to escape an occurance of the text qualifier inside    * qualified data.    *     * @return The current way to escape an occurance of the text qualifier    *         inside qualified data.    */   public int getEscapeMode() {     return userSettings.EscapeMode;   }   /**    * Sets the current way to escape an occurance of the text qualifier inside    * qualified data.    *     * @param escapeMode    *            The way to escape an occurance of the text qualifier inside    *            qualified data.    * @exception IllegalArgumentException    *                When an illegal value is specified for escapeMode.    */   public void setEscapeMode(int escapeMode) throws IllegalArgumentException {     if (escapeMode != ESCAPE_MODE_DOUBLED         && escapeMode != ESCAPE_MODE_BACKSLASH) {       throw new IllegalArgumentException(           "Parameter escapeMode must be a valid value.");     }     userSettings.EscapeMode = escapeMode;   }   public boolean getSkipEmptyRecords() {     return userSettings.SkipEmptyRecords;   }   public void setSkipEmptyRecords(boolean skipEmptyRecords) {     userSettings.SkipEmptyRecords = skipEmptyRecords;   }   /**    * Safety caution to prevent the parser from using large amounts of memory    * in the case where parsing settings like file encodings don't end up    * matching the actual format of a file. This switch can be turned off if    * the file format is known and tested. With the switch off, the max column    * lengths and max column count per record supported by the parser will    * greatly increase. Default is true.    *     * @return The current setting of the safety switch.    */   public boolean getSafetySwitch() {     return userSettings.SafetySwitch;   }   /**    * Safety caution to prevent the parser from using large amounts of memory    * in the case where parsing settings like file encodings don't end up    * matching the actual format of a file. This switch can be turned off if    * the file format is known and tested. With the switch off, the max column    * lengths and max column count per record supported by the parser will    * greatly increase. Default is true.    *     * @param safetySwitch    */   public void setSafetySwitch(boolean safetySwitch) {     userSettings.SafetySwitch = safetySwitch;   }   /**    * Gets the count of columns found in this record.    *     * @return The count of columns found in this record.    */   public int getColumnCount() {     return columnsCount;   }   /**    * Gets the index of the current record.    *     * @return The index of the current record.    */   public long getCurrentRecord() {     return currentRecord - 1;   }   /**    * Gets the count of headers read in by a previous call to    * {@link com.csvreader.CsvReader#readHeaders readHeaders()}.    *     * @return The count of headers read in by a previous call to    *         {@link com.csvreader.CsvReader#readHeaders readHeaders()}.    */   public int getHeaderCount() {     return headersHolder.Length;   }   /**    * Returns the header values as a string array.    *     * @return The header values as a String array.    * @exception IOException    *                Thrown if this object has already been closed.    */   public String[] getHeaders() throws IOException {     checkClosed();     if (headersHolder.Headers == null) {       return null;     } else {       // use clone here to prevent the outside code from       // setting values on the array directly, which would       // throw off the index lookup based on header name       String[] clone = new String[headersHolder.Length];       System.arraycopy(headersHolder.Headers, 0, clone, 0,           headersHolder.Length);       return clone;     }   }   public void setHeaders(String[] headers) {     headersHolder.Headers = headers;     headersHolder.IndexByName.clear();     if (headers != null) {       headersHolder.Length = headers.length;     } else {       headersHolder.Length = 0;     }     // use headersHolder.Length here in case headers is null     for (int i = 0; i < headersHolder.Length; i++) {       headersHolder.IndexByName.put(headers[i], Integer.valueOf(i));     }   }   public String[] getValues() throws IOException {     checkClosed();     // need to return a clone, and can't use clone because values.Length     // might be greater than columnsCount     String[] clone = new String[columnsCount];     System.arraycopy(values, 0, clone, 0, columnsCount);     return clone;   }   /**    * Returns the current column value for a given column index.    *     * @param columnIndex    *            The index of the column.    * @return The current column value.    * @exception IOException    *                Thrown if this object has already been closed.    */   public String get(int columnIndex) throws IOException {     checkClosed();     if (columnIndex > -1 && columnIndex < columnsCount) {       return values[columnIndex];     } else {       return "";     }   }   /**    * Returns the current column value for a given column header name.    *     * @param headerName    *            The header name of the column.    * @return The current column value.    * @exception IOException    *                Thrown if this object has already been closed.    */   public String get(String headerName) throws IOException {     checkClosed();     return get(getIndex(headerName));   }   /**    * Creates a {@link com.csvreader.CsvReader CsvReader} object using a string    * of data as the source.&nbsp;Uses ISO-8859-1 as the    * {@link java.nio.charset.Charset Charset}.    *     * @param data    *            The String of data to use as the source.    * @return A {@link com.csvreader.CsvReader CsvReader} object using the    *         String of data as the source.    */   public static CsvReader parse(String data) {     if (data == null) {       throw new IllegalArgumentException(           "Parameter data can not be null.");     }     return new CsvReader(new StringReader(data));   }   /**    * Reads another record.    *     * @return Whether another record was successfully read or not.    * @exception IOException    *                Thrown if an error occurs while reading data from the    *                source stream.    */   public boolean readRecord() throws IOException {     checkClosed();     columnsCount = 0;     rawBuffer.Position = 0;     dataBuffer.LineStart = dataBuffer.Position;     hasReadNextLine = false;     // check to see if we've already found the end of data     if (hasMoreData) {       // loop over the data stream until the end of data is found       // or the end of the record is found       do {         if (dataBuffer.Position == dataBuffer.Count) {           checkDataLength();         } else {           startedWithQualifier = false;           // grab the current letter as a char           char currentLetter = dataBuffer.Buffer[dataBuffer.Position];           if (userSettings.UseTextQualifier               && currentLetter == userSettings.TextQualifier) {             // this will be a text qualified column, so             // we need to set startedWithQualifier to make it             // enter the seperate branch to handle text             // qualified columns             lastLetter = currentLetter;             // read qualified             startedColumn = true;             dataBuffer.ColumnStart = dataBuffer.Position + 1;             startedWithQualifier = true;             boolean lastLetterWasQualifier = false;             char escapeChar = userSettings.TextQualifier;             if (userSettings.EscapeMode == ESCAPE_MODE_BACKSLASH) {               escapeChar = Letters.BACKSLASH;             }             boolean eatingTrailingJunk = false;             boolean lastLetterWasEscape = false;             boolean readingComplexEscape = false;             int escape = ComplexEscape.UNICODE;             int escapeLength = 0;             char escapeValue = (char) 0;             dataBuffer.Position++;             do {               if (dataBuffer.Position == dataBuffer.Count) {                 checkDataLength();               } else {                 // grab the current letter as a char                 currentLetter = dataBuffer.Buffer[dataBuffer.Position];                 if (eatingTrailingJunk) {                   dataBuffer.ColumnStart = dataBuffer.Position + 1;                   if (currentLetter == userSettings.Delimiter) {                     endColumn();                   } else if ((!useCustomRecordDelimiter && (currentLetter == Letters.CR || currentLetter == Letters.LF))                       || (useCustomRecordDelimiter && currentLetter == userSettings.RecordDelimiter)) {                     endColumn();                     endRecord();                   }                 } else if (readingComplexEscape) {                   escapeLength++;                   switch (escape) {                   case ComplexEscape.UNICODE:                     escapeValue *= (char) 16;                     escapeValue += hexToDec(currentLetter);                     if (escapeLength == 4) {                       readingComplexEscape = false;                     }                     break;                   case ComplexEscape.OCTAL:                     escapeValue *= (char) 8;                     escapeValue += (char) (currentLetter - '0');                     if (escapeLength == 3) {                       readingComplexEscape = false;                     }                     break;                   case ComplexEscape.DECIMAL:                     escapeValue *= (char) 10;                     escapeValue += (char) (currentLetter - '0');                     if (escapeLength == 3) {                       readingComplexEscape = false;                     }                     break;                   case ComplexEscape.HEX:                     escapeValue *= (char) 16;                     escapeValue += hexToDec(currentLetter);                     if (escapeLength == 2) {                       readingComplexEscape = false;                     }                     break;                   }                   if (!readingComplexEscape) {                     appendLetter(escapeValue);                   } else {                     dataBuffer.ColumnStart = dataBuffer.Position + 1;                   }                 } else if (currentLetter == userSettings.TextQualifier) {                   if (lastLetterWasEscape) {                     lastLetterWasEscape = false;                     lastLetterWasQualifier = false;                   } else {                     updateCurrentValue();                     if (userSettings.EscapeMode == ESCAPE_MODE_DOUBLED) {                       lastLetterWasEscape = true;                     }                     lastLetterWasQualifier = true;                   }                 } else if (userSettings.EscapeMode == ESCAPE_MODE_BACKSLASH                     && lastLetterWasEscape) {                   switch (currentLetter) {                   case 'n':                     appendLetter(Letters.LF);                     break;                   case 'r':                     appendLetter(Letters.CR);                     break;                   case 't':                     appendLetter(Letters.TAB);                     break;                   case 'b':                     appendLetter(Letters.BACKSPACE);                     break;                   case 'f':                     appendLetter(Letters.FORM_FEED);                     break;                   case 'e':                     appendLetter(Letters.ESCAPE);                     break;                   case 'v':                     appendLetter(Letters.VERTICAL_TAB);                     break;                   case 'a':                     appendLetter(Letters.ALERT);                     break;                   case '0':                   case '1':                   case '2':                   case '3':                   case '4':                   case '5':                   case '6':                   case '7':                     escape = ComplexEscape.OCTAL;                     readingComplexEscape = true;                     escapeLength = 1;                     escapeValue = (char) (currentLetter - '0');                     dataBuffer.ColumnStart = dataBuffer.Position + 1;                     break;                   case 'u':                   case 'x':                   case 'o':                   case 'd':                   case 'U':                   case 'X':                   case 'O':                   case 'D':                     switch (currentLetter) {                     case 'u':                     case 'U':                       escape = ComplexEscape.UNICODE;                       break;                     case 'x':                     case 'X':                       escape = ComplexEscape.HEX;                       break;                     case 'o':                     case 'O':                       escape = ComplexEscape.OCTAL;                       break;                     case 'd':                     case 'D':                       escape = ComplexEscape.DECIMAL;                       break;                     }                     readingComplexEscape = true;                     escapeLength = 0;                     escapeValue = (char) 0;                     dataBuffer.ColumnStart = dataBuffer.Position + 1;                     break;                   default:                     break;                   }                   lastLetterWasEscape = false;                   // can only happen for ESCAPE_MODE_BACKSLASH                 } else if (currentLetter == escapeChar) {                   updateCurrentValue();                   lastLetterWasEscape = true;                 } else {                   if (lastLetterWasQualifier) {                     if (currentLetter == userSettings.Delimiter) {                       endColumn();                     } else if ((!useCustomRecordDelimiter && (currentLetter == Letters.CR || currentLetter == Letters.LF))                         || (useCustomRecordDelimiter && currentLetter == userSettings.RecordDelimiter)) {                       endColumn();                       endRecord();                     } else {                       dataBuffer.ColumnStart = dataBuffer.Position + 1;                       eatingTrailingJunk = true;                     }                     // make sure to clear the flag for next                     // run of the loop                     lastLetterWasQualifier = false;                   }                 }                 // keep track of the last letter because we need                 // it for several key decisions                 lastLetter = currentLetter;                 if (startedColumn) {                   dataBuffer.Position++;                   if (userSettings.SafetySwitch                       && dataBuffer.Position                           - dataBuffer.ColumnStart                           + columnBuffer.Position > 100000) {                     close();                     throw new IOException(                         "Maximum column length of 100,000 exceeded in column "                             + NumberFormat                                 .getIntegerInstance()                                 .format(                                     columnsCount)                             + " in record "                             + NumberFormat                                 .getIntegerInstance()                                 .format(                                     currentRecord)                             + ". Set the SafetySwitch property to false"                             + " if you're expecting column lengths greater than 100,000 characters to"                             + " avoid this error.");                   }                 }               } // end else             } while (hasMoreData && startedColumn);           } else if (currentLetter == userSettings.Delimiter) {             // we encountered a column with no data, so             // just send the end column             lastLetter = currentLetter;             endColumn();           } else if (useCustomRecordDelimiter               && currentLetter == userSettings.RecordDelimiter) {             // this will skip blank lines             if (startedColumn || columnsCount > 0                 || !userSettings.SkipEmptyRecords) {               endColumn();               endRecord();             } else {               dataBuffer.LineStart = dataBuffer.Position + 1;             }             lastLetter = currentLetter;           } else if (!useCustomRecordDelimiter               && (currentLetter == Letters.CR || currentLetter == Letters.LF)) {             // this will skip blank lines             if (startedColumn                 || columnsCount > 0                 || (!userSettings.SkipEmptyRecords && (currentLetter == Letters.CR || lastLetter != Letters.CR))) {               endColumn();               endRecord();             } else {               dataBuffer.LineStart = dataBuffer.Position + 1;             }             lastLetter = currentLetter;           } else if (userSettings.UseComments && columnsCount == 0               && currentLetter == userSettings.Comment) {             // encountered a comment character at the beginning of             // the line so just ignore the rest of the line             lastLetter = currentLetter;             skipLine();           } else if (userSettings.TrimWhitespace               && (currentLetter == Letters.SPACE || currentLetter == Letters.TAB)) {             // do nothing, this will trim leading whitespace             // for both text qualified columns and non             startedColumn = true;             dataBuffer.ColumnStart = dataBuffer.Position + 1;           } else {             // since the letter wasn't a special letter, this             // will be the first letter of our current column             startedColumn = true;             dataBuffer.ColumnStart = dataBuffer.Position;             boolean lastLetterWasBackslash = false;             boolean readingComplexEscape = false;             int escape = ComplexEscape.UNICODE;             int escapeLength = 0;             char escapeValue = (char) 0;             boolean firstLoop = true;             do {               if (!firstLoop                   && dataBuffer.Position == dataBuffer.Count) {                 checkDataLength();               } else {                 if (!firstLoop) {                   // grab the current letter as a char                   currentLetter = dataBuffer.Buffer[dataBuffer.Position];                 }                 if (!userSettings.UseTextQualifier                     && userSettings.EscapeMode == ESCAPE_MODE_BACKSLASH                     && currentLetter == Letters.BACKSLASH) {                   if (lastLetterWasBackslash) {                     lastLetterWasBackslash = false;                   } else {                     updateCurrentValue();                     lastLetterWasBackslash = true;                   }                 } else if (readingComplexEscape) {                   escapeLength++;                   switch (escape) {                   case ComplexEscape.UNICODE:                     escapeValue *= (char) 16;                     escapeValue += hexToDec(currentLetter);                     if (escapeLength == 4) {                       readingComplexEscape = false;                     }                     break;                   case ComplexEscape.OCTAL:                     escapeValue *= (char) 8;                     escapeValue += (char) (currentLetter - '0');                     if (escapeLength == 3) {                       readingComplexEscape = false;                     }                     break;                   case ComplexEscape.DECIMAL:                     escapeValue *= (char) 10;                     escapeValue += (char) (currentLetter - '0');                     if (escapeLength == 3) {                       readingComplexEscape = false;                     }                     break;                   case ComplexEscape.HEX:                     escapeValue *= (char) 16;                     escapeValue += hexToDec(currentLetter);                     if (escapeLength == 2) {                       readingComplexEscape = false;                     }                     break;                   }                   if (!readingComplexEscape) {                     appendLetter(escapeValue);                   } else {                     dataBuffer.ColumnStart = dataBuffer.Position + 1;                   }                 } else if (userSettings.EscapeMode == ESCAPE_MODE_BACKSLASH                     && lastLetterWasBackslash) {                   switch (currentLetter) {                   case 'n':                     appendLetter(Letters.LF);                     break;                   case 'r':                     appendLetter(Letters.CR);                     break;                   case 't':                     appendLetter(Letters.TAB);                     break;                   case 'b':                     appendLetter(Letters.BACKSPACE);                     break;                   case 'f':                     appendLetter(Letters.FORM_FEED);                     break;                   case 'e':                     appendLetter(Letters.ESCAPE);                     break;                   case 'v':                     appendLetter(Letters.VERTICAL_TAB);                     break;                   case 'a':                     appendLetter(Letters.ALERT);                     break;                   case '0':                   case '1':                   case '2':                   case '3':                   case '4':                   case '5':                   case '6':                   case '7':                     escape = ComplexEscape.OCTAL;                     readingComplexEscape = true;                     escapeLength = 1;                     escapeValue = (char) (currentLetter - '0');                     dataBuffer.ColumnStart = dataBuffer.Position + 1;                     break;                   case 'u':                   case 'x':                   case 'o':                   case 'd':                   case 'U':                   case 'X':                   case 'O':                   case 'D':                     switch (currentLetter) {                     case 'u':                     case 'U':                       escape = ComplexEscape.UNICODE;                       break;                     case 'x':                     case 'X':                       escape = ComplexEscape.HEX;                       break;                     case 'o':                     case 'O':                       escape = ComplexEscape.OCTAL;                       break;                     case 'd':                     case 'D':                       escape = ComplexEscape.DECIMAL;                       break;                     }                     readingComplexEscape = true;                     escapeLength = 0;                     escapeValue = (char) 0;                     dataBuffer.ColumnStart = dataBuffer.Position + 1;                     break;                   default:                     break;                   }                   lastLetterWasBackslash = false;                 } else {                   if (currentLetter == userSettings.Delimiter) {                     endColumn();                   } else if ((!useCustomRecordDelimiter && (currentLetter == Letters.CR || currentLetter == Letters.LF))                       || (useCustomRecordDelimiter && currentLetter == userSettings.RecordDelimiter)) {                     endColumn();                     endRecord();                   }                 }                 // keep track of the last letter because we need                 // it for several key decisions                 lastLetter = currentLetter;                 firstLoop = false;                 if (startedColumn) {                   dataBuffer.Position++;                   if (userSettings.SafetySwitch                       && dataBuffer.Position                           - dataBuffer.ColumnStart                           + columnBuffer.Position > 100000) {                     close();                     throw new IOException(                         "Maximum column length of 100,000 exceeded in column "                             + NumberFormat                                 .getIntegerInstance()                                 .format(                                     columnsCount)                             + " in record "                             + NumberFormat                                 .getIntegerInstance()                                 .format(                                     currentRecord)                             + ". Set the SafetySwitch property to false"                             + " if you're expecting column lengths greater than 100,000 characters to"                             + " avoid this error.");                   }                 }               } // end else             } while (hasMoreData && startedColumn);           }           if (hasMoreData) {             dataBuffer.Position++;           }         } // end else       } while (hasMoreData && !hasReadNextLine);       // check to see if we hit the end of the file       // without processing the current record       if (startedColumn || lastLetter == userSettings.Delimiter) {         endColumn();         endRecord();       }     }     if (userSettings.CaptureRawRecord) {       if (hasMoreData) {         if (rawBuffer.Position == 0) {           rawRecord = new String(dataBuffer.Buffer,               dataBuffer.LineStart, dataBuffer.Position                   - dataBuffer.LineStart - 1);         } else {           rawRecord = new String(rawBuffer.Buffer, 0,               rawBuffer.Position)               + new String(dataBuffer.Buffer,                   dataBuffer.LineStart, dataBuffer.Position                       - dataBuffer.LineStart - 1);         }       } else {         // for hasMoreData to ever be false, all data would have had to         // have been         // copied to the raw buffer         rawRecord = new String(rawBuffer.Buffer, 0, rawBuffer.Position);       }     } else {       rawRecord = "";     }     return hasReadNextLine;   }   /**    * @exception IOException    *                Thrown if an error occurs while reading data from the    *                source stream.    */   private void checkDataLength() throws IOException {     if (!initialized) {       if (fileName != null) {         inputStream = new BufferedReader(new InputStreamReader(             new FileInputStream(fileName), charset),             StaticSettings.MAX_FILE_BUFFER_SIZE);       }       charset = null;       initialized = true;     }     updateCurrentValue();     if (userSettings.CaptureRawRecord && dataBuffer.Count > 0) {       if (rawBuffer.Buffer.length - rawBuffer.Position < dataBuffer.Count           - dataBuffer.LineStart) {         int newLength = rawBuffer.Buffer.length             + Math.max(dataBuffer.Count - dataBuffer.LineStart,                 rawBuffer.Buffer.length);         char[] holder = new char[newLength];         System.arraycopy(rawBuffer.Buffer, 0, holder, 0,             rawBuffer.Position);         rawBuffer.Buffer = holder;       }       System.arraycopy(dataBuffer.Buffer, dataBuffer.LineStart,           rawBuffer.Buffer, rawBuffer.Position, dataBuffer.Count               - dataBuffer.LineStart);       rawBuffer.Position += dataBuffer.Count - dataBuffer.LineStart;     }     try {       dataBuffer.Count = inputStream.read(dataBuffer.Buffer, 0,           dataBuffer.Buffer.length);     } catch (IOException ex) {       close();       throw ex;     }     // if no more data could be found, set flag stating that     // the end of the data was found     if (dataBuffer.Count == -1) {       hasMoreData = false;     }     dataBuffer.Position = 0;     dataBuffer.LineStart = 0;     dataBuffer.ColumnStart = 0;   }   /**    * Read the first record of data as column headers.    *     * @return Whether the header record was successfully read or not.    * @exception IOException    *                Thrown if an error occurs while reading data from the    *                source stream.    */   public boolean readHeaders() throws IOException {     boolean result = readRecord();     // copy the header data from the column array     // to the header string array     headersHolder.Length = columnsCount;     headersHolder.Headers = new String[columnsCount];     for (int i = 0; i < headersHolder.Length; i++) {       String columnValue = get(i);       headersHolder.Headers[i] = columnValue;       // if there are duplicate header names, we will save the last one       headersHolder.IndexByName.put(columnValue, Integer.valueOf(i));     }     if (result) {       currentRecord--;     }     columnsCount = 0;     return result;   }   /**    * Returns the column header value for a given column index.    *     * @param columnIndex    *            The index of the header column being requested.    * @return The value of the column header at the given column index.    * @exception IOException    *                Thrown if this object has already been closed.    */   public String getHeader(int columnIndex) throws IOException {     checkClosed();     // check to see if we have read the header record yet     // check to see if the column index is within the bounds     // of our header array     if (columnIndex > -1 && columnIndex < headersHolder.Length) {       // return the processed header data for this column       return headersHolder.Headers[columnIndex];     } else {       return "";     }   }   public boolean isQualified(int columnIndex) throws IOException {     checkClosed();     if (columnIndex < columnsCount && columnIndex > -1) {       return isQualified[columnIndex];     } else {       return false;     }   }   /**    * @exception IOException    *                Thrown if a very rare extreme exception occurs during    *                parsing, normally resulting from improper data format.    */   private void endColumn() throws IOException {     String currentValue = "";     // must be called before setting startedColumn = false     if (startedColumn) {       if (columnBuffer.Position == 0) {         if (dataBuffer.ColumnStart < dataBuffer.Position) {           int lastLetter = dataBuffer.Position - 1;           if (userSettings.TrimWhitespace && !startedWithQualifier) {             while (lastLetter >= dataBuffer.ColumnStart                 && (dataBuffer.Buffer[lastLetter] == Letters.SPACE || dataBuffer.Buffer[lastLetter] == Letters.TAB)) {               lastLetter--;             }           }           currentValue = new String(dataBuffer.Buffer,               dataBuffer.ColumnStart, lastLetter                   - dataBuffer.ColumnStart + 1);         }       } else {         updateCurrentValue();         int lastLetter = columnBuffer.Position - 1;         if (userSettings.TrimWhitespace && !startedWithQualifier) {           while (lastLetter >= 0               && (columnBuffer.Buffer[lastLetter] == Letters.SPACE || columnBuffer.Buffer[lastLetter] == Letters.SPACE)) {             lastLetter--;           }         }         currentValue = new String(columnBuffer.Buffer, 0,             lastLetter + 1);       }     }     columnBuffer.Position = 0;     startedColumn = false;     if (columnsCount >= 100000 && userSettings.SafetySwitch) {       close();       throw new IOException(           "Maximum column count of 100,000 exceeded in record "               + NumberFormat.getIntegerInstance().format(                   currentRecord)               + ". Set the SafetySwitch property to false"               + " if you're expecting more than 100,000 columns per record to"               + " avoid this error.");     }     // check to see if our current holder array for     // column chunks is still big enough to handle another     // column chunk     if (columnsCount == values.length) {       // holder array needs to grow to be able to hold another column       int newLength = values.length * 2;       String[] holder = new String[newLength];       System.arraycopy(values, 0, holder, 0, values.length);       values = holder;       boolean[] qualifiedHolder = new boolean[newLength];       System.arraycopy(isQualified, 0, qualifiedHolder, 0,           isQualified.length);       isQualified = qualifiedHolder;     }     values[columnsCount] = currentValue;     isQualified[columnsCount] = startedWithQualifier;     currentValue = "";     columnsCount++;   }   private void appendLetter(char letter) {     if (columnBuffer.Position == columnBuffer.Buffer.length) {       int newLength = columnBuffer.Buffer.length * 2;       char[] holder = new char[newLength];       System.arraycopy(columnBuffer.Buffer, 0, holder, 0,           columnBuffer.Position);       columnBuffer.Buffer = holder;     }     columnBuffer.Buffer[columnBuffer.Position++] = letter;     dataBuffer.ColumnStart = dataBuffer.Position + 1;   }   private void updateCurrentValue() {     if (startedColumn && dataBuffer.ColumnStart < dataBuffer.Position) {       if (columnBuffer.Buffer.length - columnBuffer.Position < dataBuffer.Position           - dataBuffer.ColumnStart) {         int newLength = columnBuffer.Buffer.length             + Math.max(                 dataBuffer.Position - dataBuffer.ColumnStart,                 columnBuffer.Buffer.length);         char[] holder = new char[newLength];         System.arraycopy(columnBuffer.Buffer, 0, holder, 0,             columnBuffer.Position);         columnBuffer.Buffer = holder;       }       System.arraycopy(dataBuffer.Buffer, dataBuffer.ColumnStart,           columnBuffer.Buffer, columnBuffer.Position,           dataBuffer.Position - dataBuffer.ColumnStart);       columnBuffer.Position += dataBuffer.Position           - dataBuffer.ColumnStart;     }     dataBuffer.ColumnStart = dataBuffer.Position + 1;   }   /**    * @exception IOException    *                Thrown if an error occurs while reading data from the    *                source stream.    */   private void endRecord() throws IOException {     // this flag is used as a loop exit condition     // during parsing     hasReadNextLine = true;     currentRecord++;   }   /**    * Gets the corresponding column index for a given column header name.    *     * @param headerName    *            The header name of the column.    * @return The column index for the given column header name.&nbsp;Returns    *         -1 if not found.    * @exception IOException    *                Thrown if this object has already been closed.    */   public int getIndex(String headerName) throws IOException {     checkClosed();     Integer indexValue = headersHolder.IndexByName.get(headerName);     if (indexValue != null) {       return indexValue.intValue();     } else {       return -1;     }   }   /**    * Skips the next record of data by parsing each column.&nbsp;Does not    * increment    * {@link com.csvreader.CsvReader#getCurrentRecord getCurrentRecord()}.    *     * @return Whether another record was successfully skipped or not.    * @exception IOException    *                Thrown if an error occurs while reading data from the    *                source stream.    */   public boolean skipRecord() throws IOException {     checkClosed();     boolean recordRead = false;     if (hasMoreData) {       recordRead = readRecord();       if (recordRead) {         currentRecord--;       }     }     return recordRead;   }   /**    * Skips the next line of data using the standard end of line characters and    * does not do any column delimited parsing.    *     * @return Whether a line was successfully skipped or not.    * @exception IOException    *                Thrown if an error occurs while reading data from the    *                source stream.    */   public boolean skipLine() throws IOException {     checkClosed();     // clear public column values for current line     columnsCount = 0;     boolean skippedLine = false;     if (hasMoreData) {       boolean foundEol = false;       do {         if (dataBuffer.Position == dataBuffer.Count) {           checkDataLength();         } else {           skippedLine = true;           // grab the current letter as a char           char currentLetter = dataBuffer.Buffer[dataBuffer.Position];           if (currentLetter == Letters.CR               || currentLetter == Letters.LF) {             foundEol = true;           }           // keep track of the last letter because we need           // it for several key decisions           lastLetter = currentLetter;           if (!foundEol) {             dataBuffer.Position++;           }         } // end else       } while (hasMoreData && !foundEol);       columnBuffer.Position = 0;       dataBuffer.LineStart = dataBuffer.Position + 1;     }     rawBuffer.Position = 0;     rawRecord = "";     return skippedLine;   }   /**    * Closes and releases all related resources.    */   public void close() {     if (!closed) {       close(true);       closed = true;     }   }   /**    *     */   private void close(boolean closing) {     if (!closed) {       if (closing) {         charset = null;         headersHolder.Headers = null;         headersHolder.IndexByName = null;         dataBuffer.Buffer = null;         columnBuffer.Buffer = null;         rawBuffer.Buffer = null;       }       try {         if (initialized) {           inputStream.close();         }       } catch (Exception e) {         // just eat the exception       }       inputStream = null;       closed = true;     }   }   /**    * @exception IOException    *                Thrown if this object has already been closed.    */   private void checkClosed() throws IOException {     if (closed) {       throw new IOException(           "This instance of the CsvReader class has already been closed.");     }   }   /**    *     */   protected void finalize() {     close(false);   }   private class ComplexEscape {     private static final int UNICODE = 1;     private static final int OCTAL = 2;     private static final int DECIMAL = 3;     private static final int HEX = 4;   }   private static char hexToDec(char hex) {     char result;     if (hex >= 'a') {       result = (char) (hex - 'a' + 10);     } else if (hex >= 'A') {       result = (char) (hex - 'A' + 10);     } else {       result = (char) (hex - '0');     }     return result;   }   private class DataBuffer {     public char[] Buffer;     public int Position;     // / <summary>     // / How much usable data has been read into the stream,     // / which will not always be as long as Buffer.Length.     // / </summary>     public int Count;     // / <summary>     // / The position of the cursor in the buffer when the     // / current column was started or the last time data     // / was moved out to the column buffer.     // / </summary>     public int ColumnStart;     public int LineStart;     public DataBuffer() {       Buffer = new char[StaticSettings.MAX_BUFFER_SIZE];       Position = 0;       Count = 0;       ColumnStart = 0;       LineStart = 0;     }   }   private class ColumnBuffer {     public char[] Buffer;     public int Position;     public ColumnBuffer() {       Buffer = new char[StaticSettings.INITIAL_COLUMN_BUFFER_SIZE];       Position = 0;     }   }   private class RawRecordBuffer {     public char[] Buffer;     public int Position;     public RawRecordBuffer() {       Buffer = new char[StaticSettings.INITIAL_COLUMN_BUFFER_SIZE           * StaticSettings.INITIAL_COLUMN_COUNT];       Position = 0;     }   }   private class Letters {     public static final char LF = '\n';     public static final char CR = '\r';     public static final char QUOTE = '"';     public static final char COMMA = ',';     public static final char SPACE = ' ';     public static final char TAB = '\t';     public static final char POUND = '#';     public static final char BACKSLASH = '\\';     public static final char NULL = '\0';     public static final char BACKSPACE = '\b';     public static final char FORM_FEED = '\f';     public static final char ESCAPE = '\u001B'; // ASCII/ANSI escape     public static final char VERTICAL_TAB = '\u000B';     public static final char ALERT = '\u0007';   }   private class UserSettings {     // having these as publicly accessible members will prevent     // the overhead of the method call that exists on properties     public boolean CaseSensitive;     public char TextQualifier;     public boolean TrimWhitespace;     public boolean UseTextQualifier;     public char Delimiter;     public char RecordDelimiter;     public char Comment;     public boolean UseComments;     public int EscapeMode;     public boolean SafetySwitch;     public boolean SkipEmptyRecords;     public boolean CaptureRawRecord;     public UserSettings() {       CaseSensitive = true;       TextQualifier = Letters.QUOTE;       TrimWhitespace = true;       UseTextQualifier = true;       Delimiter = Letters.COMMA;       RecordDelimiter = Letters.NULL;       Comment = Letters.POUND;       UseComments = false;       EscapeMode = CsvReader.ESCAPE_MODE_DOUBLED;       SafetySwitch = true;       SkipEmptyRecords = true;       CaptureRawRecord = true;     }   }   private class HeadersHolder {     public String[] Headers;     public int Length;     public HashMap<String, Integer> IndexByName;     public HeadersHolder() {       Headers = null;       Length = 0;       IndexByName = new HashMap<String, Integer>();     }   }   private class StaticSettings {     // these are static instead of final so they can be changed in unit test     // isn't visible outside this class and is only accessed once during     // CsvReader construction     public static final int MAX_BUFFER_SIZE = 1024;     public static final int MAX_FILE_BUFFER_SIZE = 4 * 1024;     public static final int INITIAL_COLUMN_COUNT = 10;     public static final int INITIAL_COLUMN_BUFFER_SIZE = 50;   } }