From ad364148ca4fa18708ffa3330dcf164a353b21c0 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Thu, 16 Mar 2017 11:16:34 -0700 Subject: [PATCH] Fix #142 --- release-notes/CREDITS | 5 + release-notes/VERSION | 4 +- .../jackson/dataformat/csv/CsvSchema.java | 106 ++++++++++++++++-- .../dataformat/csv/schema/SchemaTest.java | 52 ++++++++- 4 files changed, 156 insertions(+), 11 deletions(-) diff --git a/release-notes/CREDITS b/release-notes/CREDITS index 9ac5205..0bbd6b6 100644 --- a/release-notes/CREDITS +++ b/release-notes/CREDITS @@ -97,3 +97,8 @@ George Fraser (georgewfraser@github) * Contributed #127: Add `CsvGenerator.Feature.ALWAYS_QUOTE_EMPTY_STRINGS` to allow forced quoting of empty Strings. (2.9.0) + +Austin Sharp (sharpau@github) + +* Suggested #142: Add methods for appending columns of a `CsvSchema` into another + (2.9.0) diff --git a/release-notes/VERSION b/release-notes/VERSION index e1044c9..8b2e676 100644 --- a/release-notes/VERSION +++ b/release-notes/VERSION @@ -12,7 +12,9 @@ Project: jackson-dataformat-csv #130: Add fluent addColumns operation to CsvSchema.Builder (contributed by Peter A) #139: Add `CsvParser.Feature.ALLOW_TRAILING_COMMA` to allow enforcing strict handling - (contributed by Nick B) + (contributed by Nick B) +#142: Add methods for appending columns of a `CsvSchema` into another + (suggested by Austin S) 2.8.6 (12-Jan-2017) 2.8.5 (14-Nov-2016) diff --git a/src/main/java/com/fasterxml/jackson/dataformat/csv/CsvSchema.java b/src/main/java/com/fasterxml/jackson/dataformat/csv/CsvSchema.java index eb024ba..a513144 100644 --- a/src/main/java/com/fasterxml/jackson/dataformat/csv/CsvSchema.java +++ b/src/main/java/com/fasterxml/jackson/dataformat/csv/CsvSchema.java @@ -453,21 +453,37 @@ public Builder(CsvSchema src) _anyPropertyName = src._anyPropertyName; } + /** + * NOTE: does NOT check for duplicate column names so it is possibly to + * accidentally add duplicates. + */ public Builder addColumn(String name) { int index = _columns.size(); return addColumn(new Column(index, name)); } + + /** + * NOTE: does NOT check for duplicate column names so it is possibly to + * accidentally add duplicates. + */ public Builder addColumn(String name, ColumnType type) { int index = _columns.size(); return addColumn(new Column(index, name, type)); } + /** + * NOTE: does NOT check for duplicate column names so it is possibly to + * accidentally add duplicates. + */ public Builder addColumn(Column c) { _columns.add(c); return this; } /** + * NOTE: does NOT check for duplicate column names so it is possibly to + * accidentally add duplicates. + * * @since 2.9 */ public Builder addColumns(Iterable cs) { @@ -478,6 +494,9 @@ public Builder addColumns(Iterable cs) { } /** + * NOTE: does NOT check for duplicate column names so it is possibly to + * accidentally add duplicates. + * * @since 2.9 */ public Builder addColumns(Iterable names, ColumnType type) { @@ -488,6 +507,24 @@ public Builder addColumns(Iterable names, ColumnType type) { return result; } + /** + * NOTE: unlike many other add methods, this method DOES check for, and + * discard, possible duplicate columns: that is, if this builder already + * has a column with same name as column to be added, existing column + * is retained and new column ignored. + * + * @since 2.9 + */ + public Builder addColumnsFrom(CsvSchema schema) { + Builder result = this; + for (Column col : schema) { + if (!hasColumn(col.getName())) { + result = result.addColumn(col); + } + } + return result; + } + public Builder addArrayColumn(String name) { int index = _columns.size(); return addColumn(new Column(index, name, ColumnType.ARRAY, "")); @@ -577,7 +614,25 @@ public int size() { public Iterator getColumns() { return _columns.iterator(); } - + + /** + *

