diff --git a/csv/src/main/java/com/fasterxml/jackson/dataformat/csv/CsvEscapes.java b/csv/src/main/java/com/fasterxml/jackson/dataformat/csv/CsvEscapes.java new file mode 100644 index 00000000..e8ed8ff8 --- /dev/null +++ b/csv/src/main/java/com/fasterxml/jackson/dataformat/csv/CsvEscapes.java @@ -0,0 +1,99 @@ +package com.fasterxml.jackson.dataformat.csv; + +import java.util.Arrays; + +/** + * @author Petar Tahchiev + * @since 2.0.1 + */ +public class CsvEscapes { + private final static char[] HEX = "0123456789ABCDEF".toCharArray(); + + private final static int UNICODE_ESCAPE = -1; + + private final static int[] sValueEscapes; + static { + final int[] table = new int[256]; + // For values, fewer escapes needed, but most control chars need them + for (int i = 0; i < 32; ++i) { + table[i] = UNICODE_ESCAPE; + // also high-bit ones + table[128+i] = UNICODE_ESCAPE; + } + // also, that one weird character needs escaping: + table[0x7F] = UNICODE_ESCAPE; + + // except for "well-known" ones + table['\t'] = 't'; + table['\r'] = 'r'; + table['\n'] = 'n'; + + // Beyond that, just backslash + table['\\'] = '\\'; + sValueEscapes = table; + } + + private final static int[] sKeyEscapes; + static { + // with keys, start with value escapes, and add the rest + final int[] table = Arrays.copyOf(sValueEscapes, 256); + + // comment line starters (could get by with just start char but whatever) + table['#'] = '#'; + table['!'] = '!'; + // and then equals (and equivalents) that mark end of key + table['='] = '='; + table[':'] = ':'; + // plus space chars are escapes too + table[' '] = ' '; + + sKeyEscapes = table; + } + + public static void appendKey(StringBuilder sb, String key) { + final int end = key.length(); + if (end == 0) { + return; + } + final int[] esc = sKeyEscapes; + // first quick loop for common case of no escapes + int i = 0; + + while (true) { + char c = key.charAt(i); + if ((c > 0xFF) || esc[c] != 0) { + break; + } + sb.append(c); + if (++i == end) { + return; + } + } + _appendWithEscapes(sb, key, esc, i); + } + + private static void _appendWithEscapes(StringBuilder sb, String key, + int[] esc, int i) + { + final int end = key.length(); + do { + char c = key.charAt(i); + int type = (c > 0xFF) ? UNICODE_ESCAPE : esc[c]; + if (type == 0) { + sb.append(c); + continue; + } + if (type == UNICODE_ESCAPE) { + sb.append('\\'); + sb.append('u'); + sb.append(HEX[c >>> 12]); + sb.append(HEX[(c >> 8) & 0xF]); + sb.append(HEX[(c >> 4) & 0xF]); + sb.append(HEX[c & 0xF]); + } else { + sb.append('\\'); + sb.append((char) type); + } + } while (++i < end); + } +} diff --git a/csv/src/main/java/com/fasterxml/jackson/dataformat/csv/CsvGenerator.java b/csv/src/main/java/com/fasterxml/jackson/dataformat/csv/CsvGenerator.java index 710f9bc3..476bb01d 100644 --- a/csv/src/main/java/com/fasterxml/jackson/dataformat/csv/CsvGenerator.java +++ b/csv/src/main/java/com/fasterxml/jackson/dataformat/csv/CsvGenerator.java @@ -138,6 +138,10 @@ private Feature(boolean defaultState) { */ protected int _formatFeatures; + protected final StringBuilder _basePath = new StringBuilder(50); + + protected int _indentLength; + /** * Definition of columns being written, if available. */ @@ -146,6 +150,12 @@ private Feature(boolean defaultState) { // note: can not be final since we may need to re-create it for new schema protected CsvEncoder _writer; + /** + * Current context, in form we can use it (GeneratorBase has + * untyped reference; left as null) + */ + protected CsvWriteContext _writeContext; + /* /********************************************************** /* Output state @@ -201,7 +211,7 @@ private Feature(boolean defaultState) { * * @since 2.7 */ - protected JsonWriteContext _skipWithin; + protected CsvWriteContext _skipWithin; /* /********************************************************** @@ -220,6 +230,7 @@ public CsvGenerator(IOContext ctxt, int jsonFeatures, int csvFeatures, _formatFeatures = csvFeatures; _schema = schema; _writer = new CsvEncoder(ctxt, csvFeatures, out, schema); + _writeContext = CsvWriteContext.createRootContext(); } public CsvGenerator(IOContext ctxt, int jsonFeatures, int csvFeatures, @@ -229,6 +240,7 @@ public CsvGenerator(IOContext ctxt, int jsonFeatures, int csvFeatures, _ioContext = ctxt; _formatFeatures = csvFeatures; _writer = csvWriter; + _writeContext = CsvWriteContext.createRootContext(); } /* @@ -281,12 +293,21 @@ public int getOutputBuffered() { return _writer.getOutputBuffered(); } + @Override + public CsvWriteContext getOutputContext() { + return _writeContext; + } + @Override public void setSchema(FormatSchema schema) { if (schema instanceof CsvSchema) { if (_schema != schema) { _schema = (CsvSchema) schema; + if (_writeContext.inRoot()) { + _basePath.setLength(0); + } + _writer = _writer.withSchema(_schema); } } else { @@ -345,7 +366,7 @@ public boolean canOmitFields() { @Override public final void writeFieldName(String name) throws IOException { - if (_writeContext.writeFieldName(name) == JsonWriteContext.STATUS_EXPECT_VALUE) { + if (!_writeContext.writeFieldName(name)) { _reportError("Can not write a field name, expecting a value"); } _writeFieldName(name); @@ -355,7 +376,7 @@ public final void writeFieldName(String name) throws IOException public final void writeFieldName(SerializableString name) throws IOException { // Object is a value, need to verify it's allowed - if (_writeContext.writeFieldName(name.getValue()) == JsonWriteContext.STATUS_EXPECT_VALUE) { + if (!_writeContext.writeFieldName(name.getValue())) { _reportError("Can not write a field name, expecting a value"); } _writeFieldName(name.getValue()); @@ -364,7 +385,7 @@ public final void writeFieldName(SerializableString name) throws IOException @Override public final void writeStringField(String fieldName, String value) throws IOException { - if (_writeContext.writeFieldName(fieldName) == JsonWriteContext.STATUS_EXPECT_VALUE) { + if (!_writeContext.writeFieldName(fieldName)) { _reportError("Can not write a field name, expecting a value"); } _writeFieldName(fieldName); @@ -378,16 +399,36 @@ private final void _writeFieldName(String name) throws IOException // not a low-level error, so: _reportMappingError("Unrecognized column '"+name+"', can not resolve without CsvSchema"); } - if (_skipWithin != null) { // new in 2.7 - _skipValue = true; - _nextColumnByName = -1; - return; +// if (_skipWithin != null) { // new in 2.7 +// _skipValue = true; +// _nextColumnByName = -1; +// return; +// } + + boolean internal = false; + _writeContext.truncatePath(_basePath); + if (_basePath.length() > _indentLength) { + String sep = _schema.pathSeparator(); + if (!sep.isEmpty()) { + _basePath.append(sep); + internal = true; + } } + // note: we are likely to get next column name, so pass it as hint + CsvEscapes.appendKey(_basePath, name); + + name = _basePath.toString(); + CsvSchema.Column col = _schema.column(name, _nextColumnByName+1); if (col == null) { if (isEnabled(JsonGenerator.Feature.IGNORE_UNKNOWN)) { - _skipValue = true; + if (internal) { + _skipWithin = _writeContext; + _skipValue = true; + } else { + _skipValue = true; + } _nextColumnByName = -1; return; } @@ -397,6 +438,7 @@ private final void _writeFieldName(String name) throws IOException _skipValue = false; // and all we do is just note index to use for following value write _nextColumnByName = col.getIndex(); + } /* @@ -499,7 +541,7 @@ && _skipValue && isEnabled(JsonGenerator.Feature.IGNORE_UNKNOWN)) { _reportError("CSV generator does not support nested Array values"); } } - _writeContext = _writeContext.createChildArrayContext(); + _writeContext = _writeContext.createChildArrayContext(_basePath.length()); // and that's about it, really } @@ -534,32 +576,35 @@ public final void writeStartObject() throws IOException _verifyValueWrite("start an object"); // 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 (_writeContext.inObject() || - // 07-Nov-2017, tatu: But we may actually be nested indirectly; so check - (_writeContext.inArray() && !_writeContext.getParent().inRoot())) { - if (_skipWithin == null) { // new in 2.7 - if (_skipValue && isEnabled(JsonGenerator.Feature.IGNORE_UNKNOWN)) { - _skipWithin = _writeContext; - } else { - _reportMappingError("CSV generator does not support Object values for properties (nested Objects)"); - } - } - } - _writeContext = _writeContext.createChildObjectContext(); +// if (_writeContext.inObject() || +// // 07-Nov-2017, tatu: But we may actually be nested indirectly; so check +// (_writeContext.inArray() && !_writeContext.getParent().inRoot())) { +// if (_skipWithin == null) { // new in 2.7 +// if (_skipValue && isEnabled(JsonGenerator.Feature.IGNORE_UNKNOWN)) { +// _skipWithin = _writeContext; +// } else { +// +// _reportMappingError("CSV generator does not support Object values for properties (nested Objects)"); +// } +// } +// } + _writeContext = _writeContext.createChildObjectContext(_basePath.length()); } @Override public final void writeEndObject() throws IOException { if (!_writeContext.inObject()) { - _reportError("Current context not Object but "+_writeContext.typeDesc()); + _reportError("Current context not Object but " + _writeContext.typeDesc()); } + CsvWriteContext _origContext = _writeContext; _writeContext = _writeContext.getParent(); // 14-Dec-2015, tatu: To complete skipping of ignored structured value, need this: if (_skipWithin != null) { - if (_writeContext == _skipWithin) { + if (_writeContext == _skipWithin || _origContext == _skipWithin) { _skipWithin = null; } + return; } // not 100% fool-proof, but chances are row should be done now @@ -883,7 +928,7 @@ public void writeOmittedField(String fieldName) throws IOException // assumed to have been removed from schema too } else { // basically combination of "writeFieldName()" and "writeNull()" - if (_writeContext.writeFieldName(fieldName) == JsonWriteContext.STATUS_EXPECT_VALUE) { + if (!_writeContext.writeFieldName(fieldName)) { _reportError("Can not skip a field, expecting a value"); } // and all we do is just note index to use for following value write @@ -903,8 +948,7 @@ public void writeOmittedField(String fieldName) throws IOException @Override protected final void _verifyValueWrite(String typeMsg) throws IOException { - int status = _writeContext.writeValue(); - if (status == JsonWriteContext.STATUS_EXPECT_NAME) { + if (!_writeContext.writeValue()) { _reportError("Can not "+typeMsg+", expecting field name"); } if (_handleFirstLine) { @@ -959,6 +1003,8 @@ protected void finishRow() throws IOException { _writer.endRow(); _nextColumnByName = -1; + _skipValue = false; + _basePath.setLength(0); } protected void _handleFirstLine() throws IOException diff --git a/csv/src/main/java/com/fasterxml/jackson/dataformat/csv/CsvSchema.java b/csv/src/main/java/com/fasterxml/jackson/dataformat/csv/CsvSchema.java index 59ef9bc4..16f2be35 100644 --- a/csv/src/main/java/com/fasterxml/jackson/dataformat/csv/CsvSchema.java +++ b/csv/src/main/java/com/fasterxml/jackson/dataformat/csv/CsvSchema.java @@ -1,83 +1,84 @@ - package com.fasterxml.jackson.dataformat.csv; -import java.util.*; - import com.fasterxml.jackson.core.FormatSchema; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.TreeMap; + /** * Simple {@link FormatSchema} sub-type that defines properties of * a CSV document to read or write. * Properties supported currently are: - *
* Note that schemas without any columns are legal, but if no columns * are added, behavior of parser/generator is usually different and * content will be exposed as logical Arrays instead of Objects. - *
- *
* There are 4 ways to create CsvSchema
instances:
- *
withXxx
methods
- * or {@link #rebuild} for creating {@link Builder})
- * null
is used as marker.
@@ -126,7 +136,7 @@ public class CsvSchema
* @since 2.7
*/
public final static String DEFAULT_ANY_PROPERTY_NAME = null;
-
+
public final static char DEFAULT_QUOTE_CHAR = '"';
/**
@@ -135,12 +145,11 @@ public class CsvSchema
* coerce Strings into Java nulls).
* To use automatic coercion on reading, null value must be set explicitly
* to empty String ("").
- *
* NOTE: before 2.6, this value default to empty char[]
; changed
* to Java null in 2.6.
*/
public final static char[] DEFAULT_NULL_VALUE = null;
-
+
/**
* By default, no escape character is used -- this is denoted by
* int value that does not map to a valid character
@@ -154,7 +163,7 @@ public class CsvSchema
/* Constants, other
/**********************************************************************
*/
-
+
protected final static Column[] NO_COLUMNS = new Column[0];
/*
@@ -169,14 +178,12 @@ public class CsvSchema
* {@link com.fasterxml.jackson.core.JsonToken}
* that column values are exposed as.
*/
- public enum ColumnType
- {
+ public enum ColumnType {
/**
* Default type if not explicitly defined; value will
* be presented as VALUE_STRING
by parser,
* that is, no type-inference is performed, and value is
* not trimmed.
- *
* Note that this type allows coercion into array, if higher * level application calls * {@link com.fasterxml.jackson.core.JsonParser#isExpectedStartArrayToken}, @@ -191,7 +198,7 @@ public enum ColumnType * and values are trimmed (leading/trailing white space) */ STRING_OR_LITERAL, - + /** * Value should be a number, but literals "null", "true" and "false" * are also understood, and an empty String is considered null. @@ -213,26 +220,23 @@ public enum ColumnType * or "null", or empty String (equivalent to null). * Values are trimmed (leading/trailing white space). * Values other than indicated above may result in an exception. - * + * * @since 2.5 */ BOOLEAN, - + /** * Value will be a multi-value sequence, separated by array element * separator. Element type itself may be any scalar type (that is, number * or String) and will not be optimized. * Separator may be overridden on per-column basis. - *
* Note that this type is used for generic concept of multiple values, and
* not specifically to match Java arrays: data-binding may match such columns
* to {@link java.util.Collection}s as well, or even other types as necessary.
- *
+ *
* @since 2.5
*/
- ARRAY,
-
- ;
+ ARRAY,;
}
/**
@@ -243,9 +247,11 @@ public static class Column implements java.io.Serializable // since 2.4.3
private static final long serialVersionUID = 1L;
public final static Column PLACEHOLDER = new Column(0, "");
-
+
private final String _name;
+
private final int _index;
+
private final ColumnType _type;
/**
@@ -255,64 +261,70 @@ public static class Column implements java.io.Serializable // since 2.4.3
*/
private final String _arrayElementSeparator;
+ private final String _pathSeparator;
+
/**
* Link to the next column within schema, if one exists;
* null for the last column.
- *
+ *
* @since 2.6
*/
private final Column _next;
-
+
+ public Column(int index, String name, String _pathSeparator) {
+ this(index, name, ColumnType.STRING, "", _pathSeparator);
+ }
+
public Column(int index, String name) {
- this(index, name, ColumnType.STRING, "");
+ this(index, name, ColumnType.STRING, "", DEFAULT_PATH_SEPARATOR);
}
public Column(int index, String name, ColumnType type) {
- this(index, name, type, "");
+ this(index, name, type, "", DEFAULT_PATH_SEPARATOR);
}
/**
* @deprecated use variant where `arrayElementSep` is
- * NOTE: this method requires linear scan over existing columns
+ * * NOTE: this method requires linear scan over existing columns
* so it may be more efficient to use other types of lookups if
* available (for example, {@link CsvSchema#column(String)} has a
* hash lookup to use).
@@ -639,9 +699,8 @@ public Builder setUseHeader(boolean b) {
* is set, this setting will reorder the columns defined in this
* schema to match the order set by the header.
*
- * @param b Enable / Disable this setting
- * @return This Builder instance
- *
+ * @param b Enable / Disable this setting
+ * @return This Builder instance
* @since 2.7
*/
public Builder setReorderColumns(boolean b) {
@@ -656,7 +715,6 @@ public Builder setReorderColumns(boolean b) {
*
* @param b Enable / Disable this setting
* @return This Builder instance
- *
* @since 2.7
*/
public Builder setStrictHeaders(boolean b) {
@@ -678,14 +736,14 @@ public Builder setSkipFirstDataRow(boolean b) {
* Method for specifying whether Schema should indicate that
* "hash comments" (lines where the first non-whitespace character
* is '#') are allowed; if so, they will be skipped without processing.
- *
+ *
* @since 2.5
*/
public Builder setAllowComments(boolean b) {
_feature(ENCODING_FEATURE_ALLOW_COMMENTS, b);
return this;
}
-
+
protected final void _feature(int feature, boolean state) {
_encodingFeatures = state ? (_encodingFeatures | feature) : (_encodingFeatures & ~feature);
}
@@ -739,7 +797,6 @@ public Builder disableArrayElementSeparator() {
return this;
}
-
/**
* Method for specifying character used for optional quoting
* of values.
@@ -757,7 +814,7 @@ public Builder disableQuoteChar() {
_quoteChar = -1;
return this;
}
-
+
/**
* Method for specifying character used for optional escaping
* of characters in quoted String values.
@@ -776,7 +833,7 @@ public Builder disableEscapeChar() {
_escapeChar = -1;
return this;
}
-
+
public Builder setLineSeparator(String lf) {
_lineSeparator = lf.toCharArray();
return this;
@@ -795,19 +852,16 @@ public Builder setNullValue(char[] nvl) {
_nullValue = nvl;
return this;
}
-
- public CsvSchema build()
- {
+
+ public CsvSchema build() {
Column[] cols = _columns.toArray(new Column[_columns.size()]);
- return new CsvSchema(cols, _encodingFeatures,
- _columnSeparator, _quoteChar, _escapeChar,
- _lineSeparator, _arrayElementSeparator,
- _nullValue, _anyPropertyName);
+ return new CsvSchema(cols, _encodingFeatures, _columnSeparator, _quoteChar, _escapeChar, _lineSeparator, _arrayElementSeparator, _nullValue,
+ _anyPropertyName);
}
protected void _checkIndex(int index) {
if (index < 0 || index >= _columns.size()) {
- throw new IllegalArgumentException("Illegal index "+index+"; only got "+_columns.size()+" columns");
+ throw new IllegalArgumentException("Illegal index " + index + "; only got " + _columns.size() + " columns");
}
}
}
@@ -817,30 +871,38 @@ protected void _checkIndex(int index) {
/* Configuration, construction
/**********************************************************************
*/
-
+
/**
* Column definitions, needed for optional header and/or mapping
* of field names to column positions.
*/
protected final Column[] _columns;
-
- protected final Map
* As with all `withXxx()` methods this method never modifies `this` but either
* returns it unmodified (if no new columns found from `toAppend`), or constructs
* a new instance and returns that.
@@ -1238,29 +1318,26 @@ public CsvSchema withColumnsFrom(CsvSchema toAppend) {
* @since 2.7
*/
public CsvSchema withAnyPropertyName(String name) {
- return new CsvSchema(_columns, _features,
- _columnSeparator, _quoteChar, _escapeChar, _lineSeparator, _arrayElementSeparator,
- _nullValue, _columnsByName, name);
+ return new CsvSchema(_columns, _features, _columnSeparator, _quoteChar, _escapeChar, _lineSeparator, _arrayElementSeparator, _nullValue, _columnsByName,
+ name);
}
-
+
/**
* Mutant factory method that will construct a new instance in which columns
* are sorted based on names given as argument. Columns not listed in argument
* will be sorted after those within list, using existing ordering.
- *
* For example, schema that has columns:
- *String
*/
@Deprecated // in 2.7; remove from 2.8
- public Column(int index, String name, ColumnType type, int arrayElementSep) {
- this(index, name, type, (arrayElementSep < 0) ? NO_ARRAY_ELEMENT_SEPARATOR : Character.toString((char) arrayElementSep));
+ public Column(int index, String name, ColumnType type, int arrayElementSep, String pathSeparator) {
+ this(index, name, type, (arrayElementSep < 0) ? NO_ARRAY_ELEMENT_SEPARATOR : Character.toString((char) arrayElementSep), pathSeparator);
}
- public Column(int index, String name, ColumnType type, String arrayElementSep)
- {
+ public Column(int index, String name, ColumnType type, String arrayElementSep, String pathSeparator) {
_index = index;
_name = name;
_type = type;
_arrayElementSeparator = _validArrayElementSeparator(arrayElementSep);
_next = null;
+ _pathSeparator = pathSeparator;
}
public Column(Column src, Column next) {
this(src, src._index, next);
}
- protected Column(Column src, int index, Column next)
- {
+ protected Column(Column src, int index, Column next) {
_index = index;
_name = src._name;
_type = src._type;
_arrayElementSeparator = src._arrayElementSeparator;
_next = next;
+ _pathSeparator = src._pathSeparator;
}
-
+
public Column withName(String newName) {
if (_name == newName) {
return this;
}
- return new Column(_index, newName, _type, _arrayElementSeparator);
+ return new Column(_index, newName, _type, _arrayElementSeparator, _pathSeparator);
}
public Column withType(ColumnType newType) {
if (newType == _type) {
return this;
}
- return new Column(_index, _name, newType, _arrayElementSeparator);
+ return new Column(_index, _name, newType, _arrayElementSeparator, _pathSeparator);
}
/**
@@ -328,7 +340,14 @@ public Column withArrayElementSeparator(String separator) {
if (_arrayElementSeparator.equals(sep)) {
return this;
}
- return new Column(_index, _name, _type, sep);
+ return new Column(_index, _name, _type, sep, _pathSeparator);
+ }
+
+ public Column withPathSeparator(String separator) {
+ if (_pathSeparator.equals(separator)) {
+ return this;
+ }
+ return new Column(_index, _name, _type, _arrayElementSeparator, separator);
}
public Column withNext(Column next) {
@@ -347,12 +366,22 @@ public Column withNext(int index, Column next) {
}
return new Column(this, index, next);
}
-
- public int getIndex() { return _index; }
- public String getName() { return _name; }
- public ColumnType getType() { return _type; }
- public Column getNext() { return _next; }
+ public int getIndex() {
+ return _index;
+ }
+
+ public String getName() {
+ return _name;
+ }
+
+ public ColumnType getType() {
+ return _type;
+ }
+
+ public Column getNext() {
+ return _next;
+ }
/**
* Access that returns same as {@link #getNext} iff name of that
@@ -366,37 +395,44 @@ public Column getNextWithName(String name) {
}
public boolean hasName(String n) {
- return (_name == n) || _name.equals(n);
+ return (_name == n) || _name.equals(n) || (_name.contains(getPathSeparator()) && _name.substring(0, _name.indexOf(getPathSeparator())).equals(n));
}
-
+
/**
* @since 2.5
*/
- public String getArrayElementSeparator() { return _arrayElementSeparator; }
+ public String getArrayElementSeparator() {
+ return _arrayElementSeparator;
+ }
+
+ public String getPathSeparator() {
+ return _pathSeparator;
+ }
public boolean isArray() {
return (_type == ColumnType.ARRAY);
}
}
-
+
/**
* Class used for building {@link CsvSchema} instances.
*/
- public static class Builder
- {
+ public static class Builder {
protected final ArrayListwithXxx()
methods.
*/
- protected CsvSchema(Column[] columns, int features,
- char columnSeparator, int quoteChar, int escapeChar,
- char[] lineSeparator, String arrayElementSeparator,
- char[] nullValue,
- MapsortedBy()
methods.
*/
- protected CsvSchema(CsvSchema base, Column[] columns)
- {
+ protected CsvSchema(CsvSchema base, Column[] columns) {
_columns = _link(columns);
_features = base._features;
_columnSeparator = base._columnSeparator;
@@ -952,16 +1003,16 @@ protected CsvSchema(CsvSchema base, Column[] columns)
if (_columns.length == 0) {
_columnsByName = Collections.emptyMap();
} else {
- _columnsByName = new HashMap
+ *
*
*/
public static CsvSchema emptySchema() {
return builder().build();
}
-
+
/**
* Helper method for constructing Builder that can be used to create modified
* schema.
@@ -1033,7 +1083,7 @@ public Builder rebuild() {
/* Mutant factories
/**********************************************************************
*/
-
+
public CsvSchema withUseHeader(boolean state) {
return _withFeature(ENCODING_FEATURE_USE_HEADER, state);
}
@@ -1042,9 +1092,9 @@ public CsvSchema withUseHeader(boolean state) {
* Returns a clone of this instance by changing or setting the
* column reordering flag
*
- * @param state New value for setting
- * @return A copy of itself, ensuring the setting for
- * the column reordering feature.
+ * @param state New value for setting
+ * @return A copy of itself, ensuring the setting for
+ * the column reordering feature.
* @since 2.7
*/
public CsvSchema withColumnReordering(boolean state) {
@@ -1055,9 +1105,9 @@ public CsvSchema withColumnReordering(boolean state) {
* Returns a clone of this instance by changing or setting the
* strict headers flag
*
- * @param state New value for setting
- * @return A copy of itself, ensuring the setting for
- * the strict headers feature.
+ * @param state New value for setting
+ * @return A copy of itself, ensuring the setting for
+ * the strict headers feature.
* @since 2.7
*/
public CsvSchema withStrictHeaders(boolean state) {
@@ -1087,7 +1137,7 @@ public CsvSchema withSkipFirstDataRow(boolean state) {
/**
* Method to indicate whether "hash comments" are allowed
* for document described by this schema.
- *
+ *
* @since 2.5
*/
public CsvSchema withAllowComments(boolean state) {
@@ -1097,7 +1147,7 @@ public CsvSchema withAllowComments(boolean state) {
/**
* Method to indicate that "hash comments" ARE allowed
* for document described by this schema.
- *
+ *
* @since 2.5
*/
public CsvSchema withComments() {
@@ -1107,7 +1157,7 @@ public CsvSchema withComments() {
/**
* Method to indicate that "hash comments" are NOT allowed for document
* described by this schema.
- *
+ *
* @since 2.5
*/
public CsvSchema withoutComments() {
@@ -1120,38 +1170,74 @@ protected CsvSchema _withFeature(int feature, boolean state) {
}
public CsvSchema withColumnSeparator(char sep) {
- return (_columnSeparator == sep) ? this :
- new CsvSchema(_columns, _features,
- sep, _quoteChar, _escapeChar, _lineSeparator, _arrayElementSeparator,
- _nullValue, _columnsByName, _anyPropertyName);
+ return (_columnSeparator == sep)
+ ? this
+ : new CsvSchema(_columns, _features, sep, _quoteChar, _escapeChar, _lineSeparator, _arrayElementSeparator, _nullValue, _columnsByName,
+ _anyPropertyName);
}
public CsvSchema withQuoteChar(char c) {
- return (_quoteChar == c) ? this :
- new CsvSchema(_columns, _features,
- _columnSeparator, c, _escapeChar, _lineSeparator,_arrayElementSeparator,
- _nullValue, _columnsByName, _anyPropertyName);
+ return (_quoteChar == c)
+ ? this
+ : new CsvSchema(_columns, _features, _columnSeparator, c, _escapeChar, _lineSeparator, _arrayElementSeparator, _nullValue, _columnsByName,
+ _anyPropertyName);
}
public CsvSchema withoutQuoteChar() {
- return (_quoteChar == -1) ? this :
- new CsvSchema(_columns, _features,
- _columnSeparator, -1, _escapeChar, _lineSeparator, _arrayElementSeparator,
- _nullValue, _columnsByName, _anyPropertyName);
+ return (_quoteChar == -1)
+ ? this
+ : new CsvSchema(_columns, _features, _columnSeparator, -1, _escapeChar, _lineSeparator, _arrayElementSeparator, _nullValue, _columnsByName,
+ _anyPropertyName);
}
public CsvSchema withEscapeChar(char c) {
- return (_escapeChar == c) ? this
- : new CsvSchema(_columns, _features,
- _columnSeparator, _quoteChar, c, _lineSeparator, _arrayElementSeparator,
- _nullValue, _columnsByName, _anyPropertyName);
+ return (_escapeChar == c)
+ ? this
+ : new CsvSchema(_columns, _features, _columnSeparator, _quoteChar, c, _lineSeparator, _arrayElementSeparator, _nullValue, _columnsByName,
+ _anyPropertyName);
}
public CsvSchema withoutEscapeChar() {
- return (_escapeChar == -1) ? this
- : new CsvSchema(_columns, _features,
- _columnSeparator, _quoteChar, -1, _lineSeparator, _arrayElementSeparator,
- _nullValue, _columnsByName, _anyPropertyName);
+ return (_escapeChar == -1)
+ ? this
+ : new CsvSchema(_columns, _features, _columnSeparator, _quoteChar, -1, _lineSeparator, _arrayElementSeparator, _nullValue, _columnsByName,
+ _anyPropertyName);
+ }
+
+ /**
+ * Mutant factory method for constructing a new instance with
+ * specified path separator; default being comma (".").
+ * Note that setting separator to `null` or empty String will
+ * basically disable handling of nesting, similar to
+ * calling {@link #withoutPathSeparator}.
+ */
+ public CsvSchema withPathSeparator(String v) {
+ if (v == null) {
+ v = "";
+ }
+ if (_equals(v, _pathSeparator)) {
+ return this;
+ }
+ CsvSchema s = new CsvSchema(_columns, _features, _columnSeparator, _quoteChar, -1, _lineSeparator, _arrayElementSeparator, _nullValue, _columnsByName,
+ _anyPropertyName);
+ s._pathSeparator = v;
+ return s;
+ }
+
+ /**
+ * Mutant factory method for constructing a new instance that
+ * specifies that no "path splitting" is to be done: this is
+ * similar to default behavior of {@link java.util.Properties}
+ * in which keys are full Strings and there is no nesting of values.
+ */
+ public CsvSchema withoutPathSeparator() {
+ if ("".equals(_pathSeparator)) {
+ return this;
+ }
+ CsvSchema s = new CsvSchema(_columns, _features, _columnSeparator, _quoteChar, -1, _lineSeparator, _arrayElementSeparator, _nullValue, _columnsByName,
+ _anyPropertyName);
+ s._pathSeparator = "";
+ return s;
}
/**
@@ -1160,7 +1246,7 @@ public CsvSchema withoutEscapeChar() {
*/
@Deprecated // in 2.7; remove in 2.8
public CsvSchema withArrayElementSeparator(char c) {
- return withArrayElementSeparator( Character.toString(c));
+ return withArrayElementSeparator(Character.toString(c));
}
/**
@@ -1168,43 +1254,38 @@ public CsvSchema withArrayElementSeparator(char c) {
*/
public CsvSchema withArrayElementSeparator(String separator) {
String sep = separator == null ? "" : separator;
- return (_arrayElementSeparator.equals(sep)) ? this : new CsvSchema(_columns, _features,
- _columnSeparator, _quoteChar, _escapeChar, _lineSeparator, separator,
- _nullValue, _columnsByName, _anyPropertyName);
+ return (_arrayElementSeparator.equals(sep))
+ ? this
+ : new CsvSchema(_columns, _features, _columnSeparator, _quoteChar, _escapeChar, _lineSeparator, separator, _nullValue, _columnsByName,
+ _anyPropertyName);
}
-
/**
* @since 2.5
*/
public CsvSchema withoutArrayElementSeparator() {
- return (_arrayElementSeparator.isEmpty()) ? this
- : new CsvSchema(_columns, _features,
- _columnSeparator, _quoteChar, _escapeChar, _lineSeparator, "",
- _nullValue, _columnsByName, _anyPropertyName);
+ return (_arrayElementSeparator.isEmpty())
+ ? this
+ : new CsvSchema(_columns, _features, _columnSeparator, _quoteChar, _escapeChar, _lineSeparator, "", _nullValue, _columnsByName,
+ _anyPropertyName);
}
-
+
public CsvSchema withLineSeparator(String sep) {
- return new CsvSchema(_columns, _features,
- _columnSeparator, _quoteChar, _escapeChar, sep.toCharArray(),
- _arrayElementSeparator, _nullValue, _columnsByName, _anyPropertyName);
+ return new CsvSchema(_columns, _features, _columnSeparator, _quoteChar, _escapeChar, sep.toCharArray(), _arrayElementSeparator, _nullValue,
+ _columnsByName, _anyPropertyName);
}
/**
* @since 2.5
*/
public CsvSchema withNullValue(String nvl) {
- return new CsvSchema(_columns, _features,
- _columnSeparator, _quoteChar, _escapeChar, _lineSeparator,
- _arrayElementSeparator,
- (nvl == null) ? null : nvl.toCharArray(),
- _columnsByName, _anyPropertyName);
+ return new CsvSchema(_columns, _features, _columnSeparator, _quoteChar, _escapeChar, _lineSeparator, _arrayElementSeparator,
+ (nvl == null) ? null : nvl.toCharArray(), _columnsByName, _anyPropertyName);
}
public CsvSchema withoutColumns() {
- return new CsvSchema(NO_COLUMNS, _features,
- _columnSeparator, _quoteChar, _escapeChar, _lineSeparator, _arrayElementSeparator,
- _nullValue, _columnsByName, _anyPropertyName);
+ return new CsvSchema(NO_COLUMNS, _features, _columnSeparator, _quoteChar, _escapeChar, _lineSeparator, _arrayElementSeparator, _nullValue,
+ _columnsByName, _anyPropertyName);
}
/**
@@ -1212,7 +1293,6 @@ public CsvSchema withoutColumns() {
* from `toAppend`, starting with columns of this instance, and ignoring
* duplicates (if any) from argument `toAppend`.
* All settings aside from column sets are copied from `this` instance.
- *"a", "d", "c", "b"
- *
+ * "a", "d", "c", "b"
+ *
* ordered with schema.sortedBy("a", "b");
* would result instance that columns in order:
- *"a", "b", "d", "c"
- *
- *
+ * "a", "b", "d", "c"
+ *
+ *
* @since 2.4
*/
- public CsvSchema sortedBy(String... columnNames)
- {
- LinkedHashMapnull
,
@@ -1365,25 +1474,34 @@ public String getNullValueString() {
return str;
}
- public boolean usesQuoteChar() { return _quoteChar >= 0; }
- public boolean usesEscapeChar() { return _escapeChar >= 0; }
+ public boolean usesQuoteChar() {
+ return _quoteChar >= 0;
+ }
+
+ public boolean usesEscapeChar() {
+ return _escapeChar >= 0;
+ }
/**
* @since 2.5
*/
- public boolean hasArrayElementSeparator() { return !_arrayElementSeparator.isEmpty(); }
+ public boolean hasArrayElementSeparator() {
+ return !_arrayElementSeparator.isEmpty();
+ }
/**
* @since 2.7
*/
- public String getAnyPropertyName() { return _anyPropertyName; }
+ public String getAnyPropertyName() {
+ return _anyPropertyName;
+ }
/*
/**********************************************************************
/* Public API, extended; column access
/**********************************************************************
*/
-
+
@Override
public Iterator
+ *
*/
public Column column(int index) {
return _columns[index];
@@ -1412,11 +1532,15 @@ public Column column(int index) {
public String columnName(int index) {
return _columns[index].getName();
}
-
+
public Column column(String name) {
return _columnsByName.get(name);
}
+ public String pathSeparator() {
+ return _pathSeparator;
+ }
+
/**
* Optimized variant where a hint is given as to likely index of the column
* name.
@@ -1430,15 +1554,21 @@ public Column column(String name, int probableIndex) {
return col;
}
}
- return _columnsByName.get(name);
+
+ for (Column c : _columns) {
+ if (c.hasName(name)) {
+ return c;
+ }
+ }
+
+ return null;
}
-
+
/**
* Method for getting description of column definitions in
* developer-readable form
*/
- public String getColumnDesc()
- {
+ public String getColumnDesc() {
StringBuilder sb = new StringBuilder(100);
for (Column col : _columns) {
if (sb.length() == 0) {
@@ -1461,11 +1591,9 @@ public String getColumnDesc()
*/
@Override
- public String toString()
- {
+ public String toString() {
StringBuilder sb = new StringBuilder(150);
- sb.append("[CsvSchema: ")
- .append("columns=[");
+ sb.append("[CsvSchema: ").append("columns=[");
boolean first = true;
for (Column col : _columns) {
if (first) {
@@ -1489,7 +1617,7 @@ public String toString()
} else {
sb.append("as '").append(anyProp).append("'");
}
-
+
sb.append(']');
return sb.toString();
}
@@ -1506,4 +1634,11 @@ protected static String _validArrayElementSeparator(String sep) {
}
return sep;
}
+
+ private
* 0 <= index < size()
- *
+ *