Skip to content

Convert parameterized objects with @DefaultDataTableEntryTransformer #2995

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
## Fixed
- [Core] Convert parameterized objects with `@DefaultDataTableEntryTransformer` ([#2995](https://github.com/cucumber/cucumber-jvm/pull/2995) Jean Tissot)

### Changed
- [Archetype] Assume new projects are created with at least Java 17

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import io.cucumber.datatable.TypeFactory.MapType;
import io.cucumber.datatable.TypeFactory.OptionalType;
import io.cucumber.datatable.TypeFactory.OtherType;
import io.cucumber.datatable.TypeFactory.Parameterized;
import org.apiguardian.api.API;

import java.lang.reflect.Type;
Expand Down Expand Up @@ -87,7 +88,7 @@ public <T> T convert(DataTable dataTable, Type type, boolean transposed) {
return (T) Optional.ofNullable(singleton);
}

if (javaType instanceof OtherType) {
if (javaType instanceof OtherType || javaType instanceof Parameterized) {
return toSingleton(dataTable, javaType);
}

Expand All @@ -106,7 +107,8 @@ public <T> T convert(DataTable dataTable, Type type, boolean transposed) {
return (T) toLists(dataTable, listElement.getElementType());
}

assert listElementType instanceof OtherType || listElementType instanceof OptionalType;
assert listElementType instanceof OtherType || listElementType instanceof OptionalType
|| listElementType instanceof Parameterized;
return (T) toList(dataTable, listElementType);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import org.junit.jupiter.api.Test;

import java.beans.ConstructorProperties;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.math.BigInteger;
Expand All @@ -17,6 +18,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.TimeZone;

Expand All @@ -30,6 +32,7 @@
import static java.util.Collections.emptyMap;
import static java.util.Collections.singletonList;
import static java.util.Locale.ENGLISH;
import static java.util.stream.Collectors.toMap;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.startsWith;
import static org.hamcrest.MatcherAssert.assertThat;
Expand Down Expand Up @@ -93,7 +96,11 @@ class DataTableTypeRegistryTableConverterTest {
}.getType();
private static final Type MAP_OF_STRING_TO_MAP_OF_INTEGER_TO_PIECE = new TypeReference<Map<String, Map<Integer, Piece>>>() {
}.getType();
public static final Type OPTIONAL_CHESS_BOARD_TYPE = new TypeReference<Optional<ChessBoard>>() {
private static final Type OPTIONAL_CHESS_BOARD_TYPE = new TypeReference<Optional<ChessBoard>>() {
}.getType();
private static final Type NUMBERED_AUTHOR = new TypeReference<NumberedObject<Author>>() {
}.getType();
private static final Type LIST_OF_NUMBERED_AUTHOR = new TypeReference<List<NumberedObject<Author>>>() {
}.getType();
private static final TableTransformer<ChessBoard> CHESS_BOARD_TABLE_TRANSFORMER = table -> new ChessBoard(
table.subTable(1, 1).values());
Expand All @@ -120,11 +127,30 @@ class DataTableTypeRegistryTableConverterTest {
};
private static final TableEntryByTypeTransformer JACKSON_TABLE_ENTRY_BY_TYPE_CONVERTER = (entry, type,
cellTransformer) -> objectMapper.convertValue(entry, objectMapper.constructType(type));
private static final TableEntryByTypeTransformer JACKSON_NUMBERED_OBJECT_TABLE_ENTRY_CONVERTER = (entry, type,
cellTransformer) -> {
if (!(type instanceof ParameterizedType)) {
throw new IllegalArgumentException("Unsupported type " + type);
}
ParameterizedType parameterizedType = (ParameterizedType) type;
if (!NumberedObject.class.equals(parameterizedType.getRawType())) {
throw new IllegalArgumentException("Unsupported type " + parameterizedType);
}
return convertToNumberedObject(entry, parameterizedType.getActualTypeArguments()[0]);
};
private static final TableCellByTypeTransformer JACKSON_TABLE_CELL_BY_TYPE_CONVERTER = (value,
cellType) -> objectMapper.convertValue(value, objectMapper.constructType(cellType));
private static final DataTableType DATE_TABLE_CELL_TRANSFORMER = new DataTableType(Date.class,
(TableCellTransformer<Date>) SIMPLE_DATE_FORMAT::parse);

private static Object convertToNumberedObject(Map<String, String> numberedEntry, Type type) {
int number = Integer.parseInt(numberedEntry.get("#"));
Map<String, String> entry = numberedEntry.entrySet().stream()
.filter(e -> !"#".equals(e.getKey()))
.collect(toMap(Map.Entry::getKey, Map.Entry::getValue));
return new NumberedObject<>(number, objectMapper.convertValue(entry, objectMapper.constructType(type)));
}

static {
SIMPLE_DATE_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC"));
}
Expand Down Expand Up @@ -427,6 +453,26 @@ void convert_to_empty_list_of_object__using_default_converter__throws_exception(
"Note: Usually solving one is enough"));
}

@Test
void convert_to_list_of_parameterized_object__using_default_converter() {
DataTable table = parse("",
"| # | firstName | lastName | birthDate |",
"| 1 | Annie M. G. | Schmidt | 1911-03-20 |",
"| 2 | Roald | Dahl | 1916-09-13 |",
"| 3 | Astrid | Lindgren | 1907-11-14 |");

registry.setDefaultDataTableEntryTransformer(JACKSON_NUMBERED_OBJECT_TABLE_ENTRY_CONVERTER);
registry.setDefaultDataTableCellTransformer(TABLE_CELL_BY_TYPE_CONVERTER_SHOULD_NOT_BE_USED);

List<NumberedObject<Author>> expected = asList(
new NumberedObject<>(1, new Author("Annie M. G.", "Schmidt", "1911-03-20")),
new NumberedObject<>(2, new Author("Roald", "Dahl", "1916-09-13")),
new NumberedObject<>(3, new Author("Astrid", "Lindgren", "1907-11-14")));

assertEquals(expected, converter.toList(table, NUMBERED_AUTHOR));
assertEquals(expected, converter.convert(table, LIST_OF_NUMBERED_AUTHOR));
}

@Test
void convert_to_list_of_primitive() {
DataTable table = parse("",
Expand Down Expand Up @@ -1222,6 +1268,20 @@ void convert_to_single_object__single_cell__using_default_transformer() {
assertEquals(Piece.BLACK_BISHOP, converter.convert(table, Piece.class));
}

@Test
void convert_to_parameterized_object__using_default_converter() {
DataTable table = parse("",
"| # | firstName | lastName | birthDate |",
"| 1 | Annie M. G. | Schmidt | 1911-03-20 |");

registry.setDefaultDataTableEntryTransformer(JACKSON_NUMBERED_OBJECT_TABLE_ENTRY_CONVERTER);
registry.setDefaultDataTableCellTransformer(TABLE_CELL_BY_TYPE_CONVERTER_SHOULD_NOT_BE_USED);

NumberedObject<Author> expected = new NumberedObject<>(1, new Author("Annie M. G.", "Schmidt", "1911-03-20"));

assertEquals(expected, converter.convert(table, NUMBERED_AUTHOR));
}

@Test
void convert_to_table__table_transformer_takes_precedence_over_identity_transform() {
DataTable table = parse("",
Expand Down Expand Up @@ -1713,6 +1773,28 @@ void to_maps_of_unknown_value_type__throws_exception__register_table_cell_transf
"Note: Usually solving one is enough"));
}

private static class NumberedObject<T> {
private final int number;
private final T value;

private NumberedObject(int number, T value) {
this.number = number;
this.value = value;
}

@Override
public boolean equals(Object obj) {
return obj instanceof NumberedObject
&& ((NumberedObject<?>) obj).number == number
&& Objects.equals(((NumberedObject<?>) obj).value, value);
}

@Override
public String toString() {
return String.format("%d: %s", number, value);
}
}

private enum Piece {
BLACK_PAWN("♟"),
BLACK_BISHOP("♝"),
Expand Down