Skip to content

Commit

Permalink
Merge branch '2.17'
Browse files Browse the repository at this point in the history
  • Loading branch information
cowtowncoder committed Jan 15, 2024
2 parents 943f663 + 2cd53c4 commit 4125969
Show file tree
Hide file tree
Showing 6 changed files with 87 additions and 18 deletions.
18 changes: 17 additions & 1 deletion csv/src/main/java/tools/jackson/dataformat/csv/CsvGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,23 @@ public enum Feature
* <p>
* Default value is false so that control characters are echoed out (backwards compatible).
*/
ESCAPE_CONTROL_CHARS_WITH_ESCAPE_CHAR(false)
ESCAPE_CONTROL_CHARS_WITH_ESCAPE_CHAR(false),

/**
* Feature that determines whether a line-feed will be written at the end of content,
* after the last row of output.
*<p>
* NOTE! When disabling this feature it is important that
* {@link #flush()} is NOT called before {@link #close()} is called;
* the current implementation relies on ability to essentially remove the
* last linefeed that was appended in the output buffer.
*<p>
* Default value is {@code true} so all rows, including the last, are terminated by
* a line feed.
*
* @since 2.17
*/
WRITE_LINEFEED_AFTER_LAST_ROW(true)
;

protected final boolean _defaultState;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,8 @@ public class CsvEncoder
*/
protected int _lastBuffered = -1;

protected boolean _trailingLFRemoved = false;

