<commit_before>
/*
 * Copyright (C) 2011 <NAME>. All rights reserved.
 *
 * $Id$
 */

package org.dellroad.stuff.string;

import au.com.bytecode.opencsv.CSVWriter;

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Map;

/**
 * CSV file output stream that ensures values are matched to the correct columns.
 * This class requires the <a href="http://opencsv.sourceforge.net/">OpenCSV</a> library.
 *
 * @see <a href="http://opencsv.sourceforge.net/">OpenCSV</a>
 */
public class CSVOutput {

    private final CSVWriter writer;
    private final String[] columns;
    private boolean header;

    /**
     * Constructor.
     *
     * @param writer destination for the CSV output
     * @param columns CSV columns names in their desired order
     * @throws IllegalArgumentException if {@code writer} is null
     * @throws IllegalArgumentException if {@code columns} is null
     * @throws IllegalArgumentException if {@code columns} contains a duplicate column name
     */
    public CSVOutput(Writer writer, String... columns) {
        this(writer, Arrays.asList(columns));
    }

    /**
     * Constructor.
     *
     * @param writer destination for the CSV output
     * @param columns CSV columns names, iterated in their desired order
     * @throws IllegalArgumentException if {@code writer} is null
     * @throws IllegalArgumentException if {@code columns} is null
     * @throws IllegalArgumentException if {@code columns} contains a duplicate column name
     */
    public CSVOutput(Writer writer, Iterable<String> columns) {
        this(new CSVWriter(new BufferedWriter(writer)), columns);
        if (writer == null)
            throw new IllegalArgumentException("null writer");
    }

    /**
     * Constructor.
     *
     * @param writer CSV output object
     * @param columns CSV columns names, iterated in their desired order
     * @throws IllegalArgumentException if {@code writer} is null
     * @throws IllegalArgumentException if {@code columns} is null
     * @throws IllegalArgumentException if {@code columns} contains a duplicate column name
     */
    public CSVOutput(CSVWriter writer, Iterable<String> columns) {
        if (writer == null)
            throw new IllegalArgumentException("null writer");
        if (columns == null)
            throw new IllegalArgumentException("null columns");
        this.writer = writer;
        LinkedHashSet<String> columnSet = new LinkedHashSet<String>();
        for (String column : columns) {
            if (!columnSet.add(column))
                throw new IllegalArgumentException("duplicate column name `" + column + "'");
        }
        this.columns = columnSet.toArray(new String[columnSet.size()]);
    }

    /**
     * Output a CSV row.
     *
     * @param row mapping from column name to value
     * @throws IllegalArgumentException if {@code row} contains an unknown column name
     */
    public void writeRow(Map<String, ?> row) {

        // Sanity check column names
        HashSet<String> unknowns = new HashSet<String>(row.keySet());
        unknowns.removeAll(Arrays.asList(this.columns));
        if (!unknowns.isEmpty())
            throw new IllegalArgumentException("row contains unknown column(s): " + unknowns);

        // Format columns
        String[] values = new String[this.columns.length];
        for (int i = 0; i < this.columns.length; i++)
            values[i] = this.formatObject(this.columns[i], row.get(this.columns[i]));

        // Output header
        if (!this.header) {
            this.writer.writeNext(this.columns);
            this.header = true;
        }

        // Output row
        this.writer.writeNext(values);
    }

    /**
     * Flush output.
     */
    public void flush() throws IOException {
        this.writer.flush();
    }<commit_msg>Output header row even if zero data rows are output.<commit_after>
/*
 * Copyright (C) 2011 <NAME>. All rights reserved.
 *
 * $Id$
 */

package org.dellroad.stuff.string;

import au.com.bytecode.opencsv.CSVWriter;

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Map;

/**
 * CSV file output stream that ensures values are matched to the correct columns.
 * This class requires the <a href="http://opencsv.sourceforge.net/">OpenCSV</a> library.
 *
 * @see <a href="http://opencsv.sourceforge.net/">OpenCSV</a>
 */
public class CSVOutput {

    private final CSVWriter writer;
    private final String[] columns;

    /**
     * Constructor.
     *
     * @param writer destination for the CSV output
     * @param columns CSV columns names in their desired order
     * @throws IllegalArgumentException if {@code writer} is null
     * @throws IllegalArgumentException if {@code columns} is null
     * @throws IllegalArgumentException if {@code columns} contains a duplicate column name
     */
    public CSVOutput(Writer writer, String... columns) {
        this(writer, Arrays.asList(columns));
    }

    /**
     * Constructor.
     *
     * @param writer destination for the CSV output
     * @param columns CSV columns names, iterated in their desired order
     * @throws IllegalArgumentException if {@code writer} is null
     * @throws IllegalArgumentException if {@code columns} is null
     * @throws IllegalArgumentException if {@code columns} contains a duplicate column name
     */
    public CSVOutput(Writer writer, Iterable<String> columns) {
        this(new CSVWriter(new BufferedWriter(writer)), columns);
        if (writer == null)
            throw new IllegalArgumentException("null writer");
    }

    /**
     * Constructor.
     *
     * @param writer CSV output object
     * @param columns CSV columns names, iterated in their desired order
     * @throws IllegalArgumentException if {@code writer} is null
     * @throws IllegalArgumentException if {@code columns} is null
     * @throws IllegalArgumentException if {@code columns} contains a duplicate column name
     */
    public CSVOutput(CSVWriter writer, Iterable<String> columns) {
        if (writer == null)
            throw new IllegalArgumentException("null writer");
        if (columns == null)
            throw new IllegalArgumentException("null columns");
        this.writer = writer;
        LinkedHashSet<String> columnSet = new LinkedHashSet<String>();
        for (String column : columns) {
            if (!columnSet.add(column))
                throw new IllegalArgumentException("duplicate column name `" + column + "'");
        }
        this.columns = columnSet.toArray(new String[columnSet.size()]);

        // Output header
        this.writer.writeNext(this.columns);
    }

    /**
     * Output a CSV row.
     *
     * @param row mapping from column name to value; missing or values are treated as null
     * @throws IllegalArgumentException if {@code row} contains an unknown column name
     */
    public void writeRow(Map<String, ?> row) {

        // Sanity check column names
        HashSet<String> unknowns = new HashSet<String>(row.keySet());
        unknowns.removeAll(Arrays.asList(this.columns));
        if (!unknowns.isEmpty())
            throw new IllegalArgumentException("row contains unknown column(s): " + unknowns);

        // Format columns
        String[] values = new String[this.columns.length];
        for (int i = 0; i < this.columns.length; i++)
            values[i] = this.formatObject(this.columns[i], row.get(this.columns[i]));

        // Output row
        this.writer.writeNext(values);
    }

    /**
     * Flush output.
     */
    public void flush() throws IOException {
        this.writer.flush();
    }
{'fraction_non_alphanumeric': 0.1065989847715736, 'fraction_numerical': 0.0012690355329949238, 'mean_word_length': 2.7780441035474595, 'pattern_counts': {'":': 0, '<': 29, '<?xml version=': 0, '>': 27, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2268096', 'n_tokens_mistral': 2102, 'n_tokens_neox': 2034, 'n_words': 669}