+ * 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). + * + * @since 2.9 + */ + public boolean hasColumn(String name) { + for (int i = 0, end = _columns.size(); i < end; ++i) { + if (_columns.get(i).getName().equals(name)) { + return true; + } + } + return false; + } + /** * Method for specifying whether Schema should indicate that * a header line (first row that contains column names) is to be @@ -1114,10 +1169,7 @@ public CsvSchema withoutEscapeChar() { */ @Deprecated // in 2.7; remove in 2.8 public CsvSchema withArrayElementSeparator(char c) { - return (Character.toString(c).equals(_arrayElementSeparator)) ? this - : new CsvSchema(_columns, _features, - _columnSeparator, _quoteChar, _escapeChar, _lineSeparator, Character.toString(c), - _nullValue, _columnsByName, _anyPropertyName); + return withArrayElementSeparator( Character.toString(c)); } /** @@ -1157,13 +1209,40 @@ public CsvSchema withNullValue(String nvl) { (nvl == null) ? null : nvl.toCharArray(), _columnsByName, _anyPropertyName); } - + public CsvSchema withoutColumns() { return new CsvSchema(NO_COLUMNS, _features, _columnSeparator, _quoteChar, _escapeChar, _lineSeparator, _arrayElementSeparator, _nullValue, _columnsByName, _anyPropertyName); } + /** + * Mutant factory method that will try to combine columns of this schema with those + * 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. + *

+ * 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. + * + * @since 2.9 + */ + public CsvSchema withColumnsFrom(CsvSchema toAppend) { + int addCount = toAppend.size(); + if (addCount == 0) { + return this; + } + Builder b = rebuild(); + for (int i = 0; i < addCount; ++i) { + Column col = toAppend.column(i); + if (column(col.getName()) == null) { + b.addColumn(col); + } + } + return b.build(); + } + /** * @since 2.7 */ @@ -1318,9 +1397,20 @@ public String getNullValueString() { public Iterator iterator() { return Arrays.asList(_columns).iterator(); } - + + /** + * Accessor for finding out how many columns this schema defines. + * + * @return Number of columns this schema defines + */ public int size() { return _columns.length; } - + + /** + * Accessor for column at specified index (0-based); index having to be within + *

+     *    0 <= index < size()
+     *
+ */ public Column column(int index) { return _columns[index]; } diff --git a/src/test/java/com/fasterxml/jackson/dataformat/csv/schema/SchemaTest.java b/src/test/java/com/fasterxml/jackson/dataformat/csv/schema/SchemaTest.java index 70d37ae..0f55771 100644 --- a/src/test/java/com/fasterxml/jackson/dataformat/csv/schema/SchemaTest.java +++ b/src/test/java/com/fasterxml/jackson/dataformat/csv/schema/SchemaTest.java @@ -22,7 +22,7 @@ static class ArrayWrapper { public List c; } - // for [databind#74] + // for [dataformat-csv#74] static class Point { public int y; public int x; @@ -30,6 +30,17 @@ static class Point { @JsonPropertyOrder() public static class PointWithAnnotation extends Point {} + + // for [dataformat-csv#142] + interface Named { + public String getFirstName(); + public String getLastName(); + } + + static abstract class YZ { + public abstract int getY(); + public abstract int getZ(); + } /* /********************************************************************** @@ -133,7 +144,7 @@ private void _verifyLinks(CsvSchema schema) prev = curr; } } - + // For [dataformat-csv#74]: problems applying default do-sort handling public void testSchemaWithOrdering() throws Exception { @@ -158,4 +169,41 @@ public void testSchemaWithReordering() CsvSchema schemaWithoutReordering = schemaWithReordering.withColumnReordering(false); assertFalse(schemaWithoutReordering.reordersColumns()); } + + // For [dataformat-csv#142]: append columns from POJOs + public void testSchemaComposition() throws Exception + { + CsvSchema pointSchema = MAPPER.typedSchemaFor(Point.class); + CsvSchema yzSchema = MAPPER.typedSchemaFor(YZ.class); + CsvSchema namedSchema = MAPPER.typedSchemaFor(Named.class); + + // should only add `z` since there's already `y` + CsvSchema schema = pointSchema; + schema = schema.withColumnsFrom(yzSchema); + // but then two name properties + schema = schema.withColumnsFrom(namedSchema); + + assertEquals(5, schema.size()); + Iterator it = schema.iterator(); + assertEquals("x", it.next().getName()); + assertEquals("y", it.next().getName()); + assertEquals("z", it.next().getName()); + assertEquals("firstName", it.next().getName()); + assertEquals("lastName", it.next().getName()); + + // and try alternate way as well. + CsvSchema.Builder builder = CsvSchema.builder(); + builder.addColumnsFrom(yzSchema) + .addColumnsFrom(namedSchema) + .addColumnsFrom(pointSchema); + schema = builder.build(); + + assertEquals(5, schema.size()); + it = schema.iterator(); + assertEquals("y", it.next().getName()); + assertEquals("z", it.next().getName()); + assertEquals("firstName", it.next().getName()); + assertEquals("lastName", it.next().getName()); + assertEquals("x", it.next().getName()); + } }