To generate CSV from Java consider this simple interface:
package net.doepner;
import java.io.IOException;
/**
* Generates CSV (comma separated values) for rows of Java objects
*/
public interface ICsvWriter {
/**
* Adds a row of objects to the CSV document
*
* @param values The objects in the row (count must match the number of the
* headers)
*/
void row(Object... values);
/**
* Writes CSV based on the the String representations of the objects
*
* @param appendable The writer to append to
* @throws IOException If underlying IO fails
*/
void appendTo(Appendable appendable) throws IOException;
}
I implemented the interface with this CsvWriter class:
package net.doepner;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedList;
import java.util.regex.Pattern;
/**
* Convenient generation of CSV
*/
public class CsvWriter implements ICsvWriter {
private static final CharSequence CSV_ROW_END = "\r\n";
private static final char LINE_BREAK_WITHIN_CELL = '\n';
private static final Pattern QUOTES = Pattern.compile("\"");
private static final String ESCAPED_QUOTE = "\"\"";
private final Object[] headers;
private final Collection<Object[]> rows = new LinkedList<Object[]>();
/**
* @param headers The objects representing the column headers
*/
public CsvWriter(Object... headers) {
this.headers = Arrays.copyOf(headers, headers.length);
if (cols() == 0) {
throw new IllegalArgumentException("No columns");
}
}
@Override
public final void row(Object... values) {
if (values.length != cols()) {
throw new IllegalArgumentException("Specify " + cols() + "values ");
}
rows.add(Arrays.copyOf(values, values.length));
}
@Override
public final void appendTo(Appendable appendable) throws IOException {
appendRow(appendable, headers);
for (Object[] row : rows) {
appendRow(appendable, row);
}
}
private static void appendRow(Appendable appendable, Object[] row)
throws IOException {
boolean first = true;
for (Object value : row) {
if (first) {
first = false;
} else {
appendable.append(",");
}
appendable.append('"');
appendable.append(toCsvString(value));
appendable.append('"');
}
appendable.append(CSV_ROW_END);
}
private static CharSequence toCsvString(Object object) {
if (object instanceof Iterable) {
final StringBuilder sb = new StringBuilder();
boolean first = true;
for (Object o : (Iterable<?>) object) {
if (first) {
first = false;
} else {
sb.append(LINE_BREAK_WITHIN_CELL);
}
sb.append(toCsvString(o));
}
return sb.toString();
} else {
if (object == null) {
return "";
} else {
final String s = object.toString();
return QUOTES.matcher(s).replaceAll(ESCAPED_QUOTE);
}
}
}
private int cols() {
return headers.length;
}
}
Example for how it can be used in a Servlet, here with full support for Unicode characters using UTF-8, in a way that even Excel understands:
private static final char BYTE_ORDER_MARK = (char) 0xfeff;
private static void generateCsv(ServletResponse resp,
Iterable<IMessage> messages)
throws ServletException {
resp.setContentType("text/csv");
resp.setCharacterEncoding("UTF-8");
final ICsvWriter csv = new CsvWriter(
"Date", "Sender", "Recipients", "Subject");
for (IMessage msg : messages) {
csv.row(msg.getDateTime(), msg.getSender(),
msg.getRecipients, msg.getSubject());
}
final PrintWriter writer = getResponseWriter(resp);
try {
// The BOM is required so that Excel will recognize UTF-8
// characters properly, i.e. all non-ASCII letters, etc.
writer.print(BYTE_ORDER_MARK);
writer.flush();
csv.appendTo(writer);
writer.flush();
} catch (IOException e) {
throw new ServletException(e);
}
}
private static PrintWriter getResponseWriter(ServletResponse resp)
throws ServletException {
try {
return resp.getWriter();
} catch (IOException e) {
throw new ServletException(e);
}
}