Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
156 changes: 154 additions & 2 deletions csv/src/main/java/com/fasterxml/jackson/dataformat/csv/CsvSchema.java
Original file line number Diff line number Diff line change
Expand Up @@ -629,6 +629,55 @@ public Builder removeColumn(int index) {
return this;
}

/**
* Method for renaming an existing column, located by its current name
* instead of by index.
*
* @param oldName Current name of the column to rename
* @param newName New name to assign to the column
*
* @throws IllegalArgumentException if no column with given {@code oldName} exists
*
* @since 2.23
*/
public Builder renameColumn(String oldName, String newName) {
return renameColumn(_columnIndex(oldName), newName);
}

/**
* Method for replacing an existing column, located by its current name
* instead of by index.
*<p>
* NOTE: index of the replacement column {@code c} is ignored (as with
* {@link #replaceColumn(int, Column)}): columns are renumbered to match
* their actual position when {@link #build()} is called. Caller hence does
* not need to know index of the column being replaced.
*
* @param name Name of the column to replace
* @param c Column definition to use as replacement
*
* @throws IllegalArgumentException if no column with given {@code name} exists
*
* @since 2.23
*/
public Builder replaceColumn(String name, Column c) {
return replaceColumn(_columnIndex(name), c);
}

/**
* Method for removing an existing column, located by its name instead
* of by index.
*
* @param name Name of the column to remove
*
* @throws IllegalArgumentException if no column with given {@code name} exists
*
* @since 2.23
*/
public Builder removeColumn(String name) {
return removeColumn(_columnIndex(name));
}

/**
* Helper method called to drop the last collected column name if
* it is empty: called if {link CsvParser.Feature#ALLOW_TRAILING_COMMA}
Expand All @@ -651,12 +700,41 @@ public Builder setColumnType(int index, ColumnType type) {
return this;
}

/**
* Method for changing type of an existing column, located by its name
* instead of by index.
*
* @param name Name of the column to change type of
* @param type Type to assign to the column
*
* @throws IllegalArgumentException if no column with given {@code name} exists
*
* @since 2.23
*/
public Builder setColumnType(String name, ColumnType type) {
return setColumnType(_columnIndex(name), type);
}

public Builder removeArrayElementSeparator(int index) {
_checkIndex(index);
_columns.set(index, _columns.get(index).withArrayElementSeparator(""));
return this;
}

/**
* Method for removing array element separator of an existing column,
* located by its name instead of by index.
*
* @param name Name of the column to remove array element separator of
*
* @throws IllegalArgumentException if no column with given {@code name} exists
*
* @since 2.23
*/
public Builder removeArrayElementSeparator(String name) {
return removeArrayElementSeparator(_columnIndex(name));
}

/**
* @since 2.7
*/
Expand All @@ -666,6 +744,25 @@ public Builder setArrayElementSeparator(int index, String sep) {
return this;
}

/**
* Method for setting array element separator of an existing column,
* located by its name instead of by index.
*<p>
* NOTE: not to be confused with single-argument
* {@link #setArrayElementSeparator(String)}, which sets the schema-wide
* default separator instead of that of a single column.
*
* @param name Name of the column to set array element separator of
* @param sep Array element separator to assign to the column
*
* @throws IllegalArgumentException if no column with given {@code name} exists
*
* @since 2.23
*/
public Builder setArrayElementSeparator(String name, String sep) {
return setArrayElementSeparator(_columnIndex(name), sep);
}

