/** * Copyright (C) 2015 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.strata.collect.io; import java.io.UncheckedIOException; import java.util.List; import java.util.function.Consumer; import java.util.stream.Collectors; import java.util.stream.Stream; import com.opengamma.strata.collect.ArgChecker; import com.opengamma.strata.collect.Unchecked; /** * Outputs a CSV formatted file. * <p> * Provides a simple tool for writing a CSV file. * <p> * Each line in the CSV file will consist of comma separated entries. * Each entry may be quoted using a double quote. * If an entry contains a double quote, comma or trimmable whitespace, it will be quoted. * Two double quotes will be used to escape a double quote. */ public final class CsvOutput { /** * The header row, ordered as the headers appear in the file. */ private final Appendable underlying; /** * The new line string. */ private final String newLine; /** * Creates an instance, using the system default line separator. * * @param underlying the underlying writer */ public CsvOutput(Appendable underlying) { this(underlying, System.lineSeparator()); } /** * Creates an instance, allowing the new line charactor to be controlled. * * @param underlying the underlying writer * @param newLine the new line string */ public CsvOutput(Appendable underlying, String newLine) { this.underlying = ArgChecker.notNull(underlying, "underlying"); this.newLine = newLine; } //------------------------------------------------------------------------ /** * Writes CSV lines to the underlying. * <p> * The boolean flag controls whether each entry is always quoted or only quoted when necessary. * * @param lines the lines to write * @param alwaysQuote when true, each column will be quoted, when false, quoting is selective * @throws UncheckedIOException if an IO exception occurs */ public void writeLines(Iterable<? extends List<String>> lines, boolean alwaysQuote) { ArgChecker.notNull(lines, "lines"); for (List<String> line : lines) { writeLine(line, alwaysQuote); } } /** * Writes a CSV line to the underlying, only quoting if needed. * <p> * This can be used as a method reference from a {@code Stream} pipeline from * {@link Stream#forEachOrdered(Consumer)}. * * @param line the line to write * @throws UncheckedIOException if an IO exception occurs */ public void writeLine(List<String> line) { writeLine(line, false); } /** * Writes a CSV line to the underlying. * <p> * The boolean flag controls whether each entry is always quoted or only quoted when necessary. * * @param line the line to write * @param alwaysQuote when true, each column will be quoted, when false, quoting is selective * @throws UncheckedIOException if an IO exception occurs */ public void writeLine(List<String> line, boolean alwaysQuote) { ArgChecker.notNull(line, "line"); Unchecked.wrap(() -> underlying.append(formatLine(line, alwaysQuote)).append(newLine)); } // formats the line private String formatLine(List<String> line, boolean alwaysQuote) { return line.stream() .map(entry -> formatEntry(entry, alwaysQuote)) .collect(Collectors.joining(",")); } // formats a single entry, quoting if necessary private String formatEntry(String entry, boolean alwaysQuote) { if (alwaysQuote || isQuotingRequired(entry)) { return quotedEntry(entry); } else { return entry; } } // quoting is required if entry contains quote, comma or trimmable whitespace private boolean isQuotingRequired(String line) { return line.indexOf('"') >= 0 || line.indexOf(',') >= 0 || line.trim().length() != line.length(); } // quotes the entry private String quotedEntry(String entry) { StringBuilder buf = new StringBuilder(entry.length() + 8); buf.append('"') .append(entry.replace("\"", "\"\"")) .append('"'); return buf.toString(); } }