/*
/**********************************************************************
/* Output buffering, low-level
Expand Down Expand Up @@ -1093,6 +1095,11 @@ public void flush(boolean flushStream) throws IOException

public void close(boolean autoClose, boolean flushStream) throws IOException
{
// May need to remove the linefeed appended after the last row written
// (if not yet done)
if (!CsvGenerator.Feature.WRITE_LINEFEED_AFTER_LAST_ROW.enabledIn(_csvFeatures)) {
_removeTrailingLF();
}
_flushBuffer();
if (autoClose) {
_out.close();
Expand All @@ -1104,6 +1111,15 @@ public void close(boolean autoClose, boolean flushStream) throws IOException
_releaseBuffers();
}

private void _removeTrailingLF() throws IOException {
if (!_trailingLFRemoved) {
_trailingLFRemoved = true;
// Remove trailing LF if (but only if) it appears to be in output
// buffer (may not be possible if `flush()` has been called)
_outputTail = Math.max(0, _outputTail - _cfgLineSeparatorLength);
}
}

/*
/**********************************************************************
/* Internal methods
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,14 @@ public void testSimpleExplicit() throws Exception

FiveMinuteUser user = new FiveMinuteUser("Silu", "Seppala", false, Gender.MALE,
new byte[] { 1, 2, 3, 4, 5});
String csv = MAPPER.writer(schema).writeValueAsString(user);
assertEquals("Silu,Seppala,MALE,AQIDBAU=,false\n", csv);
assertEquals("Silu,Seppala,MALE,AQIDBAU=,false\n",
MAPPER.writer(schema).writeValueAsString(user));

// 14-Jan-2024, tatu: [dataformats-text#45] allow suppressing trailing LF:
assertEquals("Silu,Seppala,MALE,AQIDBAU=,false",
MAPPER.writer(schema)
.without(CsvGenerator.Feature.WRITE_LINEFEED_AFTER_LAST_ROW)
.writeValueAsString(user));
}

public void testSimpleWithAutoSchema() throws Exception
Expand All @@ -99,25 +105,30 @@ public void testSimpleWithAutoSchema() throws Exception

public void testWriteHeaders() throws Exception
{
CsvMapper mapper = mapperForCsv();
CsvSchema schema = mapper.schemaFor(FiveMinuteUser.class).withHeader();
CsvSchema schema = MAPPER.schemaFor(FiveMinuteUser.class).withHeader();
FiveMinuteUser user = new FiveMinuteUser("Barbie", "Benton", false, Gender.FEMALE, null);
String result = mapper.writer(schema).writeValueAsString(user);
assertEquals("firstName,lastName,gender,verified,userImage\n"
+"Barbie,Benton,FEMALE,false,\n", result);
}
+"Barbie,Benton,FEMALE,false,\n",
MAPPER.writer(schema).writeValueAsString(user));

// 14-Jan-2024, tatu: [dataformats-text#45] allow suppressing trailing LF:
assertEquals("firstName,lastName,gender,verified,userImage\n"
+"Barbie,Benton,FEMALE,false,",
MAPPER.writer(schema)
.without(CsvGenerator.Feature.WRITE_LINEFEED_AFTER_LAST_ROW)
.writeValueAsString(user));
}

/**
* Test that verifies that if a header line is needed, configured schema
* MUST contain at least one column
*/
public void testFailedWriteHeaders() throws Exception
{
CsvMapper mapper = mapperForCsv();
CsvSchema schema = CsvSchema.builder().setUseHeader(true).build();
FiveMinuteUser user = new FiveMinuteUser("Barbie", "Benton", false, Gender.FEMALE, null);
try {
mapper.writer(schema).writeValueAsString(user);
MAPPER.writer(schema).writeValueAsString(user);
fail("Should fail without columns");
} catch (CsvWriteException e) {
verifyException(e, "contains no column names");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@
import java.util.*;

import com.fasterxml.jackson.annotation.JsonPropertyOrder;

import tools.jackson.core.JsonGenerator;

import tools.jackson.databind.*;

import tools.jackson.dataformat.csv.CsvGenerator;
import tools.jackson.dataformat.csv.CsvMapper;
import tools.jackson.dataformat.csv.CsvSchema;
import tools.jackson.dataformat.csv.ModuleTestBase;
Expand Down Expand Up @@ -70,16 +74,30 @@ public void testMultipleListWrites() throws Exception

public void testWriteValuesWithPOJOs() throws Exception
{
CsvSchema schema = MAPPER.schemaFor(Pojo.class).withUseHeader(true);
final CsvSchema schema = MAPPER.schemaFor(Pojo.class).withUseHeader(true);

ObjectWriter writer = MAPPER.writer(schema);
StringWriter sw = new StringWriter();
SequenceWriter seqw = writer.writeValues(sw);
seqw.write(new Pojo(1, 2, 3));
seqw.write(new Pojo(0, 15, 9));
seqw.write(new Pojo(7, 8, 9));
seqw.flush();
try (SequenceWriter seqw = writer.writeValues(sw)) {
seqw.write(new Pojo(1, 2, 3));
seqw.write(new Pojo(0, 15, 9));
seqw.write(new Pojo(7, 8, 9));
}
assertEquals("a,b,c\n1,2,3\n0,15,9\n7,8,9\n",
sw.toString());
seqw.close();

// 14-Jan-2024, tatu: [dataformats-text#45] allow suppressing trailing LF.
// NOTE! Any form of `flush()` will prevent ability to "remove" trailing LF so...
writer = writer
.without(SerializationFeature.FLUSH_AFTER_WRITE_VALUE)
.without(CsvGenerator.Feature.WRITE_LINEFEED_AFTER_LAST_ROW);
sw = new StringWriter();
try (SequenceWriter seqw = writer.writeValues(sw)) {
seqw.write(new Pojo(1, 2, 3));
seqw.write(new Pojo(0, 15, 9));
seqw.write(new Pojo(7, 8, 9));
}
assertEquals("a,b,c\n1,2,3\n0,15,9\n7,8,9",
sw.toString());
}
}
6 changes: 6 additions & 0 deletions release-notes/CREDITS-2.x
Original file line number Diff line number Diff line change
Expand Up @@ -250,3 +250,9 @@ Arthur Chan (arthurscchan@github)
* Contributed fix for #445: `YAMLParser` throws unexpected `NullPointerException` in certain
number parsing cases
(2.16.1)
Mathieu Lavigne (@mathieu-lavigne)
* Proposed #45 (and suggested implementation): (csv) Allow skipping ending line break
(`CsvGenerator.Feature.WRITE_LINEFEED_AFTER_LAST_ROW`)
(2.17.0)
4 changes: 3 additions & 1 deletion release-notes/VERSION-2.x
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ Active Maintainers:

2.17.0 (not yet released)

-
#45: (csv) Allow skipping ending line break
(`CsvGenerator.Feature.WRITE_LINEFEED_AFTER_LAST_ROW`)
(proposed by Mathieu L)

2.16.1 (24-Dec-2023)

Expand Down

0 comments on commit 4125969

Please sign in to comment.