public Builder setAnyPropertyName(String name) {
_anyPropertyName = name;
return this;
Expand Down Expand Up @@ -694,12 +791,31 @@ public Iterator<Column> getColumns() {
* @since 2.9
*/
public boolean hasColumn(String name) {
return columnIndex(name) >= 0;
}

/**
* Method for finding index of the column with given name, if any.
*<p>
* 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#columnIndex(String)} has a
* hash lookup to use).
*
* @param name Name of column to find
*
* @return Index of the first column with given name, if one exists;
* {@code -1} if not
*
* @since 2.23
*/
public int columnIndex(String name) {
for (int i = 0, end = _columns.size(); i < end; ++i) {
if (_columns.get(i).getName().equals(name)) {
return true;
return i;
}
}
return false;
return -1;
}

/**
Expand Down Expand Up @@ -869,6 +985,42 @@ protected void _checkIndex(int index) {
throw new IllegalArgumentException("Illegal index "+index+"; only got "+_columns.size()+" columns");
}
}

/**
* Helper method for finding index of the column with given name, for
* use by name-based mutators; same as {@link #columnIndex(String)} except
* that a missing column is reported as an exception instead of {@code -1}.
*
* @param name Name of the column to find
*
* @return Index of the first column with given name
*
* @throws IllegalArgumentException if no column with given {@code name} exists
*
* @since 2.23
*/
protected int _columnIndex(String name) {
int ix = columnIndex(name);
if (ix < 0) {
throw new IllegalArgumentException("No column '"+name+"' in CsvSchema.Builder (known columns: "
+_columnNames()+")");
}
return ix;
}

/**
* Helper method for constructing List of names of currently included
* columns, for use in exception messages.
*
* @since 2.23
*/
private List<String> _columnNames() {
List<String> names = new ArrayList<>(_columns.size());
for (int i = 0, end = _columns.size(); i < end; ++i) {
names.add(_columns.get(i).getName());
}
return names;
}
}

/*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -253,4 +253,86 @@ public void testSchemaWithExplicitIndices()

_verifyLinks(pointSchema);
}

// For [dataformats-text#699]: modify columns in Builder by name
@Test
public void testModifyColumnsByName()
{
CsvSchema.Builder b = CsvSchema.builder()
.addColumn("a", CsvSchema.ColumnType.STRING)
.addColumn("b", CsvSchema.ColumnType.NUMBER)
.addColumn("c", CsvSchema.ColumnType.BOOLEAN);

// rename by name maps to same slot as rename by index
b.renameColumn("b", "b2");
// replace by name preserves position, changes type; note that index of
// the replacement Column is deliberately "wrong" (0, not 2) to verify it
// is ignored -- the whole point being caller need not know the index
b.replaceColumn("c", new Column(0, "c2", CsvSchema.ColumnType.STRING));

CsvSchema schema = b.build();
assertEquals(3, schema.size());
assertEquals("a", schema.column(0).getName());
assertEquals("b2", schema.column(1).getName());
assertEquals(CsvSchema.ColumnType.NUMBER, schema.column(1).getType());
assertEquals("c2", schema.column(2).getName());
assertEquals(CsvSchema.ColumnType.STRING, schema.column(2).getType());
// ... and index gets renumbered to match actual position
assertEquals(2, schema.column(2).getIndex());
assertEquals(2, schema.columnIndex("c2"));

// remove by name drops just that column
CsvSchema shrunk = schema.rebuild().removeColumn("a").build();
assertEquals(2, shrunk.size());
assertEquals("b2", shrunk.column(0).getName());
assertEquals("c2", shrunk.column(1).getName());
}

// For [dataformats-text#699]: type and array-element-separator by name too
@Test
public void testModifyColumnSettingsByName()
{
CsvSchema.Builder b = CsvSchema.builder()
.addColumn("a", CsvSchema.ColumnType.STRING)
.addColumn("tags", CsvSchema.ColumnType.ARRAY);

b.setColumnType("a", CsvSchema.ColumnType.NUMBER);
b.setArrayElementSeparator("tags", ";");

CsvSchema schema = b.build();
assertEquals(CsvSchema.ColumnType.NUMBER, schema.column(0).getType());
assertEquals(";", schema.column(1).getArrayElementSeparator());

// and removal of separator, likewise by name
assertEquals("", schema.rebuild().removeArrayElementSeparator("tags")
.build().column(1).getArrayElementSeparator());
}

// For [dataformats-text#699]: non-throwing index lookup from Builder
@Test
public void testColumnIndexLookupFromBuilder()
{
CsvSchema.Builder b = CsvSchema.builder().addColumn("a").addColumn("b");
assertEquals(0, b.columnIndex("a"));
assertEquals(1, b.columnIndex("b"));
assertEquals(-1, b.columnIndex("missing"));
// and `hasColumn()` remains consistent with it
assertTrue(b.hasColumn("a"));
assertFalse(b.hasColumn("missing"));
}

// For [dataformats-text#699]: unknown name should fail fast, and the failure
// should name the columns that DO exist (as `CsvSchema.withColumn(String,...)` does)
@Test
public void testModifyUnknownColumnByName()
{
CsvSchema.Builder b = CsvSchema.builder().addColumn("a");
try {
b.removeColumn("missing");
fail("Should not pass");
} catch (IllegalArgumentException e) {
verifyException(e, "No column 'missing' in CsvSchema.Builder");
verifyException(e, "known columns: [a]");
}
}
}
10 changes: 10 additions & 0 deletions release-notes/CREDITS-2.x
Original file line number Diff line number Diff line change
Expand Up @@ -337,3 +337,13 @@ seonwoojung (@seonwooj0810)
* Fixed #696: (toml) Write non-finite floating-point values as TOML tokens
(`nan`/`inf`/`-inf`) instead of Java tokens (`NaN`/`Infinity`)
(2.23.0)

James Howe (@OrangeDog)

* Requested #699: (csv) Ability to modify columns in `CsvSchema.Builder` by name
(2.23.0)

seonwoojung (@seonwooj0810)

* Fixed #699: (csv) Ability to modify columns in `CsvSchema.Builder` by name
(2.23.0)
3 changes: 3 additions & 0 deletions release-notes/VERSION-2.x
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ Active Maintainers:
(`nan`/`inf`/`-inf`) instead of Java tokens (`NaN`/`Infinity`)
(reported by @EverNife)
(fix by @seonwooj0810)
#699: (csv) Ability to modify columns in `CsvSchema.Builder` by name
(requested by @OrangeDog)
(fix by @seonwooj0810)

2.22.2 (not yet released)

Expand Down