Skip to content

Commit

Permalink
Add writeRecord() to CsvWriter to allow writing records field by field
Browse files Browse the repository at this point in the history
  • Loading branch information
osiegmar committed Apr 14, 2024
1 parent caa1490 commit b648663
Show file tree
Hide file tree
Showing 5 changed files with 178 additions and 11 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
- Nothing yet
### Added
- Add `writeRecord()` to `CsvWriter` to allow writing records field by field

## [3.1.0] - 2024-03-09
### Added
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package example;

import java.io.StringWriter;

import de.siegmar.fastcsv.writer.CsvWriter;

/**
* Example for writing CSV data field by field.
*/
public class ExampleCsvWriterWithSingleFields {

public static void main(final String[] args) {
final StringWriter sw = new StringWriter();
CsvWriter.builder().build(sw)
.writeRecord("header1", "header2")
.writeRecord().writeField("value1").writeField("value2").endRecord();

System.out.println(sw);
}

}
2 changes: 1 addition & 1 deletion lib/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ plugins {
}

group = "de.siegmar"
version = "3.1.0"
version = "3.2.0-SNAPSHOT"

project.base.archivesName = "fastcsv"

Expand Down
77 changes: 77 additions & 0 deletions lib/src/intTest/java/blackbox/writer/CsvWriterRecordTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package blackbox.writer;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

import java.io.IOException;
import java.io.StringWriter;
import java.io.UncheckedIOException;
import java.util.List;

import org.junit.jupiter.api.Test;

import de.siegmar.fastcsv.writer.CsvWriter;
import de.siegmar.fastcsv.writer.LineDelimiter;

