Skip to content

Commit

Permalink
datatable: Allow null values
Browse files Browse the repository at this point in the history
  • Loading branch information
mpkorstanje committed Aug 2, 2019
1 parent 8c8ef65 commit 7d9cba8
Show file tree
Hide file tree
Showing 7 changed files with 87 additions and 100 deletions.
1 change: 1 addition & 0 deletions datatable/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
### Added

### Changed
* Allow `null` values in `DataTable`.

### Deprecated

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ public static DataTable create(List<List<String>> raw) {
* @throws IllegalArgumentException when the table is not rectangular or contains null values
*/
public static DataTable create(List<List<String>> raw, TableConverter tableConverter) {
return new DataTable(copy(requireNonNullEntries(requireRectangularTable(raw))), tableConverter);
return new DataTable(copy(requireRectangularTable(raw)), tableConverter);
}

private static List<List<String>> copy(List<List<String>> balanced) {
Expand All @@ -106,21 +106,6 @@ private static List<List<String>> copy(List<List<String>> balanced) {
return unmodifiableList(rawCopy);
}

private static List<List<String>> requireNonNullEntries(List<List<String>> raw) {
// Iterate in case of linked list.
int rowIndex = 0;
for (List<String> row : raw) {
int columnIndex = row.indexOf(null);
if (columnIndex >= 0) {
throw new IllegalArgumentException(
"raw contained null at row: " + rowIndex + " column: " + columnIndex);
}
rowIndex++;
}

return raw;
}

private static List<List<String>> requireRectangularTable(List<List<String>> table) {
int columns = table.isEmpty() ? 0 : table.get(0).size();
for (List<String> row : table) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.function.Function;

import static io.cucumber.datatable.TypeFactory.constructType;
import static java.lang.String.format;
Expand All @@ -24,81 +25,42 @@ public final class DataTableTypeRegistry {
public DataTableTypeRegistry(Locale locale) {
final NumberParser numberParser = new NumberParser(locale);

defineDataTableType(new DataTableType(BigInteger.class, new TableCellTransformer<BigInteger>() {
@Override
public BigInteger transform(String cell) {
return cell.isEmpty() ? null : new BigInteger(cell);
}
}));

defineDataTableType(new DataTableType(BigDecimal.class, new TableCellTransformer<BigDecimal>() {
@Override
public BigDecimal transform(String cell) {
return cell.isEmpty() ? null : numberParser.parseBigDecimal(cell);
}
}));

TableCellTransformer<Byte> byteTableCellTransformer = new TableCellTransformer<Byte>() {
@Override
public Byte transform(String cell) {
return cell.isEmpty() ? null : Byte.decode(cell);
}
};
TableCellTransformer<BigInteger> bigIntegerTableCellTransformer = applyIfPresent(BigInteger::new);
defineDataTableType(new DataTableType(BigInteger.class, bigIntegerTableCellTransformer));

TableCellTransformer<BigDecimal> bigDecimalTableCellTransformer = applyIfPresent(numberParser::parseBigDecimal);
defineDataTableType(new DataTableType(BigDecimal.class, bigDecimalTableCellTransformer));
TableCellTransformer<Byte> byteTableCellTransformer = applyIfPresent(Byte::decode);
defineDataTableType(new DataTableType(Byte.class, byteTableCellTransformer));
defineDataTableType(new DataTableType(byte.class, byteTableCellTransformer));

TableCellTransformer<Short> shortTableCellTransformer = new TableCellTransformer<Short>() {
@Override
public Short transform(String cell) {
return cell.isEmpty() ? null : Short.decode(cell);
}
};
TableCellTransformer<Short> shortTableCellTransformer = applyIfPresent(Short::decode);
defineDataTableType(new DataTableType(Short.class, shortTableCellTransformer));
defineDataTableType(new DataTableType(short.class, shortTableCellTransformer));

TableCellTransformer<Integer> integerTableCellTransformer = new TableCellTransformer<Integer>() {
@Override
public Integer transform(String cell) {
return cell.isEmpty() ? null : Integer.decode(cell);
}
};
TableCellTransformer<Integer> integerTableCellTransformer = applyIfPresent(Integer::decode);
defineDataTableType(new DataTableType(Integer.class, integerTableCellTransformer));
defineDataTableType(new DataTableType(int.class, integerTableCellTransformer));

TableCellTransformer<Long> longTableCellTransformer = new TableCellTransformer<Long>() {
@Override
public Long transform(String cell) {
return cell.isEmpty() ? null : Long.decode(cell);
}
};
TableCellTransformer<Long> longTableCellTransformer = applyIfPresent(Long::decode);
defineDataTableType(new DataTableType(Long.class, longTableCellTransformer));
defineDataTableType(new DataTableType(long.class, longTableCellTransformer));

TableCellTransformer<Float> floatTableCellTransformer = new TableCellTransformer<Float>() {
@Override
public Float transform(String cell) {
return cell.isEmpty() ? null : numberParser.parseFloat(cell);
}
};
TableCellTransformer<Float> floatTableCellTransformer = applyIfPresent(numberParser::parseFloat);
defineDataTableType(new DataTableType(Float.class, floatTableCellTransformer));
defineDataTableType(new DataTableType(float.class, floatTableCellTransformer));

TableCellTransformer<Double> doubleTableCellTransformer = new TableCellTransformer<Double>() {
@Override
public Double transform(String cell) {
return cell.isEmpty() ? null : numberParser.parseDouble(cell);
}
};
TableCellTransformer<Double> doubleTableCellTransformer = applyIfPresent(numberParser::parseDouble);
defineDataTableType(new DataTableType(Double.class, doubleTableCellTransformer));
defineDataTableType(new DataTableType(double.class, doubleTableCellTransformer));

defineDataTableType(new DataTableType(String.class, new TableCellTransformer<String>() {
@Override
public String transform(String cell) {
return cell;
}
}));
TableCellTransformer<String> stringTableCellTransformer = (String cell) -> cell;
defineDataTableType(new DataTableType(String.class, stringTableCellTransformer));

}

private static <R> TableCellTransformer<R> applyIfPresent(Function<String, R> f) {
return s -> s == null ? null : f.apply(s);
}

public void defineDataTableType(DataTableType dataTableType) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,18 @@ private void printRow(List<String> cells, int rowIndex, Appendable buffer) throw
}

private String escapeCell(String cell) {
return cell.replaceAll("\\\\(?!\\|)", "\\\\\\\\").replaceAll("\\n", "\\\\n").replaceAll("\\|", "\\\\|");
if(cell == null){
return "";
}

if(cell.isEmpty()){
return "[empty]";
}

return cell
.replaceAll("\\\\(?!\\|)", "\\\\\\\\")
.replaceAll("\\n", "\\\\n")
.replaceAll("\\|", "\\\\|");
}

private void padSpace(Appendable buffer, int indent) throws IOException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,13 @@ public void raw_should_equal_raw() {
assertEquals(raw, table.cells());
}

@Test
public void raw_may_contain_nulls() {
List<List<String>> raw = asList(asList(null, null), asList(null, null));
DataTable table = DataTable.create(raw);
assertEquals(raw, table.cells());
}

@Test
public void cells_should_equal_raw() {
List<List<String>> raw = asList(asList("hundred", "100"), asList("thousand", "1000"));
Expand Down Expand Up @@ -344,6 +351,24 @@ public void can_create_table_from_list_of_list_of_string() {
other.toString());
}

@Test
public void can_print_table_with_empty_cells() {
DataTable dataTable = DataTable.create(singletonList(singletonList("")));
List<List<String>> listOfListOfString = dataTable.cells();
DataTable other = DataTable.create(listOfListOfString);
assertEquals(" | [empty] |\n",
other.toString());
}

@Test
public void can_print_table_with_null_cells() {
DataTable dataTable = DataTable.create(singletonList(singletonList(null)));
List<List<String>> listOfListOfString = dataTable.cells();
DataTable other = DataTable.create(listOfListOfString);
assertEquals(" | |\n",
other.toString());
}

@Test(expected = UnsupportedOperationException.class)
public void cells_row_is_immutable() {
createSimpleTable().cells().remove(0);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,7 @@
import static java.lang.Double.parseDouble;
import static java.lang.String.format;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static java.util.Collections.emptyMap;
import static java.util.Collections.*;
import static java.util.Locale.ENGLISH;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
Expand Down Expand Up @@ -381,6 +380,16 @@ public void convert_to_list_of_primitive() {
assertEquals(expected, converter.convert(table, LIST_OF_INT));
}

@Test
public void convert_null_cells_to_null() {
DataTable table = DataTable.create(singletonList(singletonList(null)));

List<Integer> expected = singletonList(null);

assertEquals(expected, converter.toList(table, Integer.class));
assertEquals(expected, converter.convert(table, LIST_OF_INT));
}

@Test
public void convert_to_list_of_unknown_type__throws_exception__register_transformer() {
expectedException.expectMessage(listNoConverterDefined(Author.class, "TableEntryTransformer or TableRowTransformer", Author.class).getMessage());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,106 +156,100 @@ public void parse_decimal_with_german_locale() {
}

@Test
public void empty_big_integer_transformed_to_null() {
public void null_big_integer_transformed_to_null() {
DataTableTypeRegistry registry = new DataTableTypeRegistry(Locale.ENGLISH);
DataTableType dataTableType = registry.lookupTableTypeByType(LIST_OF_LIST_OF_BIG_INTEGER);
assertEquals(
singletonList(singletonList(null)),
dataTableType.transform(singletonList(singletonList("")))
dataTableType.transform(singletonList(singletonList(null)))
);

}

@Test
public void empty_big_decimal_transformed_to_null() {
public void null_big_decimal_transformed_to_null() {
DataTableTypeRegistry registry = new DataTableTypeRegistry(Locale.ENGLISH);
DataTableType dataTableType = registry.lookupTableTypeByType(LIST_OF_LIST_OF_BIG_DECIMAL);
assertEquals(
singletonList(singletonList(null)),
dataTableType.transform(singletonList(singletonList("")))
dataTableType.transform(singletonList(singletonList(null)))
);

}

@Test
public void empty_byte_transformed_to_null() {
public void null_byte_transformed_to_null() {
DataTableTypeRegistry registry = new DataTableTypeRegistry(Locale.ENGLISH);
DataTableType dataTableType = registry.lookupTableTypeByType(LIST_OF_LIST_OF_BYTE);
assertEquals(
singletonList(singletonList(null)),
dataTableType.transform(singletonList(singletonList("")))
dataTableType.transform(singletonList(singletonList(null)))
);

}

@Test
public void empty_short_transformed_to_null() {
public void null_short_transformed_to_null() {
DataTableTypeRegistry registry = new DataTableTypeRegistry(Locale.ENGLISH);
DataTableType dataTableType = registry.lookupTableTypeByType(LIST_OF_LIST_OF_SHORT);
assertEquals(
singletonList(singletonList(null)),
dataTableType.transform(singletonList(singletonList("")))
dataTableType.transform(singletonList(singletonList(null)))
);

}

@Test
public void empty_integer_transformed_to_null() {
public void null_integer_transformed_to_null() {
DataTableTypeRegistry registry = new DataTableTypeRegistry(Locale.ENGLISH);
DataTableType dataTableType = registry.lookupTableTypeByType(LIST_OF_LIST_OF_INTEGER);
assertEquals(
singletonList(singletonList(null)),
dataTableType.transform(singletonList(singletonList("")))
dataTableType.transform(singletonList(singletonList(null)))
);

}

@Test
public void empty_long_transformed_to_null() {
public void null_long_transformed_to_null() {
DataTableTypeRegistry registry = new DataTableTypeRegistry(Locale.ENGLISH);
DataTableType dataTableType = registry.lookupTableTypeByType(LIST_OF_LIST_OF_LONG);
assertEquals(
singletonList(singletonList(null)),
dataTableType.transform(singletonList(singletonList("")))
dataTableType.transform(singletonList(singletonList(null)))
);

}

@Test
public void empty_float_transformed_to_null() {
public void null_float_transformed_to_null() {
DataTableTypeRegistry registry = new DataTableTypeRegistry(Locale.ENGLISH);
DataTableType dataTableType = registry.lookupTableTypeByType(LIST_OF_LIST_OF_FLOAT);
assertEquals(
singletonList(singletonList(null)),
dataTableType.transform(singletonList(singletonList("")))
dataTableType.transform(singletonList(singletonList(null)))
);

}

@Test
public void empty_double_transformed_to_null() {
public void null_double_transformed_to_null() {
DataTableTypeRegistry registry = new DataTableTypeRegistry(Locale.ENGLISH);
DataTableType dataTableType = registry.lookupTableTypeByType(LIST_OF_LIST_OF_DOUBLE);
assertEquals(
singletonList(singletonList(null)),
dataTableType.transform(singletonList(singletonList("")))
dataTableType.transform(singletonList(singletonList(null)))
);

}

/**
* TODO in v5
* To remain consistent the empty string should always be converted to null (so also for strings)
* and doing this would be a breaking change.
* Should be picked up with v5.
*/
@Test
public void empty_string_transformed_to_empty() {
public void null_string_transformed_to_null() {
DataTableTypeRegistry registry = new DataTableTypeRegistry(Locale.ENGLISH);
DataTableType dataTableType = registry.lookupTableTypeByType(LIST_OF_LIST_OF_STRING);
assertEquals(
singletonList(singletonList("")),
dataTableType.transform(singletonList(singletonList("")))
singletonList(singletonList(null)),
dataTableType.transform(singletonList(singletonList(null)))
);

}
Expand Down

0 comments on commit 7d9cba8

Please sign in to comment.