package src;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import com.csvreader.CsvReader;
import com.csvreader.CsvWriter;
public class Test {
public static void main(String[] args) throws IOException {
BufferedWriter bf = new BufferedWriter(new FileWriter("test.csv"),10240);
CsvWriter out = new CsvWriter(bf, ',');
out.writeRecord(new String[] { " 222222 ", "sssss", "1111/"1111" }, true);
out.writeRecord(new String[] { " 222222 ", "sssss", "2222/"22222", "TTTTTTTTT" }, true);
bf.close();


BufferedReader br = new BufferedReader(new FileReader("test.csv"),10240);
CsvReader in = new CsvReader(br,',');
while (in.readRecord()) {
int count = in.getColumnCount();
for (int i = 0; i < count; i++) {
System.out.print(in.get(i) + ",");
}
System.out.println();
}
}
}





/*
* 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
*/
package zyl.csvreader;

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. 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. 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. 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. 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], new Integer(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. 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, new Integer(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. Returns
* -1 if not found.
* @exception IOException
* Thrown if this object has already been closed.
*/
public int getIndex(String headerName) throws IOException {
checkClosed();

Object indexValue = headersHolder.IndexByName.get(headerName);

if (indexValue != null) {
return ((Integer) indexValue).intValue();
} else {
return -1;
}
}

/**
* Skips the next record of data by parsing each column. 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 IndexByName;

public HeadersHolder() {
Headers = null;
Length = 0;
IndexByName = new HashMap();
}
}

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;
}
}