CsvRecord.java

package de.siegmar.fastcsv.reader;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.StringJoiner;

/**
 * Represents an immutable CSV record with unnamed (indexed) fields.
 * <p>
 * The field values are never {@code null}. Empty fields are represented as empty strings.
 * <p>
 * CSV records are created by {@link CsvReader} or {@link IndexedCsvReader}.
 *
 * @see CsvReader
 * @see IndexedCsvReader
 */
@SuppressWarnings("PMD.ArrayIsStoredDirectly")
public class CsvRecord {

    /**
     * The starting line number (starting with 1).
     *
     * @see #getStartingLineNumber()
     */
    @SuppressWarnings("checkstyle:VisibilityModifier")
    final long startingLineNumber;

    /**
     * The fields this record is composed of.
     *
     * @see #getField(int)
     * @see #getFields()
     */
    @SuppressWarnings("checkstyle:VisibilityModifier")
    final String[] fields;

    /**
     * If the record is a commented record.
     *
     * @see #isComment()
     */
    @SuppressWarnings("checkstyle:VisibilityModifier")
    final boolean comment;

    CsvRecord(final long startingLineNumber, final String[] fields,
              final boolean comment) {
        this.startingLineNumber = startingLineNumber;
        this.fields = fields;
        this.comment = comment;
    }

    /**
     * Provides the line number at which this record originated, starting from 1.
     * <p>
     * This information is particularly valuable in scenarios involving CSV files containing empty lines as well as
     * multi-line or commented records, where the record number may deviate from the line number.
     * <p>
     * Example:
     * <pre>
     * 1 foo,bar
     * 2 foo,"multi
     * 3 line bar"
     * 4                    (empty, potentially skipped)
     * 5 #commented record  (potentially skipped)
     * 6 "latest
     * 7 record"
     * </pre>
     * The last record (containing the multi-line field "latest\nrecord") would have a starting line number of 6,
     * no matter if empty lines or commented records are skipped or not.
     * <p>
     * A starting offset of 1 is used to be consistent with the line numbers shown of most text editors.
     * <p>
     * Note that this number is only correct if the CSV data was read from the very beginning. If you passed
     * a {@link java.io.Reader} to the {@link CsvReader} and have already read from it, the line number will be
     * incorrect.
     *
     * @return the starting line number of this record, starting from 1
     */
    public long getStartingLineNumber() {
        return startingLineNumber;
    }

    /**
     * Retrieves the value of a field based on its index, with indexing starting from 0.
     * <p>
     * There is always at least one field, even if the line was empty.
     * <p>
     * If this records holds a comment, the comment is returned by calling this method with index 0. The comment
     * character is not included in the returned value.
     *
     * @param index index of the field to return
     * @return field value, never {@code null}
     * @throws IndexOutOfBoundsException if the index is out of range
     */
    public String getField(final int index) {
        return fields[index];
    }

    /**
     * Retrieves all fields of this record as an unmodifiable list.
     * <p>
     * The returned list has a minimum size of 1, even if the line was empty.
     * For empty lines, the first field is an empty string.
     *
     * @return all fields of this record, never {@code null}
     */
    public List<String> getFields() {
        return Collections.unmodifiableList(Arrays.asList(fields));
    }

    /**
     * Gets the count of fields in this record.
     * <p>
     * The minimum number of fields is 1, even if the line was empty.
     *
     * @return the number of fields in this record
     * @see CsvReader.CsvReaderBuilder#ignoreDifferentFieldCount(boolean)
     */
    public int getFieldCount() {
        return fields.length;
    }

    /**
     * Indicates whether the record is a commented record.
     * <p>
     * Retrieve the comment by calling {@link #getField(int)} with index 0.
     *
     * @return {@code true} if the record is a commented record
     * @see CsvReader.CsvReaderBuilder#commentStrategy(CommentStrategy)
     */
    public boolean isComment() {
        return comment;
    }

    @Override
    public String toString() {
        return new StringJoiner(", ", CsvRecord.class.getSimpleName() + "[", "]")
            .add("startingLineNumber=" + startingLineNumber)
            .add("fields=" + Arrays.toString(fields))
            .add("comment=" + comment)
            .toString();
    }

}