class CsvWriterRecordTest {

private final CsvWriter.CsvWriterBuilder crw = CsvWriter.builder()
.lineDelimiter(LineDelimiter.LF);

@Test
void emptyRecord() throws IOException {
final var sw = new StringWriter();
crw.build(sw)
.writeRecord()
.endRecord()
.close();

assertThat(sw).hasToString("\n");
}

@Test
void simpleRecord() throws IOException {
final var sw = new StringWriter();
crw.build(sw)
.writeRecord().writeField("foo").writeField("bar").endRecord()
.writeRecord("baz", "qux")
.close();

assertThat(sw).hasToString("foo,bar\nbaz,qux\n");
}

@Test
void ioError() {
final var un = new UnwritableWriter();
final var csv = crw.bufferSize(0).build(un).writeRecord();

assertThatThrownBy(() -> csv.writeField("foo"))
.isInstanceOf(UncheckedIOException.class);

assertThatThrownBy(csv::endRecord)
.isInstanceOf(UncheckedIOException.class);
}

@Test
void openRecordWriteComment() {
final CsvWriter csv = crw.build(new StringWriter());
csv.writeRecord();

assertThatThrownBy(csv::writeRecord)
.isInstanceOf(IllegalStateException.class)
.hasMessage("Record already started, call end() on CsvWriterRecord first");

assertThatThrownBy(() -> csv.writeComment("foo"))
.isInstanceOf(IllegalStateException.class)
.hasMessage("Record already started, call end() on CsvWriterRecord first");

assertThatThrownBy(() -> csv.writeRecord("foo"))
.isInstanceOf(IllegalStateException.class)
.hasMessage("Record already started, call end() on CsvWriterRecord first");

assertThatThrownBy(() -> csv.writeRecord(List.of("foo")))
.isInstanceOf(IllegalStateException.class)
.hasMessage("Record already started, call end() on CsvWriterRecord first");
}

}
86 changes: 77 additions & 9 deletions lib/src/main/java/de/siegmar/fastcsv/writer/CsvWriter.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ public final class CsvWriter implements Closeable {
private int currentLineNo = 1;
private final char[] lineDelimiterChars;
private final char[] emptyFieldValue;
private boolean openRecordWriter;

CsvWriter(final Writer writer, final char fieldSeparator, final char quoteCharacter,
final char commentCharacter, final QuoteStrategy quoteStrategy, final LineDelimiter lineDelimiter,
Expand Down Expand Up @@ -83,20 +84,20 @@ public static CsvWriterBuilder builder() {
* not configured otherwise ({@link QuoteStrategies#EMPTY})).
* @return This CsvWriter.
* @throws UncheckedIOException if a write error occurs
* @throws IllegalStateException if a record is already started (by calling {@link #writeRecord()}) and not ended
* @see #writeRecord(String...)
*/
public CsvWriter writeRecord(final Iterable<String> values) {
validateNoOpenRecord();
try {
int fieldIdx = 0;
for (final String value : values) {
writeInternal(value, fieldIdx++);
}
endRecord();
return endRecord();
} catch (final IOException e) {
throw new UncheckedIOException(e);
}

return this;
}

/**
Expand All @@ -106,19 +107,42 @@ public CsvWriter writeRecord(final Iterable<String> values) {
* not configured otherwise ({@link QuoteStrategies#EMPTY}))
* @return This CsvWriter.
* @throws UncheckedIOException if a write error occurs
* @throws IllegalStateException if a record is already started (by calling {@link #writeRecord()}) and not ended
* @see #writeRecord(Iterable)
*/
public CsvWriter writeRecord(final String... values) {
validateNoOpenRecord();
try {
for (int i = 0; i < values.length; i++) {
writeInternal(values[i], i);
}
endRecord();
return endRecord();
} catch (final IOException e) {
throw new UncheckedIOException(e);
}
}

return this;
/**
* Starts a new record.
* <p>
* This method is used to write a record field by field. The record is ended by calling
* {@link CsvWriterRecord#endRecord()}.
*
* @return CsvWriterRecord instance to write fields to.
* @throws IllegalStateException if a record is already started
* @see #writeRecord(String...)
* @see #writeRecord(Iterable)
*/
public CsvWriterRecord writeRecord() {
validateNoOpenRecord();
openRecordWriter = true;
return new CsvWriterRecord();
}

private void validateNoOpenRecord() {
if (openRecordWriter) {
throw new IllegalStateException("Record already started, call end() on CsvWriterRecord first");
}
}

@SuppressWarnings("checkstyle:BooleanExpressionComplexity")
Expand Down Expand Up @@ -222,19 +246,19 @@ private static void writeEscaped(final Writer w, final String value, final char
* {@link CsvWriterBuilder#lineDelimiter(LineDelimiter)}.
* @return This CsvWriter.
* @throws UncheckedIOException if a write error occurs
* @throws IllegalStateException if a record is already started (by calling {@link #writeRecord()}) and not ended
*/
public CsvWriter writeComment(final String comment) {
validateNoOpenRecord();
try {
writer.write(commentCharacter);
if (comment != null && !comment.isEmpty()) {
writeCommentInternal(comment);
}
endRecord();
return endRecord();
} catch (final IOException e) {
throw new UncheckedIOException(e);
}

return this;
}

private void writeCommentInternal(final String comment) throws IOException {
Expand Down Expand Up @@ -270,12 +294,13 @@ private void writeFragment(final String comment, final int i, final int startPos
writer.write(commentCharacter);
}

private void endRecord() throws IOException {
private CsvWriter endRecord() throws IOException {
++currentLineNo;
writer.write(lineDelimiterChars, 0, lineDelimiterChars.length);
if (flushWriter) {
writer.flush();
}
return this;
}

@Override
Expand Down Expand Up @@ -542,4 +567,47 @@ public void close() throws IOException {

}

/**
* This class is used to write a record field by field.
* <p>
* The record is ended by calling {@link CsvWriterRecord#endRecord()}.
*/
public final class CsvWriterRecord {

private int fieldIdx;

private CsvWriterRecord() {
}

/**
* Writes a field to the current record.
* @param value the field value
* @return this CsvWriterRecord instance
* @throws UncheckedIOException if a write error occurs
*/
public CsvWriterRecord writeField(final String value) {
try {
writeInternal(value, fieldIdx++);
} catch (final IOException e) {
throw new UncheckedIOException(e);
}
return this;
}

/**
* Ends the current record.
* @return the enclosing CsvWriter instance
* @throws UncheckedIOException if a write error occurs
*/
public CsvWriter endRecord() {
openRecordWriter = false;
try {
return CsvWriter.this.endRecord();
} catch (final IOException e) {
throw new UncheckedIOException(e);
}
}

}

}

0 comments on commit b648663

Please sign in to comment.