Development Class Java

/*
 *  Copyright (C) 2010 takaji
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program 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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see .
 */
//package dakside.csv;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
 * CSV file reader
 * 
 * @author Takaji
 */
public final class CSVFileReader {
    private CSVFormat format;
    private final BufferedReader reader;
    private int col = 1;
    private int row = 1;
    private StringBuilder element = new StringBuilder(); // store current
    // element
    private CSVLine currentLine = null; // current CSV line
    private boolean isOpened = false; // a string is opened with text delimiter
    // 
    public CSVFileReader(Reader reader) {
        //validate parameter
        checkNotNull(reader, "Reader cannot be null");
        this.reader = new BufferedReader(reader);
        this.format = new CSVFormat();
    }
    public CSVFileReader(Reader reader, CSVFormat format) {
        //validate parameter
        checkNotNull(reader, "Reader cannot be null");
        this.reader = new BufferedReader(reader);
        this.format = format;
    }
    public CSVFileReader(BufferedReader reader) throws FileNotFoundException {
        //validate parameter
        checkNotNull(reader, "Reader cannot be null");
        this.reader = reader;
        this.format = new CSVFormat();
    }
    public CSVFileReader(BufferedReader reader, CSVFormat format) {
        //validate parameter
        checkNotNull(reader, "Reader cannot be null");
        this.reader = reader;
        this.format = format;
    }
    public CSVFileReader(String filename) throws CSVException {
        //validate parameter
        checkNotNull(filename, "File name cannot be empty");
        try {
            this.reader = new BufferedReader(new FileReader(filename));
        } catch (FileNotFoundException ex) {
            Logger.getLogger(CSVFileReader.class.getName()).log(Level.SEVERE, null, ex);
            throw new CSVException("Cannot create reading buffer", ex);
        }
        this.format = new CSVFormat();
    }
    public CSVFileReader(File f) {
        //validate parameter
        checkNotNull(f, "File object cannot be null");
        try {
            this.reader = new BufferedReader(new FileReader(f));
        } catch (FileNotFoundException ex) {
            Logger.getLogger(CSVFileReader.class.getName()).log(Level.SEVERE, null, ex);
            throw new CSVException("Cannot create reading buffer", ex);
        }
        this.format = new CSVFormat();
    }
    public CSVFileReader(InputStream input) {
        //validate parameter
        checkNotNull(input, "InputStream cannot be null");
        reader = new BufferedReader(new InputStreamReader(input));
        this.format = new CSVFormat();
    }
    public CSVFileReader(String filename, CSVFormat format) {
        //validate parameter
        checkNotNull(filename, "File name cannot be empty");
        try {
            this.reader = new BufferedReader(new FileReader(filename));
        } catch (FileNotFoundException ex) {
            Logger.getLogger(CSVFileReader.class.getName()).log(Level.SEVERE, null, ex);
            throw new CSVException("Cannot create reading buffer", ex);
        }
        this.format = format;
    }
    public CSVFileReader(File f, CSVFormat format) {
        //validate parameter
        checkNotNull(f, "File object cannot be null");
        try {
            this.reader = new BufferedReader(new FileReader(f));
        } catch (FileNotFoundException ex) {
            Logger.getLogger(CSVFileReader.class.getName()).log(Level.SEVERE, null, ex);
            throw new CSVException("Cannot create reading buffer", ex);
        }
        this.format = format;
    }
    public CSVFileReader(InputStream input, CSVFormat format) {
        //validate parameter
        checkNotNull(input, "InputStream cannot be null");
        reader = new BufferedReader(new InputStreamReader(input));
        this.format = format;
    }
    // 
    /**
     * read next char
     */
    private int read() throws IOException {
        int next = -1;
        next = reader.read();
        if (next != -1) {
            col++;
        }
        return next;
    }
    /**
     * Do carriage return
     */
    private void carriageReturn() {
        synchronized (this) {
            row++;
            col = 1;
        }
    }
    /**
     * Finished read a cell, flush to current line
     */
    private void flushElement() {
        currentLine.add(element.toString());
        element.setLength(0); // reset current element
        isOpened = false;
    }
    /**
     * Parse a CSV string to data
     * 
     * @param rawData
     * @return a simple CSV object with data. null if the raw data cannot be
     *         parse
     * @throw CSVException
     */
    public CSVLine readLine() throws CSVException {
        if (reader == null) {
            throw new CSVException("No reader found.");
        }
        synchronized (this) {
            element = new StringBuilder(); // store current
            // element
            currentLine = null; // current CSV line
            isOpened = false; // a string is opened with text delimiter
            try {
                int currentInt = -1;
                // parse data
                while ((currentInt = read()) != -1) {
                    if (currentLine == null) {
                        currentLine = new CSVLine();
                    }
                    // reach field delimiter
                    if (currentInt == format.getTextDelimiter()) {
                        if (isOpened) {
                            // check next char is escape?
                            if ((currentInt = read()) != -1) {
                                if (currentInt == format.getTextDelimiter()) {
                                    // is special char
                                    element.append(format.getTextDelimiter());
                                    continue;
                                } else {
                                    // is close string then flush
                                    flushElement();
                                    // next char must be field delimiter
                                    //or a line terminator
                                    if (currentInt == format.getFieldDelimiter()) {
                                        continue;
                                    } else if (currentInt == format.getLineTerminator()) {
                                        carriageReturn();
                                        return currentLine;
                                    } else {
                                        raiseExpected(format.getFieldDelimiter(),
                                                (char) currentInt);
                                    }
                                    continue;
                                }
                            } else {
                                flushElement();
                                continue;
                            }
                        } else {
                            // is first open of the string
                            isOpened = true;
                            continue;
                        }
                    }
                    if (!isOpened) {
                        // ignore character
                        if (format.isIgnored((char) currentInt)) {
                            continue; // just ignore
                        } else if (currentInt == format.getLineTerminator()) {
                            flushElement();
                            if (currentLine != null) {
                                carriageReturn();
                            }
                            return currentLine;
                        }
                    }
                    // meet field delimiter
                    if (currentInt == format.getFieldDelimiter()) {
                        if (isOpened) {
                            element.append((char) currentInt);
                            continue;
                        } else {
                            flushElement();
                            continue;
                        }
                    }
                    // otherwise, it's a normal character, just add
                    element.append((char) currentInt);
                }// continue while
                // flush last element if needed
                if (element != null && currentLine != null) {
                    flushElement();
                }
            } catch (Exception ex) {
                ex.printStackTrace();
                raiseException(ex);
            }
            // if currentLine exists, return carriage
            if (currentLine != null) {
                carriageReturn();
            }
        }// end of synchronize
        return currentLine;
    }
    // 
    /**
     * Expected char [expected] but found another char [actual] so we
     * throw an exception here.
     * @param expected
     * @param actual
     */
    private void raiseExpected(char expected, char actual) {
        raiseException(new Exception(String.format(
                "Invalid data. Expected [%s] but [%s] was found.", String.valueOf(expected), String.valueOf(actual))));
    }
    private void raiseException(Exception ex) {
        throw new CSVException("Error happened while processing at row: " + row
                + " - col: " + (col - 1) + ")", ex);
    }
    /**
     * Check if argument null then throw a new CSV Exception
     * @param argument
     */
    public static void checkNotNull(Object argument, String message) throws CSVException {
        if (argument == null) {
            throw new CSVException(message);
        }
    }
    /**
     * Close current reader and underline stream
     */
    public void close() {
        try {
            synchronized (reader) {
                reader.close();
            }
        } catch (Exception ex) {
            Logger.getLogger(CSVFileReader.class.getName()).log(Level.SEVERE,
                    null, ex);
        }
    }
    // 
    // 
    /**
     * Parse a CSV stream to data
     * 
     * @param stream
     *            InputStream
     * @return a simple CSV object with data. null if the raw data cannot be
     *         parse
     */
    public static CSVFile parse(InputStream stream) throws CSVException {
        try {
            CSVFileReader reader = new CSVFileReader(stream);
            CSVFile file = new CSVFile();
            CSVLine line = null;
            do {
                line = reader.readLine();
                file.append(line);
            } while (line != null);
            return file;
        } catch (Exception ex) {
            throw new CSVException(ex);
        }
    }
    /**
     * Parse file content to CSV
     * 
     * @param path
     * @return
     */
    public static CSVFile parseFile(String path) {
        if (path == null || !(new File(path)).exists()) {
            throw new CSVException("Invalid path");
        }
        try {
            return parse(new FileInputStream(path));
        } catch (Exception ex) {
            Logger.getLogger(CSVFile.class.getName()).log(Level.SEVERE, null,
                    ex);
            throw new CSVException(ex);
        }
    }
    // 
}
class CSVFile {
    private ArrayList lines; //lines inside a CSV file
    /**
     * Default constructor
     */
    public CSVFile() {
        this.lines = new ArrayList();
    }
    public CSVLine[] getLines() {
        return this.lines.toArray(new CSVLine[0]);
    }
    /**
     * Get line at a specified index
     * @param idx
     * @return
     * @throws IndexOutOfBoundsException
     */
    public CSVLine getLine(int idx) throws IndexOutOfBoundsException{
        return this.lines.get(idx);
    }
    /**
     * Add a new line to current csv
     * @return new line object
     */
    public CSVLine newLine() {
        CSVLine line = new CSVLine();
        this.lines.add(line);
        return line;
    }
    /**
     * get number of line
     * @return number of line
     */
    public int size() {
        return this.lines.size();
    }
    /**
     * discard a line at the specified index
     * @param idx
     */
    public void discard(int idx) {
        if (idx >= 0 && idx < size()) {
            this.lines.remove(idx);
        }
    }
    /**
     * discard empty record
     */
    public void discardEmpty() {
        for (int i = this.lines.size() - 1; i > -1; i--) {
            if (this.lines.get(i) == null || this.lines.get(i).isEmpty()) {
                discard(i);
            }
        }
    }
    /**
     * append a CSV line to file (line is ignored if null)
     * @param line
     */
    void append(CSVLine line) {
        if (line == null) {
            return;
        }
        this.lines.add(line);
    }
}
class CSVException extends RuntimeException {
    private static final long serialVersionUID = 1L;
    public CSVException() {
    }
    public CSVException(Throwable throwable) {
        super(throwable);
    }
    public CSVException(String message) {
        super(message);
    }
    public CSVException(String message, Throwable throwable) {
        super(message, throwable);
    }
}
class CSVLine {
    private ArrayList elements;
    /**
     * Default constructor
     */
    public CSVLine() {
        this.elements = new ArrayList();
    }
    /**
     * Get all elements (cells) inside a line
     * @return empty String array if there's no element found inside
     */
    public Object[] getElements() {
        return this.elements.toArray();
    }
    /**
     * Get element at index
     * @param idx
     * @return
     * @throws IndexOutOfBoundsException
     */
    public Object getElementAt(int idx) throws IndexOutOfBoundsException {
        return elements.get(idx);
    }
    /**
     * Set element at
     * @param idx
     * @param value
     * @throws IndexOutOfBoundsException
     */
    public void setElementAt(int idx, Object value) throws IndexOutOfBoundsException {
        elements.set(idx, value);
    }
    /**
     * count elements inside this line
     * @return
     */
    public int size() {
        return elements.size();
    }
    /**
     * Add a new element
     */
    public CSVLine add(Object obj) {
        this.elements.add(obj);
        return this;
    }
    /**
     * if CSV line is empty
     * @return
     */
    public boolean isEmpty() {
        return this.elements.isEmpty();
    }
    /**
     * Remove all elements
     */
    public void clear() {
        this.elements.clear();
    }
    /**
     * Remove element at a specified index
     * @param idx
     * @throws IndexOutOfBoundsException
     */
    public void remove(int idx) throws IndexOutOfBoundsException {
        elements.remove(idx);
    }
    /**
     * Trim down or expand with null cell to a new length
     * @param newLength
     */
    public void setLength(int newLength) {
        if (elements.size() > newLength) {
            //trim down
            while (elements.size() > newLength) {
                elements.remove(elements.size() - 1);
            }
        } else {
            //add more
            while (elements.size() < newLength) {
                add(null);
            }
        }
    }
}
class CSVFormat {
    private String charset;
    private char fieldDelimiter;
    private char textDelimiter;
    private char lineTerminator;
    private char[] ignoreCharacters;
    /**
     * Construct a standard CSV format
     */
    public CSVFormat() {
        this.setCharset("UTF-8");
        this.setFieldDelimiter(',');
        this.setTextDelimiter('"');
        //auto detect line separator
        String s = System.getProperty("line.separator");
        if (s.length() > 0) {
            this.setLineTerminator(s.charAt(0));
            char[] ignoreChars = new char[s.length() - 1];
            for (int i = 1; i < s.length(); i++) {
                ignoreChars[i-1] = s.charAt(i);
            }
            this.setIgnoreCharacters(ignoreChars);
        } else {
            this.setLineTerminator('\n');
            this.setIgnoreCharacters(new char[]{'\r'});
        }
    }
    /**
     * @param lineTerminator
     *            the lineTerminator to set
     */
    public void setLineTerminator(char lineTerminator) {
        this.lineTerminator = lineTerminator;
    }
    /**
     * @return the lineTerminator
     */
    public char getLineTerminator() {
        return lineTerminator;
    }
    /**
     * @param ignoreCharacters
     *            the ignoreCharacters to set
     */
    public void setIgnoreCharacters(char[] ignoreCharacters) {
        this.ignoreCharacters = ignoreCharacters;
    }
    /**
     * @return the ignoreCharacters
     */
    public char[] getIgnoreCharacters() {
        return ignoreCharacters;
    }
    /**
     * @param charset
     *            the charset to set
     */
    public void setCharset(String charset) {
        this.charset = charset;
    }
    /**
     * @return the charset
     */
    public String getCharset() {
        return charset;
    }
    /**
     * @param fieldDelimiter
     *            the fieldDelimiter to set
     */
    public void setFieldDelimiter(char fieldDelimiter) {
        this.fieldDelimiter = fieldDelimiter;
    }
    /**
     * @return the fieldDelimiter
     */
    public char getFieldDelimiter() {
        return fieldDelimiter;
    }
    /**
     * @param textDelimiter
     *            the textDelimiter to set
     */
    public void setTextDelimiter(char textDelimiter) {
        this.textDelimiter = textDelimiter;
    }
    /**
     * @return the textDelimiter
     */
    public char getTextDelimiter() {
        return textDelimiter;
    }
    /**
     * is ignored characters
     * @param c
     * @return
     */
    public boolean isIgnored(char c) {
        return Arrays.binarySearch(ignoreCharacters, c) >= 0;
    }
}