/**
* Copyright (C) 2016 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.strata.collect.io;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.regex.Pattern;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
/**
* A row in a CSV file.
* <p>
* Represents a single row in a CSV file, accessed via {@link CsvFile}.
* Each row object provides access to the data in the row by field index.
* If the CSV file has headers, the headers can also be used to lookup the fields.
*/
public final class CsvRow {
/**
* The header row, ordered as the headers appear in the file.
*/
private final ImmutableList<String> headers;
/**
* The header map, transformed for case-insensitive searching.
*/
private final ImmutableMap<String, Integer> searchHeaders;
/**
* The fields in the row.
*/
private final ImmutableList<String> fields;
//------------------------------------------------------------------------
/**
* Creates an instance, specifying the headers and row.
* <p>
* See {@link CsvFile}.
*
* @param headers the headers
* @param fields the fields
*/
private CsvRow(ImmutableList<String> headers, ImmutableList<String> fields) {
this.headers = headers;
// need to allow duplicate headers and only store the first instance
Map<String, Integer> searchHeaders = new HashMap<>();
for (int i = 0; i < headers.size(); i++) {
String searchHeader = headers.get(i).toLowerCase(Locale.ENGLISH);
searchHeaders.putIfAbsent(searchHeader, i);
}
this.searchHeaders = ImmutableMap.copyOf(searchHeaders);
this.fields = fields;
}
/**
* Creates an instance, specifying the headers and row.
* <p>
* See {@link CsvFile}.
*
* @param headers the headers
* @param searchHeaders the search headers
* @param fields the fields
*/
CsvRow(ImmutableList<String> headers, ImmutableMap<String, Integer> searchHeaders, ImmutableList<String> fields) {
this.headers = headers;
this.searchHeaders = searchHeaders;
this.fields = fields;
}
//------------------------------------------------------------------------
/**
* Gets the header row.
* <p>
* If there is no header row, an empty list is returned.
*
* @return the header row
*/
public ImmutableList<String> headers() {
return headers;
}
/**
* Gets all fields in the row.
*
* @return the fields
*/
public ImmutableList<String> fields() {
return fields;
}
/**
* Gets the number of fields.
* <p>
* This will never be less than the number of headers.
*
* @return the number of fields
*/
public int fieldCount() {
return Math.max(fields.size(), headers.size());
}
/**
* Gets the specified field.
*
* @param index the field index
* @return the field
* @throws IndexOutOfBoundsException if the field index is invalid
*/
public String field(int index) {
if (index >= fields.size() && index < headers.size()) {
return "";
}
return fields.get(index);
}
/**
* Gets a single field value from the row by header.
* <p>
* This returns the value of the first column where the header matches the specified header.
* Matching is case insensitive.
*
* @param header the column header
* @return the trimmed field value
* @throws IllegalArgumentException if the header is not found
*/
public String getField(String header) {
return findField(header)
.orElseThrow(() -> new IllegalArgumentException("Header not found: " + header));
}
/**
* Gets a single field value from the row by header.
* <p>
* This returns the value of the first column where the header matches the specified header.
* Matching is case insensitive.
*
* @param header the column header
* @return the trimmed field value, empty if not found
*/
public Optional<String> findField(String header) {
return Optional.ofNullable(searchHeaders.get(header.toLowerCase(Locale.ENGLISH)))
.map(idx -> field(idx));
}
/**
* Gets a single field value from the row by header pattern.
* <p>
* This returns the value of the first column where the header matches the specified header pattern.
*
* @param headerPattern the header pattern to match
* @return the trimmed field value, empty
* @throws IllegalArgumentException if the header is not found
*/
public String getField(Pattern headerPattern) {
return findField(headerPattern)
.orElseThrow(() -> new IllegalArgumentException("Header pattern not found: " + headerPattern));
}
/**
* Gets a single field value from the row by header pattern.
* <p>
* This returns the value of the first column where the header matches the specified header pattern.
*
* @param headerPattern the header pattern to match
* @return the trimmed field value, empty if not found
*/
public Optional<String> findField(Pattern headerPattern) {
for (int i = 0; i < headers.size(); i++) {
if (headerPattern.matcher(headers.get(i)).matches()) {
return Optional.of(field(i));
}
}
return Optional.empty();
}
//-------------------------------------------------------------------------
/**
* Obtains a sub-row, containing a selection of fields by index.
* <p>
* All fields after the specified index are included.
*
* @param startInclusive the start index, zero-based, inclusive
* @return the sub row
*/
public CsvRow subRow(int startInclusive) {
return subRow(startInclusive, fields.size());
}
/**
* Obtains a sub-row, containing a selection of fields by index.
*
* @param startInclusive the start index, zero-based, inclusive
* @param endExclusive the end index, zero-based, exclusive
* @return the sub row
*/
public CsvRow subRow(int startInclusive, int endExclusive) {
return new CsvRow(
headers.subList(Math.min(startInclusive, headers.size()), Math.min(endExclusive, headers.size())),
fields.subList(startInclusive, endExclusive));
}
//-------------------------------------------------------------------------
/**
* Checks if this CSV file equals another.
* <p>
* The comparison checks the content.
*
* @param obj the other file, null returns false
* @return true if equal
*/
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj instanceof CsvRow) {
CsvRow other = (CsvRow) obj;
return headers.equals(other.headers) && fields.equals(other.fields);
}
return false;
}
/**
* Returns a suitable hash code for the CSV file.
*
* @return the hash code
*/
@Override
public int hashCode() {
return headers.hashCode() ^ fields.hashCode();
}
/**
* Returns a string describing the CSV file.
*
* @return the descriptive string
*/
@Override
public String toString() {
return "CsvRow" + fields.toString();
}
}