File Input Output Java

/*
 * 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.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Writer;
import java.nio.charset.Charset;
/**
 * A stream based writer for writing delimited text data to a file or a stream.
 */
public class CsvWriter {
  private PrintWriter outputStream = null;
  private String fileName = null;
  private boolean firstColumn = true;
  private boolean useCustomRecordDelimiter = false;
  private Charset charset = null;
  // this holds all the values for switches that the user is allowed to set
  private UserSettings userSettings = new UserSettings();
  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.CsvWriter CsvWriter} object using a file
   * as the data destination.
   * 
   * @param fileName
   *            The path to the file to output the data.
   * @param delimiter
   *            The character to use as the column delimiter.
   * @param charset
   *            The {@link java.nio.charset.Charset Charset} to use while
   *            writing the data.
   */
  public CsvWriter(String fileName, char delimiter, Charset charset) {
    if (fileName == null) {
      throw new IllegalArgumentException("Parameter fileName can not be null.");
    }
    if (charset == null) {
      throw new IllegalArgumentException("Parameter charset can not be null.");
    }
    this.fileName = fileName;
    userSettings.Delimiter = delimiter;
    this.charset = charset;
  }
  /**
   * Creates a {@link com.csvreader.CsvWriter CsvWriter} object using a file
   * as the data destination. 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 output the data.
   */
  public CsvWriter(String fileName) {
    this(fileName, Letters.COMMA, Charset.forName("ISO-8859-1"));
  }
  /**
   * Creates a {@link com.csvreader.CsvWriter CsvWriter} object using a Writer
   * to write data to.
   * 
   * @param outputStream
   *            The stream to write the column delimited data to.
   * @param delimiter
   *            The character to use as the column delimiter.
   */
  public CsvWriter(Writer outputStream, char delimiter) {
    if (outputStream == null) {
      throw new IllegalArgumentException("Parameter outputStream can not be null.");
    }
    this.outputStream = new PrintWriter(outputStream);
    userSettings.Delimiter = delimiter;
    initialized = true;
  }
  /**
   * Creates a {@link com.csvreader.CsvWriter CsvWriter} object using an
   * OutputStream to write data to.
   * 
   * @param outputStream
   *            The stream to write the column delimited data to.
   * @param delimiter
   *            The character to use as the column delimiter.
   * @param charset
   *            The {@link java.nio.charset.Charset Charset} to use while
   *            writing the data.
   */
  public CsvWriter(OutputStream outputStream, char delimiter, Charset charset) {
    this(new OutputStreamWriter(outputStream, charset), delimiter);
  }
  /**
   * Gets the character being used as the column delimiter.
   * 
   * @return The character being used as the column delimiter.
   */
  public char getDelimiter() {
    return userSettings.Delimiter;
  }
  /**
   * Sets the character to use as the column delimiter.
   * 
   * @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 writing data or not.
   * 
   * @return Whether text qualifiers will be used while writing data or not.
   */
  public boolean getUseTextQualifier() {
    return userSettings.UseTextQualifier;
  }
  /**
   * Sets whether text qualifiers will be used while writing data or not.
   * 
   * @param useTextQualifier
   *            Whether to use a text qualifier while writing data or not.
   */
  public void setUseTextQualifier(boolean useTextQualifier) {
    userSettings.UseTextQualifier = useTextQualifier;
  }
  public int getEscapeMode() {
    return userSettings.EscapeMode;
  }
  public void setEscapeMode(int escapeMode) {
    userSettings.EscapeMode = escapeMode;
  }
  public void setComment(char comment) {
    userSettings.Comment = comment;
  }
  public char getComment() {
    return userSettings.Comment;
  }
  /**
   * Whether fields will be surrounded by the text qualifier even if the
   * qualifier is not necessarily needed to escape this field.
   * 
   * @return Whether fields will be forced to be qualified or not.
   */
  public boolean getForceQualifier() {
    return userSettings.ForceQualifier;
  }
  /**
   * Use this to force all fields to be surrounded by the text qualifier even
   * if the qualifier is not necessarily needed to escape this field. Default
   * is false.
   * 
   * @param forceQualifier
   *            Whether to force the fields to be qualified or not.
   */
  public void setForceQualifier(boolean forceQualifier) {
    userSettings.ForceQualifier = forceQualifier;
  }
  /**
   * Writes another column of data to this record.
   * 
   * @param content
   *            The data for the new column.
   * @param preserveSpaces
   *            Whether to preserve leading and trailing whitespace in this
   *            column of data.
   * @exception IOException
   *                Thrown if an error occurs while writing data to the
   *                destination stream.
   */
  public void write(String content, boolean preserveSpaces)
      throws IOException {
    checkClosed();
    checkInit();
    if (content == null) {
      content = "";
    }
    if (!firstColumn) {
      outputStream.write(userSettings.Delimiter);
    }
    boolean textQualify = userSettings.ForceQualifier;
    if (!preserveSpaces && content.length() > 0) {
      content = content.trim();
    }
    if (!textQualify
        && userSettings.UseTextQualifier
        && (content.indexOf(userSettings.TextQualifier) > -1
            || content.indexOf(userSettings.Delimiter) > -1
            || (!useCustomRecordDelimiter && (content
                .indexOf(Letters.LF) > -1 || content
                .indexOf(Letters.CR) > -1))
            || (useCustomRecordDelimiter && content
                .indexOf(userSettings.RecordDelimiter) > -1)
            || (firstColumn && content.length() > 0 && content
                .charAt(0) == userSettings.Comment) ||
        // check for empty first column, which if on its own line must
        // be qualified or the line will be skipped
        (firstColumn && content.length() == 0))) {
      textQualify = true;
    }
    if (userSettings.UseTextQualifier && !textQualify
        && content.length() > 0 && preserveSpaces) {
      char firstLetter = content.charAt(0);
      if (firstLetter == Letters.SPACE || firstLetter == Letters.TAB) {
        textQualify = true;
      }
      if (!textQualify && content.length() > 1) {
        char lastLetter = content.charAt(content.length() - 1);
        if (lastLetter == Letters.SPACE || lastLetter == Letters.TAB) {
          textQualify = true;
        }
      }
    }
    if (textQualify) {
      outputStream.write(userSettings.TextQualifier);
      if (userSettings.EscapeMode == ESCAPE_MODE_BACKSLASH) {
        content = replace(content, "" + Letters.BACKSLASH, ""
            + Letters.BACKSLASH + Letters.BACKSLASH);
        content = replace(content, "" + userSettings.TextQualifier, ""
            + Letters.BACKSLASH + userSettings.TextQualifier);
      } else {
        content = replace(content, "" + userSettings.TextQualifier, ""
            + userSettings.TextQualifier
            + userSettings.TextQualifier);
      }
    } else if (userSettings.EscapeMode == ESCAPE_MODE_BACKSLASH) {
      content = replace(content, "" + Letters.BACKSLASH, ""
          + Letters.BACKSLASH + Letters.BACKSLASH);
      content = replace(content, "" + userSettings.Delimiter, ""
          + Letters.BACKSLASH + userSettings.Delimiter);
      if (useCustomRecordDelimiter) {
        content = replace(content, "" + userSettings.RecordDelimiter,
            "" + Letters.BACKSLASH + userSettings.RecordDelimiter);
      } else {
        content = replace(content, "" + Letters.CR, ""
            + Letters.BACKSLASH + Letters.CR);
        content = replace(content, "" + Letters.LF, ""
            + Letters.BACKSLASH + Letters.LF);
      }
      if (firstColumn && content.length() > 0
          && content.charAt(0) == userSettings.Comment) {
        if (content.length() > 1) {
          content = "" + Letters.BACKSLASH + userSettings.Comment
              + content.substring(1);
        } else {
          content = "" + Letters.BACKSLASH + userSettings.Comment;
        }
      }
    }
    outputStream.write(content);
    if (textQualify) {
      outputStream.write(userSettings.TextQualifier);
    }
    firstColumn = false;
  }
  /**
   * Writes another column of data to this record. Does not preserve
   * leading and trailing whitespace in this column of data.
   * 
   * @param content
   *            The data for the new column.
   * @exception IOException
   *                Thrown if an error occurs while writing data to the
   *                destination stream.
   */
  public void write(String content) throws IOException {
    write(content, false);
  }
  public void writeComment(String commentText) throws IOException {
    checkClosed();
    checkInit();
    outputStream.write(userSettings.Comment);
    outputStream.write(commentText);
    if (useCustomRecordDelimiter) {
      outputStream.write(userSettings.RecordDelimiter);
    } else {
      outputStream.println();
    }
    firstColumn = true;
  }
  /**
   * Writes a new record using the passed in array of values.
   * 
   * @param values
   *            Values to be written.
   * 
   * @param preserveSpaces
   *            Whether to preserver leading and trailing spaces in columns
   *            while writing out to the record or not.
   * 
   * @throws IOException
   *             Thrown if an error occurs while writing data to the
   *             destination stream.
   */
  public void writeRecord(String[] values, boolean preserveSpaces)
      throws IOException {
    if (values != null && values.length > 0) {
      for (int i = 0; i < values.length; i++) {
        write(values[i], preserveSpaces);
      }
      endRecord();
    }
  }
  /**
   * Writes a new record using the passed in array of values.
   * 
   * @param values
   *            Values to be written.
   * 
   * @throws IOException
   *             Thrown if an error occurs while writing data to the
   *             destination stream.
   */
  public void writeRecord(String[] values) throws IOException {
    writeRecord(values, false);
  }
  /**
   * Ends the current record by sending the record delimiter.
   * 
   * @exception IOException
   *                Thrown if an error occurs while writing data to the
   *                destination stream.
   */
  public void endRecord() throws IOException {
    checkClosed();
    checkInit();
    if (useCustomRecordDelimiter) {
      outputStream.write(userSettings.RecordDelimiter);
    } else {
      outputStream.println();
    }
    firstColumn = true;
  }
  /**
   * 
   */
  private void checkInit() throws IOException {
    if (!initialized) {
      if (fileName != null) {
        outputStream = new PrintWriter(new OutputStreamWriter(
            new FileOutputStream(fileName), charset));
      }
      initialized = true;
    }
  }
  /**
   * Clears all buffers for the current writer and causes any buffered data to
   * be written to the underlying device.
   */
  public void flush() {
    outputStream.flush();
  }
  /**
   * 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;
      }
      try {
        if (initialized) {
          outputStream.close();
        }
      } catch (Exception e) {
        // just eat the exception
      }
      outputStream = null;
      closed = true;
    }
  }
  /**
   * 
   */
  private void checkClosed() throws IOException {
    if (closed) {
      throw new IOException(
      "This instance of the CsvWriter class has already been closed.");
    }
  }
  /**
   * 
   */
  protected void finalize() {
    close(false);
  }
  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';
  }
  private class UserSettings {
    // having these as publicly accessible members will prevent
    // the overhead of the method call that exists on properties
    public char TextQualifier;
    public boolean UseTextQualifier;
    public char Delimiter;
    public char RecordDelimiter;
    public char Comment;
    public int EscapeMode;
    public boolean ForceQualifier;
    public UserSettings() {
      TextQualifier = Letters.QUOTE;
      UseTextQualifier = true;
      Delimiter = Letters.COMMA;
      RecordDelimiter = Letters.NULL;
      Comment = Letters.POUND;
      EscapeMode = ESCAPE_MODE_DOUBLED;
      ForceQualifier = false;
    }
  }
  public static String replace(String original, String pattern, String replace) {
    final int len = pattern.length();
    int found = original.indexOf(pattern);
    if (found > -1) {
      StringBuffer sb = new StringBuffer();
      int start = 0;
      while (found != -1) {
        sb.append(original.substring(start, found));
        sb.append(replace);
        start = found + len;
        found = original.indexOf(pattern, start);
      }
      sb.append(original.substring(start));
      return sb.toString();
    } else {
      return original;
    }
  }
}