CsvGenerator.java
package tools.jackson.dataformat.csv;
import java.io.*;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import tools.jackson.core.*;
import tools.jackson.core.base.GeneratorBase;
import tools.jackson.core.io.CharacterEscapes;
import tools.jackson.core.json.DupDetector;
import tools.jackson.core.util.SimpleStreamWriteContext;
import tools.jackson.dataformat.csv.impl.CsvEncoder;
import tools.jackson.core.io.IOContext;
import tools.jackson.core.util.JacksonFeatureSet;
public class CsvGenerator extends GeneratorBase
{
protected final static long MIN_INT_AS_LONG = Integer.MIN_VALUE;
protected final static long MAX_INT_AS_LONG = Integer.MAX_VALUE;
private final static CsvSchema EMPTY_SCHEMA = CsvSchema.emptySchema();
/*
/**********************************************************************
/* Configuration
/**********************************************************************
*/
/**
* Bit flag composed of bits that indicate which
* {@link CsvWriteFeature}s
* are enabled.
*/
protected final int _formatFeatures;
/**
* Definition of columns being written, if available.
*/
protected final CsvSchema _schema;
// note: can not be final since we may need to re-create it for new schema
protected CsvEncoder _writer;
/*
/**********************************************************************
/* Output state
/**********************************************************************
*/
/**
* Object that keeps track of the current contextual state of the generator.
*/
protected SimpleStreamWriteContext _streamWriteContext;
/**
* Flag that indicates that we need to write header line, if
* one is needed. Used because schema may be specified after
* instance is constructed.
*/
protected boolean _handleFirstLine = true;
/**
* Index of column that we will be getting next, based on
* the property name call that was made.
*/
protected int _nextColumnByName = -1;
/**
* Decorator to use for decorating the column value to follow, if any;
* {@code null} if none.
*
* @since 2.18
*/
protected CsvValueDecorator _nextColumnDecorator;
/**
* Flag set when property to write is unknown, and the matching value
* is to be skipped quietly.
*/
protected boolean _skipValue;
/**
* Flag set when a row has just been finished, used to distinguish between
* null values within a row vs null rows.
* Only relevant for Array-wrapped rows.
*
* @since 2.21
*/
protected boolean _justFinishedRow = false;
/**
* Separator to use during writing of (simple) array value, to be encoded as a
* single column value, if any.
*/
protected String _arraySeparator = CsvSchema.NO_ARRAY_ELEMENT_SEPARATOR;
/**
* Accumulated contents of an array cell, if any
*/
protected StringBuilder _arrayContents;
/**
* Additional counter that indicates number of value entries in the
* array. Needed because `null` entries do not add content, but need
* to be separated by array cell separator
*/
protected int _arrayElements;
/**
* When skipping output (for "unknown" output), outermost write context
* where skipping should occur
*/
protected SimpleStreamWriteContext _skipWithin;
/*
/**********************************************************************
/* Life-cycle
/**********************************************************************
*/
public CsvGenerator(ObjectWriteContext writeCtxt, IOContext ioCtxt,
int streamWriteFeatures, int csvFeatures,
Writer out, CsvSchema schema, CsvCharacterEscapes characterEscapes)
{
super(writeCtxt, ioCtxt, streamWriteFeatures);
_formatFeatures = csvFeatures;
final DupDetector dups = StreamWriteFeature.STRICT_DUPLICATE_DETECTION.enabledIn(streamWriteFeatures)
? DupDetector.rootDetector(this) : null;
_streamWriteContext = SimpleStreamWriteContext.createRootContext(dups);
_schema = schema;
if (characterEscapes == null) {
characterEscapes = CsvCharacterEscapes.fromCsvFeatures(csvFeatures);
}
boolean useFastDoubleWriter = isEnabled(StreamWriteFeature.USE_FAST_DOUBLE_WRITER);
_writer = new CsvEncoder(ioCtxt, csvFeatures, out, schema, characterEscapes,
useFastDoubleWriter);
}
public CsvGenerator(ObjectWriteContext writeCtxt, IOContext ioCtxt,
int streamWriteFeatures, int csvFeatures,
CsvEncoder csvWriter)
{
super(writeCtxt, ioCtxt, streamWriteFeatures);
_formatFeatures = csvFeatures;
final DupDetector dups = StreamWriteFeature.STRICT_DUPLICATE_DETECTION.enabledIn(streamWriteFeatures)
? DupDetector.rootDetector(this) : null;
_streamWriteContext = SimpleStreamWriteContext.createRootContext(dups);
_schema = EMPTY_SCHEMA;
_writer = csvWriter;
}
/*
/**********************************************************************
/* Versioned
/**********************************************************************
*/
@Override
public Version version() {
return PackageVersion.VERSION;
}
/*
/**********************************************************************
/* Overridden output state handling methods
/**********************************************************************
*/
@Override
public final TokenStreamContext streamWriteContext() { return _streamWriteContext; }
@Override
public final Object currentValue() {
return _streamWriteContext.currentValue();
}
@Override
public final void assignCurrentValue(Object v) {
_streamWriteContext.assignCurrentValue(v);
}
/*
/**********************************************************************
/* Overridden methods, configuration
/**********************************************************************
*/
@Override
public Object streamWriteOutputTarget() {
return _writer.getOutputTarget();
}
/**
* NOTE: while this method will return some information on amount of data buffered, it
* may be an incomplete view as some buffering happens at a higher level, as not-yet-serialized
* values.
*/
@Override
public int streamWriteOutputBuffered() {
return _writer.getOutputBuffered();
}
@Override
public CsvGenerator setCharacterEscapes(CharacterEscapes esc) {
if (esc != null) {
_writer.setOutputEscapes(esc.getEscapeCodesForAscii());
}
return this;
}
@Override
public CharacterEscapes getCharacterEscapes() {
// Not really true but... we have no access to full original escapes
return null;
}
@Override
public PrettyPrinter getPrettyPrinter() {
return null;
}
/*
/**********************************************************************
/* Public API, capability introspection methods
/**********************************************************************
*/
@Override
public boolean canOmitProperties() {
// Nope: CSV requires at least a placeholder
return false;
}
@Override // @since 2.12
public JacksonFeatureSet<StreamWriteCapability> streamWriteCapabilities() {
return DEFAULT_TEXTUAL_WRITE_CAPABILITIES;
}
/*
/**********************************************************************
/* Overridden methods; writing property names
/**********************************************************************
*/
/* And then methods overridden to make final, streamline some
* aspects...
*/
@Override
public JsonGenerator writeName(String name) throws JacksonException
{
if (!_streamWriteContext.writeName(name)) {
_reportError("Cannot write a property name, expecting a value");
}
_writeFieldName(name);
return this;
}
@Override
public JsonGenerator writePropertyId(long id) throws JacksonException {
// 15-Aug-2019, tatu: should be improved to avoid String generation
return writeName(Long.toString(id));
}
@Override
public JsonGenerator writeName(SerializableString name) throws JacksonException
{
// Object is a value, need to verify it's allowed
if (!_streamWriteContext.writeName(name.getValue())) {
_reportError("Cannot write a property name, expecting a value");
}
_writeFieldName(name.getValue());
return this;
}
private final void _writeFieldName(String name) throws JacksonException
{
// just find the matching index -- must have schema for that
if (_schema == null) {
// not a low-level error, so:
_reportCsvWriteError("Unrecognized column '"+name+"', can not resolve without CsvSchema");
}
if (_skipWithin != null) { // new in 2.7
_skipValue = true;
_nextColumnByName = -1;
_nextColumnDecorator = null;
return;
}
// note: we are likely to get next column name, so pass it as hint
CsvSchema.Column col = _schema.column(name, _nextColumnByName+1);
if (col == null) {
_nextColumnByName = -1;
_nextColumnDecorator = null;
if (isEnabled(StreamWriteFeature.IGNORE_UNKNOWN)) {
_skipValue = true;
return;
}
// not a low-level error, so:
_reportCsvWriteError("Unrecognized column '"+name+"': known columns: "+_schema.getColumnDesc());
}
_skipValue = false;
// and all we do is just note index to use for following value write
_nextColumnByName = col.getIndex();
_nextColumnDecorator = col.getValueDecorator();
}
/*
/**********************************************************************
/* Extended API, configuration
/**********************************************************************
*/
public final boolean isEnabled(CsvWriteFeature f) {
return (_formatFeatures & f.getMask()) != 0;
}
/*
/**********************************************************************
/* Public API: low-level I/O
/**********************************************************************
*/
@Override
public final void flush() {
try {
_writer.flush(isEnabled(StreamWriteFeature.FLUSH_PASSED_TO_STREAM));
} catch (IOException e) {
throw _wrapIOFailure(e);
}
}
@Override
public void close()
{
if (!isClosed()) {
// Let's mark row as closed, if we had any...
finishRow();
// Write the header if necessary, occurs when no rows written
if (_handleFirstLine) {
_handleFirstLine();
}
}
super.close();
}
@Override
protected void _closeInput() throws IOException
{
_writer.close(_ioContext.isResourceManaged() || isEnabled(StreamWriteFeature.AUTO_CLOSE_TARGET),
isEnabled(StreamWriteFeature.FLUSH_PASSED_TO_STREAM));
}
/*
/**********************************************************************
/* Public API: structural output
/**********************************************************************
*/
@Override
public JsonGenerator writeStartArray() throws JacksonException
{
_verifyValueWrite("start an array");
_justFinishedRow = false; // Clear flag when starting new array
// Ok to create root-level array to contain Objects/Arrays, but
// can not nest arrays in objects
if (_streamWriteContext.inObject()) {
if ((_skipWithin == null)
&& _skipValue && isEnabled(StreamWriteFeature.IGNORE_UNKNOWN)) {
_skipWithin = _streamWriteContext;
} else if (!_skipValue) {
// First: column may have its own separator
String sep;
if (_nextColumnByName >= 0) {
CsvSchema.Column col = _schema.column(_nextColumnByName);
sep = col.isArray() ? col.getArrayElementSeparator() : CsvSchema.NO_ARRAY_ELEMENT_SEPARATOR;
} else {
sep = CsvSchema.NO_ARRAY_ELEMENT_SEPARATOR;
}
if (sep.isEmpty()) {
if (!_schema.hasArrayElementSeparator()) {
_reportError("CSV generator does not support Array values for properties without setting 'arrayElementSeparator' in schema");
}
sep = _schema.getArrayElementSeparator();
}
_arraySeparator = sep;
if (_arrayContents == null) {
_arrayContents = new StringBuilder();
} else {
_arrayContents.setLength(0);
}
_arrayElements = 0;
}
} else {
if (!_arraySeparator.isEmpty()) {
// also: no nested arrays, yet
_reportError("CSV generator does not support nested Array values");
}
}
_streamWriteContext = _streamWriteContext.createChildArrayContext(null);
streamWriteConstraints().validateNestingDepth(_streamWriteContext.getNestingDepth());
// and that's about it, really
return this;
}
@Override
public JsonGenerator writeStartArray(Object currValue) throws JacksonException {
writeStartArray();
assignCurrentValue(currValue);
return this;
}
@Override
public JsonGenerator writeEndArray() throws JacksonException
{
if (!_streamWriteContext.inArray()) {
_reportError("Current context not Array but "+_streamWriteContext.typeDesc());
}
_streamWriteContext = _streamWriteContext.getParent();
// 14-Dec-2015, tatu: To complete skipping of ignored structured value, need this:
if (_skipWithin != null) {
if (_streamWriteContext == _skipWithin) {
_skipWithin = null;
}
return this;
}
if (!_arraySeparator.isEmpty()) {
String value = _arrayContents.toString();
// 26-Aug-2024, tatu: [dataformats-text#495] Decorations!
if (_nextColumnDecorator != null) {
value = _nextColumnDecorator.decorateValue(this, value);
}
_arraySeparator = CsvSchema.NO_ARRAY_ELEMENT_SEPARATOR;
_writer.write(_columnIndex(), value);
}
// 20-Nov-2014, tatu: When doing "untyped"/"raw" output, this means that row
// is now done. But not if writing such an array property, so:
if (!_streamWriteContext.inObject()) {
finishRow();
}
return this;
}
@Override
public JsonGenerator writeStartObject() throws JacksonException
{
_verifyValueWrite("start an object");
_justFinishedRow = false;
// No nesting for objects; can write Objects inside logical root-level arrays.
// 14-Dec-2015, tatu: ... except, should be fine if we are ignoring the property
if (_streamWriteContext.inObject() ||
// 07-Nov-2017, tatu: But we may actually be nested indirectly; so check
(_streamWriteContext.inArray() && !_streamWriteContext.getParent().inRoot())) {
if (_skipWithin == null) { // new in 2.7
if (_skipValue && isEnabled(StreamWriteFeature.IGNORE_UNKNOWN)) {
_skipWithin = _streamWriteContext;
} else {
_reportCsvWriteError("CSV generator does not support Object values for properties (nested Objects)");
}
}
}
_streamWriteContext = _streamWriteContext.createChildObjectContext(null);
streamWriteConstraints().validateNestingDepth(_streamWriteContext.getNestingDepth());
return this;
}
@Override
public JsonGenerator writeStartObject(Object currValue) throws JacksonException {
writeStartObject();
assignCurrentValue(currValue);
return this;
}
@Override
public JsonGenerator writeEndObject() throws JacksonException
{
if (!_streamWriteContext.inObject()) {
_reportError("Current context not Object but "+_streamWriteContext.typeDesc());
}
_streamWriteContext = _streamWriteContext.getParent();
// 14-Dec-2015, tatu: To complete skipping of ignored structured value, need this:
if (_skipWithin != null) {
if (_streamWriteContext == _skipWithin) {
_skipWithin = null;
}
return this;
}
// not 100% fool-proof, but chances are row should be done now
finishRow();
return this;
}
/*
/**********************************************************************
/* Output method implementations, textual
/**********************************************************************
*/
@Override
public JsonGenerator writeString(String text) throws JacksonException
{
if (text == null) {
return writeNull();
}
_verifyValueWrite("write String value");
if (!_skipValue) {
if (!_arraySeparator.isEmpty()) {
_addToArray(text);
} else {
// 26-Aug-2024, tatu: [dataformats-text#495] Decorations!
if (_nextColumnDecorator != null) {
text = _nextColumnDecorator.decorateValue(this, text);
}
_writer.write(_columnIndex(), text);
}
}
return this;
}
@Override
public JsonGenerator writeString(char[] text, int offset, int len) throws JacksonException
{
_verifyValueWrite("write String value");
if (!_skipValue) {
if (!_arraySeparator.isEmpty()) {
_addToArray(new String(text, offset, len));
// 26-Aug-2024, tatu: [dataformats-text#495] Decorations!
} else if (_nextColumnDecorator != null) {
String str = new String(text, offset, len);
_writer.write(_columnIndex(),
_nextColumnDecorator.decorateValue(this, str));
} else {
_writer.write(_columnIndex(), text, offset, len);
}
}
return this;
}
@Override
public JsonGenerator writeString(SerializableString sstr) throws JacksonException
{
_verifyValueWrite("write String value");
if (!_skipValue) {
if (!_arraySeparator.isEmpty()) {
_addToArray(sstr.getValue());
} else {
// 26-Aug-2024, tatu: [dataformats-text#495] Decorations!
String text = sstr.getValue();
if (_nextColumnDecorator != null) {
text = _nextColumnDecorator.decorateValue(this, text);
}
_writer.write(_columnIndex(), text);
}
}
return this;
}
@Override
public JsonGenerator writeRawUTF8String(byte[] text, int offset, int len) throws JacksonException {
return _reportUnsupportedOperation();
}
@Override
public JsonGenerator writeUTF8String(byte[] text, int offset, int len) throws JacksonException {
return writeString(new String(text, offset, len, StandardCharsets.UTF_8));
}
/*
/**********************************************************************
/* Output method implementations, unprocessed ("raw")
/**********************************************************************
*/
@Override
public JsonGenerator writeRaw(String text) throws JacksonException {
_writer.writeRaw(text);
return this;
}
@Override
public JsonGenerator writeRaw(String text, int offset, int len) throws JacksonException {
_writer.writeRaw(text, offset, len);
return this;
}
@Override
public JsonGenerator writeRaw(char[] text, int offset, int len) throws JacksonException {
_writer.writeRaw(text, offset, len);
return this;
}
@Override
public JsonGenerator writeRaw(char c) throws JacksonException {
_writer.writeRaw(c);
return this;
}
@Override
public JsonGenerator writeRawValue(String text) throws JacksonException {
_verifyValueWrite("write Raw value");
if (!_skipValue) {
// NOTE: ignore array stuff
_writer.writeNonEscaped(_columnIndex(), text);
}
return this;
}
@Override
public JsonGenerator writeRawValue(String text, int offset, int len) throws JacksonException {
_verifyValueWrite("write Raw value");
if (!_skipValue) {
// NOTE: ignore array stuff
_writer.writeNonEscaped(_columnIndex(), text.substring(offset, offset+len));
}
return this;
}
@Override
public JsonGenerator writeRawValue(char[] text, int offset, int len) throws JacksonException {
_verifyValueWrite("write Raw value");
if (!_skipValue) {
// NOTE: ignore array stuff
_writer.writeNonEscaped(_columnIndex(), new String(text, offset, len));
}
return this;
}
/*
/**********************************************************************
/* Output method implementations, base64-encoded binary
/**********************************************************************
*/
@Override
public JsonGenerator writeBinary(Base64Variant b64variant, byte[] data, int offset, int len)
throws JacksonException
{
if (data == null) {
return writeNull();
}
_verifyValueWrite("write Binary value");
if (!_skipValue) {
// ok, better just Base64 encode as a String...
if (offset > 0 || (offset+len) != data.length) {
data = Arrays.copyOfRange(data, offset, offset+len);
}
String encoded = b64variant.encode(data);
if (!_arraySeparator.isEmpty()) {
_addToArray(encoded);
} else {
// 26-Aug-2024, tatu: [dataformats-text#495] Decorations!
if (_nextColumnDecorator != null) {
encoded = _nextColumnDecorator.decorateValue(this, encoded);
}
_writer.write(_columnIndex(), encoded);
}
}
return this;
}
/*
/**********************************************************************
/* Output method implementations, primitive
/**********************************************************************
*/
@Override
public JsonGenerator writeBoolean(boolean state) throws JacksonException
{
_verifyValueWrite("write boolean value");
if (!_skipValue) {
if (!_arraySeparator.isEmpty()) {
_addToArray(state ? "true" : "false");
} else {
// 26-Aug-2024, tatu: [dataformats-text#495] Decorations!
if (_nextColumnDecorator != null) {
String text = _nextColumnDecorator.decorateValue(this, state ? "true" : "false");
_writer.write(_columnIndex(), text);
} else {
_writer.write(_columnIndex(), state);
}
}
}
return this;
}
@Override
public JsonGenerator writeNull() throws JacksonException
{
_verifyValueWrite("write null value");
if (!_skipValue) {
if (!_arraySeparator.isEmpty()) {
_addToArray(_schema.getNullValueOrEmpty());
} else if (_streamWriteContext.inObject()) {
// 26-Aug-2024, tatu: [dataformats-text#495] Decorations?
if (_nextColumnDecorator != null) {
String nvl = _nextColumnDecorator.decorateNull(this);
if (nvl != null) {
_writer.write(_columnIndex(), nvl);
return this;
}
}
_writer.writeNull(_columnIndex());
} else if (_streamWriteContext.inArray()) {
// [dataformat-csv#106]: Need to make sure we don't swallow nulls in arrays either
// 04-Jan-2016, tatu: but check for case of array-wrapping, in which case null stands for absence
// of Object. In this case, could either add an empty row, or skip -- for now, we'll
// just skip; can change, if so desired, to expose "root null" as empty rows, possibly
// based on either schema property, or CsvGenerator.Feature.
// Note: if nulls are to be written that way, would need to call `finishRow()` right after `writeNull()`
// [dataformats-text#10]: When we have a schema and we haven't just finished a row,
// it means we're inside an array-as-row (like Object[]), so null is a column value
boolean writeNullValue = !_streamWriteContext.getParent().inRoot()
|| (_schema.size() > 0 && !_justFinishedRow);
if (writeNullValue) {
// 26-Aug-2024, tatu: [dataformats-text#495] Decorations?
if (_nextColumnDecorator != null) {
String nvl = _nextColumnDecorator.decorateNull(this);
if (nvl != null) {
_writer.write(_columnIndex(), nvl);
return this;
}
}
_writer.writeNull(_columnIndex());
}
// ... so, for "root-level nulls" (with or without array-wrapping), we would do:
/*
_writer.writeNull(_columnIndex());
finishRow();
*/
}
}
return this;
}
@Override
public JsonGenerator writeNumber(short v) throws JacksonException {
return writeNumber((int) v);
}
@Override
public JsonGenerator writeNumber(int v) throws JacksonException
{
_verifyValueWrite("write number");
if (!_skipValue) {
if (!_arraySeparator.isEmpty()) {
_addToArray(String.valueOf(v));
// 26-Aug-2024, tatu: [dataformats-text#495] Decorations?
} else if (_nextColumnDecorator != null) {
_writer.write(_columnIndex(),
_nextColumnDecorator.decorateValue(this, String.valueOf(v)));
} else {
_writer.write(_columnIndex(), v);
}
}
return this;
}
@Override
public JsonGenerator writeNumber(long v) throws JacksonException
{
// First: maybe 32 bits is enough?
if (v <= MAX_INT_AS_LONG && v >= MIN_INT_AS_LONG) {
return writeNumber((int) v);
}
_verifyValueWrite("write number");
if (!_skipValue) {
if (!_arraySeparator.isEmpty()) {
_addToArray(String.valueOf(v));
// 26-Aug-2024, tatu: [dataformats-text#495] Decorations?
} else if (_nextColumnDecorator != null) {
_writer.write(_columnIndex(),
_nextColumnDecorator.decorateValue(this, String.valueOf(v)));
} else {
_writer.write(_columnIndex(), v);
}
}
return this;
}
@Override
public JsonGenerator writeNumber(BigInteger v) throws JacksonException
{
if (v == null) {
return writeNull();
}
_verifyValueWrite("write number");
if (!_skipValue) {
if (!_arraySeparator.isEmpty()) {
_addToArray(String.valueOf(v));
// 26-Aug-2024, tatu: [dataformats-text#495] Decorations?
} else if (_nextColumnDecorator != null) {
_writer.write(_columnIndex(),
_nextColumnDecorator.decorateValue(this, String.valueOf(v)));
} else {
_writer.write(_columnIndex(), v);
}
}
return this;
}
@Override
public JsonGenerator writeNumber(double v) throws JacksonException
{
_verifyValueWrite("write number");
if (!_skipValue) {
if (!_arraySeparator.isEmpty()) {
_addToArray(String.valueOf(v));
// 26-Aug-2024, tatu: [dataformats-text#495] Decorations?
} else if (_nextColumnDecorator != null) {
_writer.write(_columnIndex(),
_nextColumnDecorator.decorateValue(this, String.valueOf(v)));
} else {
_writer.write(_columnIndex(), v);
}
}
return this;
}
@Override
public JsonGenerator writeNumber(float v) throws JacksonException
{
_verifyValueWrite("write number");
if (!_skipValue) {
if (!_arraySeparator.isEmpty()) {
_addToArray(String.valueOf(v));
// 26-Aug-2024, tatu: [dataformats-text#495] Decorations?
} else if (_nextColumnDecorator != null) {
_writer.write(_columnIndex(),
_nextColumnDecorator.decorateValue(this, String.valueOf(v)));
} else {
_writer.write(_columnIndex(), v);
}
}
return this;
}
@Override
public JsonGenerator writeNumber(BigDecimal v) throws JacksonException
{
if (v == null) {
return writeNull();
}
_verifyValueWrite("write number");
if (!_skipValue) {
boolean plain = isEnabled(StreamWriteFeature.WRITE_BIGDECIMAL_AS_PLAIN);
if (!_arraySeparator.isEmpty()) {
_addToArray(plain ? v.toPlainString() : v.toString());
// 26-Aug-2024, tatu: [dataformats-text#495] Decorations?
} else if (_nextColumnDecorator != null) {
String numStr = plain ? v.toPlainString() : v.toString();
_writer.write(_columnIndex(),
_nextColumnDecorator.decorateValue(this, numStr));
} else {
_writer.write(_columnIndex(), v, plain);
}
}
return this;
}
@Override
public JsonGenerator writeNumber(String encodedValue) throws JacksonException
{
if (encodedValue == null) {
return writeNull();
}
_verifyValueWrite("write number");
if (!_skipValue) {
if (!_arraySeparator.isEmpty()) {
_addToArray(encodedValue);
// 26-Aug-2024, tatu: [dataformats-text#495] Decorations?
} else if (_nextColumnDecorator != null) {
_writer.write(_columnIndex(),
_nextColumnDecorator.decorateValue(this, encodedValue));
} else {
_writer.write(_columnIndex(), encodedValue);
}
}
return this;
}
/*
/**********************************************************************
/* Overrides for property write methods
/**********************************************************************
*/
@Override
public JsonGenerator writeOmittedProperty(String propName) throws JacksonException
{
// Hmmh. Should we require a match? Actually, let's use logic: if property found,
// assumption is we must add a placeholder; if not, we can merely ignore
CsvSchema.Column col = _schema.column(propName);
if (col == null) {
// assumed to have been removed from schema too
} else {
// basically combination of "writeName()" and "writeNull()"
if (!_streamWriteContext.writeName(propName)) {
_reportError("Cannot skip a property, expecting a value");
}
// and all we do is just note index to use for following value write
_nextColumnByName = col.getIndex();
// We can basically copy what 'writeNull()' does...
_verifyValueWrite("skip positional value due to filtering");
_writer.write(_columnIndex(), "");
}
return this;
}
/*
/**********************************************************************
/* Implementations for methods from base class
/**********************************************************************
*/
@Override
protected final void _verifyValueWrite(String typeMsg) throws JacksonException
{
if (!_streamWriteContext.writeValue()) {
_reportError("Cannot "+typeMsg+", expecting a property name");
}
if (_handleFirstLine) {
_handleFirstLine();
}
}
@Override
protected void _releaseBuffers() {
_writer._releaseBuffers();
}
/*
/**********************************************************************
/* Internal methods, error reporting
/**********************************************************************
*/
/**
* Method called when there is a problem related to mapping data
* (compared to a low-level generation); if so, should be surfaced
* as
*/
protected void _reportCsvWriteError(String msg) throws JacksonException {
throw CsvWriteException.from(this, msg, _schema);
}
/*
/**********************************************************************
/* Internal methods, other
/**********************************************************************
*/
protected final int _columnIndex()
{
int ix = _nextColumnByName;
if (ix < 0) { // if we had one, remove now
ix = _writer.nextColumnIndex();
}
return ix;
}
/**
* Method called when the current row is complete; typically
* will flush possibly buffered column values, append linefeed
* and reset state appropriately.
*/
protected void finishRow() throws JacksonException
{
_writer.endRow();
_nextColumnByName = -1;
_justFinishedRow = true;
}
protected void _handleFirstLine() throws JacksonException
{
_handleFirstLine = false;
if (_schema.usesHeader()) {
int count = _schema.size();
if (count == 0) {
_reportCsvWriteError("Schema specified that header line is to be written; but contains no column names");
}
for (CsvSchema.Column column : _schema) {
_writer.writeColumnName(column.getName());
}
_writer.endRow();
}
}
protected void _addToArray(String value) {
if (_arrayElements > 0) {
_arrayContents.append(_arraySeparator);
}
++_arrayElements;
_arrayContents.append(value);
}
protected void _addToArray(char[] value) {
if (_arrayElements > 0) {
_arrayContents.append(_arraySeparator);
}
++_arrayElements;
_arrayContents.append(value);
}
}