Skip to content

Commit

Permalink
improve javadoc
Browse files Browse the repository at this point in the history
  • Loading branch information
osiegmar committed Dec 3, 2023
1 parent 408475f commit f002862
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 26 deletions.
44 changes: 34 additions & 10 deletions lib/src/main/java/de/siegmar/fastcsv/reader/CsvRecord.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,14 @@
import java.util.StringJoiner;

/**
* Index based CSV-record.
* 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 {
Expand Down Expand Up @@ -56,18 +63,34 @@ public class CsvRecord {
}

/**
* Returns the starting line number (starting with 1). On multi-line records this is the starting
* line number.
* Empty lines could be skipped via {@link CsvReader.CsvReaderBuilder#skipEmptyLines(boolean)}.
* 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.
*
* @return the starting line number
* @return the starting line number of this record, starting from 1
*/
public long getStartingLineNumber() {
return startingLineNumber;
}

/**
* Gets a field value by its index (starting with 0).
* Retrieves the value of a field based on its index, with indexing starting from 0.
*
* @param index index of the field to return
* @return field value, never {@code null}
Expand All @@ -78,7 +101,7 @@ public String getField(final int index) {
}

/**
* Gets all fields of this record as an unmodifiable list.
* Retrieves all fields of this record as an unmodifiable list.
*
* @return all fields of this record, never {@code null}
*/
Expand All @@ -87,7 +110,7 @@ public List<String> getFields() {
}

