package net.quies.tech.csv; /* Copyright (c) 2006, 2007 Pascal S. de Kloe. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ import java.io.*; /** * Formats CSV to to a stream. @author Pascal S. de Kloe @since 1.0 */ public final class CSVWriter { /** @param target the CSV stream. The use of {@link java.io.BufferedWriter} is recommended. @since 1.0 */ public CSVWriter(Writer target) { if (target == null) throw new IllegalArgumentException(); out = target; } /** * Flushes the stream. @throws IOException if the {@code Writer} caused any troubles. @since 1.0 @see java.io.Writer#flush() */ public void flush() throws IOException { out.flush(); } /** @deprecated As of version 1.1, replaced by {@link #getRecordIndex()}. @since 1.0 */ public long getRecordNumber() { return getRecordIndex(); } /** * Gets the zero-based record index which is equal to the number of written records. @since 1.1 */ public long getRecordIndex() { return record; } /** * This method is equal to {@link #writeRecord(String[])}. @since 1.0 */ public void writeHeader(String[] name) throws IOException, RFC4180Violation { writeRecord(name); } /** @throws NullPointerException if {@code field} is {@code null} or if {@code field} has any {@code null} elements. @throws IOException if the {@code Writer} caused any troubles. @throws RFC4180Violation if {@code field} has no elements or if one or more fields contain characters that can't be written. * These characters are in ASCII sexadecimal: 00-09, 0B-0C, 0E-1F. @since 1.0 */ public void writeRecord(String[] field) throws IOException, RFC4180Violation { int fields = field.length; // throws NullPointerException if field is null if (fields == 0) throw new RFC4180Violation("A record must contain at least one field."); writeField(field[0]); for (int i = 1; i < fields; ++i) { out.write((int) ','); writeField(field[i]); } out.write("\r\n"); ++record; } private void writeField(String field) throws IOException, RFC4180Violation { char[] data = field.toCharArray(); // throws NullPointerException if field is null if (needEscape(data)) writeEscaped(data); else out.write(data); } /** * Verifies the characters. @return {@code true} if {@code data} containes characters that need to be escaped. @throws RFC4180Violation if {@code data} containes characters that can't be written. */ private static boolean needEscape(char[] data) throws RFC4180Violation { boolean need = false; for (int i = data.length; --i >= 0;) { char c = data[i]; if (c < ' ') { if (c == '\r' || c == '\n') need = true; else throw new RFC4180Violation("illegal character 0x" + Integer.toHexString((int) c).toUpperCase()); } else if (c == ',' || c == '"') need = true; } return need; } private void writeEscaped(char[] data) throws IOException { out.write((int) '"'); for (int i = 0; i < data.length; ++i) { char c = data[i]; if (c == '"') out.write("\"\""); else out.write((int) c); } out.write((int) '"'); } private final Writer out; private long record = 0L; }