Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 4baca3d

Browse files
committed
Merge cucumber#635, which fixes cucumber#382. Update History.md.
2 parents 15ade0b + 0c37374 commit 4baca3d

File tree

8 files changed

+227
-8
lines changed

8 files changed

+227
-8
lines changed

History.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
## [1-1-6-SNAPSHOT (Git master)](https://github.com/cucumber/cucumber-jvm/compare/v1.1.5...master)
22

3+
* [Core] Add support for transposed tables. ([#382](https://github.com/cucumber/cucumber-jvm/issues/382), [#635](https://github.com/cucumber/cucumber-jvm/pull/635), Roberto Lo Giacco)
34
* [Examples] Fixed concurrency bugs in Webbit Selenium example (Aslak Hellesøy)
45
* [Core] Fixed thread leak in timeout implementation. ([#639](https://github.com/cucumber/cucumber-jvm/issues/639), [#640](https://github.com/cucumber/cucumber-jvm/pull/640), Nikolay Volnov)
56
* [TestNG] Allow TestNG Cucumber runner to use composition instead of inheritance. ([#622](https://github.com/cucumber/cucumber-jvm/pull/622) Marty Kube)

core/src/main/java/cucumber/api/DataTable.java

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package cucumber.api;
22

3+
import cucumber.runtime.CucumberException;
34
import cucumber.runtime.ParameterInfo;
45
import cucumber.runtime.table.DiffableRow;
56
import cucumber.runtime.table.TableConverter;
@@ -55,15 +56,25 @@ private static DataTable create(List<?> raw, Locale locale, String format, Strin
5556
public DataTable(List<DataTableRow> gherkinRows, TableConverter tableConverter) {
5657
this.gherkinRows = gherkinRows;
5758
this.tableConverter = tableConverter;
59+
int columns = gherkinRows.get(0).getCells().size();
5860
List<List<String>> raw = new ArrayList<List<String>>();
5961
for (Row row : gherkinRows) {
6062
List<String> list = new ArrayList<String>();
6163
list.addAll(row.getCells());
64+
if (columns != row.getCells().size()) {
65+
throw new CucumberException(String.format("Table is unbalanced: expected %s column(s) but found %s.", columns, row.getCells().size()));
66+
}
6267
raw.add(Collections.unmodifiableList(list));
6368
}
6469
this.raw = Collections.unmodifiableList(raw);
6570
}
6671

72+
private DataTable(List<DataTableRow> gherkinRows, List<List<String>> raw, TableConverter tableConverter) {
73+
this.gherkinRows = gherkinRows;
74+
this.tableConverter = tableConverter;
75+
this.raw = Collections.unmodifiableList(raw);
76+
}
77+
6778
/**
6879
* Converts the table to a 2D array.
6980
*
@@ -74,7 +85,11 @@ public List<List<String>> raw() {
7485
}
7586

7687
public <T> T convert(Type type) {
77-
return tableConverter.convert(type, this);
88+
return tableConverter.convert(type, this, false);
89+
}
90+
91+
public <T> T convert(Type type, boolean transposed) {
92+
return tableConverter.convert(type, this, transposed);
7893
}
7994

8095
/**
@@ -100,7 +115,7 @@ public List<Map<String, String>> asMaps() {
100115
* @return a list of objects
101116
*/
102117
public <T> List<T> asList(Type type) {
103-
List<T> result = tableConverter.toList(type, this);
118+
List<T> result = tableConverter.toList(type, this, false);
104119
return result;
105120
}
106121

@@ -188,6 +203,25 @@ public List<String> flatten() {
188203
return result;
189204
}
190205

206+
public DataTable transpose() {
207+
List<List<String>> transposed = new ArrayList<List<String>>();
208+
for (int i = 0; i < gherkinRows.size(); i++) {
209+
Row gherkinRow = gherkinRows.get(i);
210+
for (int j = 0; j < gherkinRow.getCells().size(); j++) {
211+
List<String> row = null;
212+
if (j < transposed.size()) {
213+
row = transposed.get(j);
214+
}
215+
if (row == null) {
216+
row = new ArrayList<String>();
217+
transposed.add(row);
218+
}
219+
row.add(gherkinRow.getCells().get(j));
220+
}
221+
}
222+
return new DataTable(this.gherkinRows, transposed, this.tableConverter);
223+
}
224+
191225
@Override
192226
public boolean equals(Object o) {
193227
if (this == o) return true;
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package cucumber.api;
2+
3+
import java.lang.annotation.ElementType;
4+
import java.lang.annotation.Retention;
5+
import java.lang.annotation.RetentionPolicy;
6+
import java.lang.annotation.Target;
7+
8+
/**
9+
* <p>
10+
* This annotation can be specified on step definition method parameters to give Cucumber a hint
11+
* to transpose a DataTable into an object or list of objects.
12+
*
13+
* For example, if you have the following Gherkin step with a table
14+
* </p>
15+
* <pre>
16+
* Given the user is
17+
* | firstname | Roberto |
18+
* | lastname | Lo Giacco |
19+
* | nationality | Italian |
20+
* </pre>
21+
* <p>
22+
* Then the following Java Step Definition would convert that into an User object:
23+
* </p>
24+
* <pre>
25+
* &#064;Given("^the user is$")
26+
* public void the_user_is(@Transpose User user) {
27+
* this.user = user;
28+
* }
29+
* </pre>
30+
* <p>
31+
*
32+
* This annotation also works for data tables that are transformed to a list of beans.
33+
* </p>
34+
*/
35+
@Retention(RetentionPolicy.RUNTIME)
36+
@Target(ElementType.PARAMETER)
37+
public @interface Transpose {
38+
boolean value() default true;
39+
}

core/src/main/java/cucumber/runtime/ParameterInfo.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import cucumber.api.Format;
55
import cucumber.api.Transform;
66
import cucumber.api.Transformer;
7+
import cucumber.api.Transpose;
78
import cucumber.deps.com.thoughtworks.xstream.annotations.XStreamConverter;
89
import cucumber.deps.com.thoughtworks.xstream.converters.SingleValueConverter;
910
import cucumber.runtime.xstream.LocalizedXStreams;
@@ -24,6 +25,7 @@ public class ParameterInfo {
2425
private final Type type;
2526
private final String format;
2627
private final String delimiter;
28+
private final boolean transposed;
2729
private final Transformer transformer;
2830

2931
public static List<ParameterInfo> fromMethod(Method method) {
@@ -33,6 +35,7 @@ public static List<ParameterInfo> fromMethod(Method method) {
3335
for (int i = 0; i < genericParameterTypes.length; i++) {
3436
String format = null;
3537
String delimiter = DEFAULT_DELIMITER;
38+
boolean transposed = false;
3639
Transformer transformer = null;
3740
for (Annotation annotation : annotations[i]) {
3841
if (annotation instanceof Format) {
@@ -41,6 +44,9 @@ public static List<ParameterInfo> fromMethod(Method method) {
4144
if (annotation instanceof Delimiter) {
4245
delimiter = ((Delimiter) annotation).value();
4346
}
47+
if (annotation instanceof Transpose) {
48+
transposed = ((Transpose) annotation).value();
49+
}
4450
if (annotation instanceof Transform) {
4551
try {
4652
transformer = ((Transform) annotation).value().newInstance();
@@ -51,15 +57,20 @@ public static List<ParameterInfo> fromMethod(Method method) {
5157
}
5258
}
5359
}
54-
result.add(new ParameterInfo(genericParameterTypes[i], format, delimiter, transformer));
60+
result.add(new ParameterInfo(genericParameterTypes[i], format, delimiter, transposed, transformer));
5561
}
5662
return result;
5763
}
5864

5965
public ParameterInfo(Type type, String format, String delimiter, Transformer transformer) {
66+
this(type, format, delimiter, false, transformer);
67+
}
68+
69+
public ParameterInfo(Type type, String format, String delimiter, boolean transposed, Transformer transformer) {
6070
this.type = type;
6171
this.format = format;
6272
this.delimiter = delimiter;
73+
this.transposed = transposed;
6374
this.transformer = transformer;
6475
}
6576

@@ -79,6 +90,10 @@ public Type getType() {
7990
return type;
8091
}
8192

93+
public boolean isTransposed() {
94+
return transposed;
95+
}
96+
8297
@Override
8398
public String toString() {
8499
return type.toString();

core/src/main/java/cucumber/runtime/StepDefinitionMatch.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ private ParameterInfo getParameterType(int n, Type argumentType) {
8282
ParameterInfo parameterInfo = stepDefinition.getParameterType(n, argumentType);
8383
if (parameterInfo == null) {
8484
// Some backends return null because they don't know
85-
parameterInfo = new ParameterInfo(argumentType, null, null, null);
85+
parameterInfo = new ParameterInfo(argumentType, null, null, false, null);
8686
}
8787
return parameterInfo;
8888
}
@@ -91,7 +91,7 @@ private Object tableArgument(Step step, int argIndex, LocalizedXStreams.Localize
9191
ParameterInfo parameterInfo = getParameterType(argIndex, DataTable.class);
9292
DataTable table = new DataTable(step.getRows(), new TableConverter(xStream, parameterInfo));
9393
Type type = parameterInfo.getType();
94-
return table.convert(type);
94+
return table.convert(type, parameterInfo.isTransposed());
9595
}
9696

9797
private CucumberException arityMismatch(int parameterCount) {

core/src/main/java/cucumber/runtime/table/TableConverter.java

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public TableConverter(LocalizedXStreams.LocalizedXStream xStream, ParameterInfo
4444
this.parameterInfo = parameterInfo;
4545
}
4646

47-
public <T> T convert(Type type, DataTable dataTable) {
47+
public <T> T convert(Type type, DataTable dataTable, boolean transposed) {
4848
try {
4949
xStream.setParameterType(parameterInfo);
5050
if (type == null || (type instanceof Class && ((Class) type).isAssignableFrom(DataTable.class))) {
@@ -56,6 +56,10 @@ public <T> T convert(Type type, DataTable dataTable) {
5656
throw new CucumberException("Not a List type: " + type);
5757
}
5858

59+
if (transposed) {
60+
dataTable = dataTable.transpose();
61+
}
62+
5963
Type listItemType = listItemType(itemType);
6064
if (listItemType == null) {
6165
SingleValueConverter singleValueConverter = xStream.getSingleValueConverter(itemType);
@@ -153,9 +157,16 @@ private List<Map<Object, Object>> toListOfSingleValueMap(DataTable dataTable, Si
153157
*/
154158
public <T> List<T> toList(final Type type, DataTable dataTable) {
155159
if (type == null) {
156-
return convert(new GenericListType(new GenericListType(String.class)), dataTable);
160+
return convert(new GenericListType(new GenericListType(String.class)), dataTable, false);
161+
}
162+
return convert(new GenericListType(type), dataTable, false);
163+
}
164+
165+
public <T> List<T> toList(final Type type, DataTable dataTable, boolean transposed) {
166+
if (type == null) {
167+
return convert(new GenericListType(new GenericListType(String.class)), dataTable, transposed);
157168
}
158-
return convert(new GenericListType(type), dataTable);
169+
return convert(new GenericListType(type), dataTable, transposed);
159170
}
160171

161172
/**

core/src/test/java/cucumber/runtime/table/DataTableTest.java

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package cucumber.runtime.table;
22

33
import cucumber.api.DataTable;
4+
import cucumber.runtime.CucumberException;
45
import cucumber.runtime.xstream.LocalizedXStreams;
56
import gherkin.formatter.model.Comment;
67
import gherkin.formatter.model.DataTableRow;
@@ -26,6 +27,25 @@ public void rawShouldHaveThreeColumnsAndTwoRows() {
2627
}
2728
}
2829

30+
@Test
31+
public void transposedRawShouldHaveTwoColumnsAndThreeRows() {
32+
List<List<String>> raw = createSimpleTable().transpose().raw();
33+
assertEquals("Rows size", 3, raw.size());
34+
for (List<String> list : raw) {
35+
assertEquals("Cols size: " + list, 2, list.size());
36+
}
37+
}
38+
39+
@Test(expected=CucumberException.class)
40+
public void canNotSupportNonRectangularTablesMissingColumn() {
41+
List<List<String>> raw = createNonRectangularTableMissingColumn().raw();
42+
}
43+
44+
@Test(expected=CucumberException.class)
45+
public void canNotSupportNonRectangularTablesExceedingColumn() {
46+
List<List<String>> raw = createNonRectangularTableExceedingColumn().raw();
47+
}
48+
2949
@Test
3050
public void canCreateTableFromListOfListOfString() {
3151
DataTable dataTable = createSimpleTable();
@@ -67,16 +87,39 @@ public void two_identical_tables_are_considered_equal() {
6787
assertEquals(createSimpleTable().hashCode(), createSimpleTable().hashCode());
6888
}
6989

90+
@Test
91+
public void two_identical_transposed_tables_are_considered_equal() {
92+
assertEquals(createSimpleTable().transpose(), createSimpleTable().transpose());
93+
assertEquals(createSimpleTable().transpose().hashCode(), createSimpleTable().transpose().hashCode());
94+
}
95+
7096
@Test
7197
public void two_different_tables_are_considered_non_equal() {
7298
assertFalse(createSimpleTable().equals(createTable(asList("one"))));
7399
assertNotSame(createSimpleTable().hashCode(), createTable(asList("one")).hashCode());
74100
}
75101

102+
@Test
103+
public void two_different_transposed_tables_are_considered_non_equal() {
104+
assertFalse(createSimpleTable().transpose().equals(createTable(asList("one")).transpose()));
105+
assertNotSame(createSimpleTable().transpose().hashCode(), createTable(asList("one")).transpose().hashCode());
106+
}
107+
76108
public DataTable createSimpleTable() {
77109
return createTable(asList("one", "four", "seven"), asList("4444", "55555", "666666"));
78110
}
79111

112+
public DataTable createNonRectangularTableMissingColumn() {
113+
return createTable(asList("one", "four", "seven"),
114+
asList("a1", "a4444"),
115+
asList("b1"));
116+
}
117+
118+
public DataTable createNonRectangularTableExceedingColumn() {
119+
return createTable(asList("one", "four", "seven"),
120+
asList("a1", "a4444", "b7777777", "zero"));
121+
}
122+
80123
private DataTable createTable(List<String>... rows) {
81124
List<DataTableRow> simpleRows = new ArrayList<DataTableRow>();
82125
for (int i = 0; i < rows.length; i++) {

0 commit comments

Comments
 (0)