/**
* Gets the number of fields of this record.
* Obtains the count of fields in this record.
*
* @return the number of fields of this record
* @see CsvReader.CsvReaderBuilder#ignoreDifferentFieldCount(boolean)
Expand All @@ -97,7 +120,7 @@ public int getFieldCount() {
}

/**
* Provides the information if the record is a commented record.
* Indicates whether the record is a commented record.
*
* @return {@code true} if the record is a commented record
* @see CsvReader.CsvReaderBuilder#commentStrategy(CommentStrategy)
Expand All @@ -107,7 +130,8 @@ public boolean isComment() {
}

/**
* Provides the information if the record is an empty record.
* Indicates whether the record is considered empty, signifying that it contains only a single field with an empty
* string.
*
* @return {@code true} if the record is an empty record
* @see CsvReader.CsvReaderBuilder#skipEmptyLines(boolean)
Expand Down
65 changes: 49 additions & 16 deletions lib/src/main/java/de/siegmar/fastcsv/reader/NamedCsvRecord.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,14 @@
import java.util.StringJoiner;

/**
* Name (header) based CSV-record.
* Represents an immutable CSV record with named (and indexed) fields.
* <p>
* The field values are never {@code null}. Empty fields are represented as empty strings.
* <p>
* Named CSV records are created by {@link NamedCsvReader}.
*
* @see NamedCsvReader
* @see CsvReader
*/
@SuppressWarnings("PMD.ArrayIsStoredDirectly")
public final class NamedCsvRecord extends CsvRecord {
Expand All @@ -26,9 +33,13 @@ public final class NamedCsvRecord extends CsvRecord {
}

/**
* Gets a field value by its name (first occurrence if duplicates exists).
* Retrieves the value of a field by its case-sensitive name, considering the first occurrence in case of
* duplicates.
* <p>
* This method is equivalent to {@code findField(name).orElseThrow(NoSuchElementException::new)} although a
* more explanatory exception message is provided.
*
* @param name field name
* @param name case-sensitive name of the field to be retrieved
* @return field value, never {@code null}
* @throws NoSuchElementException if this record has no such field
* @throws NullPointerException if name is {@code null}
Expand All @@ -37,17 +48,23 @@ public final class NamedCsvRecord extends CsvRecord {
*/
public String getField(final String name) {
final int fieldPos = findHeader(name);

// Check if the field position is valid
if (fieldPos == -1) {
throw new NoSuchElementException(MessageFormat.format(
"Header does not contain a field ''{0}''. Valid names are: {1}", name, Arrays.toString(header)));
} else if (fieldPos >= fields.length) {
}
if (fieldPos >= fields.length) {
throw new NoSuchElementException(MessageFormat.format(
"Field ''{0}'' is on position {1}, but current record only contains {2} fields",
name, fieldPos + 1, fields.length));
}

// Return the value of the field
return fields[fieldPos];
}

// Finds the position of the first occurrence of the given header name (case-sensitive); returns -1 if not found
private int findHeader(final String name) {
for (int i = 0; i < header.length; i++) {
final String h = header[i];
Expand All @@ -60,25 +77,33 @@ private int findHeader(final String name) {
}

/**
* Finds a field value by its name (first occurrence if duplicates exists).
* Retrieves the value of a field by its case-sensitive name, considering the first occurrence in case of
* duplicates.
* <p>
* This method is equivalent to {@code findFields(name).stream().findFirst()} but more performant.
*
* @param name field name
* @return the field value ({@link Optional#empty()} if record doesn't contain that field), never {@code null}
* @param name case-sensitive name of the field to be retrieved
* @return An {@code Optional} containing the value of the field if found,
* or an empty {@code Optional} if the field is not present. Never returns {@code null}.
* @throws NullPointerException if name is {@code null}
* @see #findFields(String)
*/
public Optional<String> findField(final String name) {
final int fieldPos = findHeader(name);

// Check if the field position is valid
if (fieldPos == -1 || fieldPos >= fields.length) {
return Optional.empty();
}

// Return the value of the field wrapped in an Optional
return Optional.of(fields[fieldPos]);
}

/**
* Builds a list of field values found by its name.
* Collects all field values with the given name (case-sensitive) in the order they appear in the header.
*
* @param name field name
* @param name case-sensitive name of the field to collect values for
* @return the field values (empty list if record doesn't contain that field), never {@code null}
* @throws NullPointerException if name is {@code null}
*/
Expand All @@ -94,13 +119,16 @@ public List<String> findFields(final String name) {
}

/**
* Builds an ordered map of header names and field values (first occurrence if duplicates exists)
* of this record.
* Constructs an ordered map, associating header names with corresponding field values of this record,
* considering the first occurrence in case of duplicates.
* <p>
* The map will contain only entries for fields that have a key and a value –
* no map entry will have a {@code null} key or value.
* The constructed map will only contain entries for fields that have a key and a value. No map entry will have a
* {@code null} key or value.
* <p>
* If you need to collect all fields with the same name (duplicate header), use {@link #getFieldsAsMapList()}.
*
* @return an ordered map of header names and field values of this record, never {@code null}
* @see #getFieldsAsMapList()
*/
public Map<String, String> getFieldsAsMap() {
final int bound = commonSize();
Expand All @@ -112,12 +140,16 @@ public Map<String, String> getFieldsAsMap() {
}

/**
* Builds an unordered map of header names and field values of this record.
* Constructs an unordered map, associating header names with an ordered list of corresponding field values of
* this record.
* <p>
* The constructed map will only contain entries for fields that have a key and a value. No map entry will have a
* {@code null} key or value.
* <p>
* The map will contain only entries for fields that have a key and a value –
* no map entry will have a {@code null} key or value.
* If you don't have to handle duplicate headers, you may simply use {@link #getFieldsAsMap()}.
*
* @return an unordered map of header names and field values of this record, never {@code null}
* @see #getFieldsAsMap()
*/
@SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
public Map<String, List<String>> getFieldsAsMapList() {
Expand All @@ -135,6 +167,7 @@ public Map<String, List<String>> getFieldsAsMapList() {
return map;
}

// Mappings will only be created for fields that have a key and a value – return the minimum of both sizes
private int commonSize() {
return Math.min(header.length, fields.length);
}
Expand Down

0 comments on commit f002862

Please sign in to comment.