Skip to content

Commit

Permalink
Merge #635, which fixes #382. Update History.md.
Browse files Browse the repository at this point in the history
  • Loading branch information
brasmusson committed Dec 11, 2013
2 parents 15ade0b + 0c37374 commit 4baca3d
Show file tree
Hide file tree
Showing 8 changed files with 227 additions and 8 deletions.
1 change: 1 addition & 0 deletions History.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
## [1-1-6-SNAPSHOT (Git master)](https://github.com/cucumber/cucumber-jvm/compare/v1.1.5...master)

* [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)
* [Examples] Fixed concurrency bugs in Webbit Selenium example (Aslak Hellesøy)
* [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)
* [TestNG] Allow TestNG Cucumber runner to use composition instead of inheritance. ([#622](https://github.com/cucumber/cucumber-jvm/pull/622) Marty Kube)
Expand Down
38 changes: 36 additions & 2 deletions core/src/main/java/cucumber/api/DataTable.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package cucumber.api;

import cucumber.runtime.CucumberException;
import cucumber.runtime.ParameterInfo;
import cucumber.runtime.table.DiffableRow;
import cucumber.runtime.table.TableConverter;
Expand Down Expand Up @@ -55,15 +56,25 @@ private static DataTable create(List<?> raw, Locale locale, String format, Strin
public DataTable(List<DataTableRow> gherkinRows, TableConverter tableConverter) {
this.gherkinRows = gherkinRows;
this.tableConverter = tableConverter;
int columns = gherkinRows.get(0).getCells().size();
List<List<String>> raw = new ArrayList<List<String>>();
for (Row row : gherkinRows) {
List<String> list = new ArrayList<String>();
list.addAll(row.getCells());
if (columns != row.getCells().size()) {
throw new CucumberException(String.format("Table is unbalanced: expected %s column(s) but found %s.", columns, row.getCells().size()));
}
raw.add(Collections.unmodifiableList(list));
}
this.raw = Collections.unmodifiableList(raw);
}

private DataTable(List<DataTableRow> gherkinRows, List<List<String>> raw, TableConverter tableConverter) {
this.gherkinRows = gherkinRows;
this.tableConverter = tableConverter;
this.raw = Collections.unmodifiableList(raw);
}

/**
* Converts the table to a 2D array.
*
Expand All @@ -74,7 +85,11 @@ public List<List<String>> raw() {
}

public <T> T convert(Type type) {
return tableConverter.convert(type, this);
return tableConverter.convert(type, this, false);
}

public <T> T convert(Type type, boolean transposed) {
return tableConverter.convert(type, this, transposed);
}

/**
Expand All @@ -100,7 +115,7 @@ public List<Map<String, String>> asMaps() {
* @return a list of objects
*/
public <T> List<T> asList(Type type) {
List<T> result = tableConverter.toList(type, this);
List<T> result = tableConverter.toList(type, this, false);
return result;
}

Expand Down Expand Up @@ -188,6 +203,25 @@ public List<String> flatten() {
return result;
}

public DataTable transpose() {
List<List<String>> transposed = new ArrayList<List<String>>();
for (int i = 0; i < gherkinRows.size(); i++) {
Row gherkinRow = gherkinRows.get(i);
for (int j = 0; j < gherkinRow.getCells().size(); j++) {
List<String> row = null;
if (j < transposed.size()) {
row = transposed.get(j);
}
if (row == null) {
row = new ArrayList<String>();
transposed.add(row);
}
row.add(gherkinRow.getCells().get(j));
}
}
return new DataTable(this.gherkinRows, transposed, this.tableConverter);
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
Expand Down
39 changes: 39 additions & 0 deletions core/src/main/java/cucumber/api/Transpose.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package cucumber.api;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* <p>
* This annotation can be specified on step definition method parameters to give Cucumber a hint
* to transpose a DataTable into an object or list of objects.
*
* For example, if you have the following Gherkin step with a table
* </p>
* <pre>
* Given the user is
* | firstname | Roberto |
* | lastname | Lo Giacco |
* | nationality | Italian |
* </pre>
* <p>
* Then the following Java Step Definition would convert that into an User object:
* </p>
* <pre>
* &#064;Given("^the user is$")
* public void the_user_is(@Transpose User user) {
* this.user = user;
* }
* </pre>
* <p>
*
* This annotation also works for data tables that are transformed to a list of beans.
* </p>
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface Transpose {
boolean value() default true;
}
17 changes: 16 additions & 1 deletion core/src/main/java/cucumber/runtime/ParameterInfo.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import cucumber.api.Format;
import cucumber.api.Transform;
import cucumber.api.Transformer;
import cucumber.api.Transpose;
import cucumber.deps.com.thoughtworks.xstream.annotations.XStreamConverter;
import cucumber.deps.com.thoughtworks.xstream.converters.SingleValueConverter;
import cucumber.runtime.xstream.LocalizedXStreams;
Expand All @@ -24,6 +25,7 @@ public class ParameterInfo {
private final Type type;
private final String format;
private final String delimiter;
private final boolean transposed;
private final Transformer transformer;

public static List<ParameterInfo> fromMethod(Method method) {
Expand All @@ -33,6 +35,7 @@ public static List<ParameterInfo> fromMethod(Method method) {
for (int i = 0; i < genericParameterTypes.length; i++) {
String format = null;
String delimiter = DEFAULT_DELIMITER;
boolean transposed = false;
Transformer transformer = null;
for (Annotation annotation : annotations[i]) {
if (annotation instanceof Format) {
Expand All @@ -41,6 +44,9 @@ public static List<ParameterInfo> fromMethod(Method method) {
if (annotation instanceof Delimiter) {
delimiter = ((Delimiter) annotation).value();
}
if (annotation instanceof Transpose) {
transposed = ((Transpose) annotation).value();
}
if (annotation instanceof Transform) {
try {
transformer = ((Transform) annotation).value().newInstance();
Expand All @@ -51,15 +57,20 @@ public static List<ParameterInfo> fromMethod(Method method) {
}
}
}
result.add(new ParameterInfo(genericParameterTypes[i], format, delimiter, transformer));
result.add(new ParameterInfo(genericParameterTypes[i], format, delimiter, transposed, transformer));
}
return result;
}

public ParameterInfo(Type type, String format, String delimiter, Transformer transformer) {
this(type, format, delimiter, false, transformer);
}

public ParameterInfo(Type type, String format, String delimiter, boolean transposed, Transformer transformer) {
this.type = type;
this.format = format;
this.delimiter = delimiter;
this.transposed = transposed;
this.transformer = transformer;
}

Expand All @@ -79,6 +90,10 @@ public Type getType() {
return type;
}

public boolean isTransposed() {
return transposed;
}

@Override
public String toString() {
return type.toString();
Expand Down
4 changes: 2 additions & 2 deletions core/src/main/java/cucumber/runtime/StepDefinitionMatch.java
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ private ParameterInfo getParameterType(int n, Type argumentType) {
ParameterInfo parameterInfo = stepDefinition.getParameterType(n, argumentType);
if (parameterInfo == null) {
// Some backends return null because they don't know
parameterInfo = new ParameterInfo(argumentType, null, null, null);
parameterInfo = new ParameterInfo(argumentType, null, null, false, null);
}
return parameterInfo;
}
Expand All @@ -91,7 +91,7 @@ private Object tableArgument(Step step, int argIndex, LocalizedXStreams.Localize
ParameterInfo parameterInfo = getParameterType(argIndex, DataTable.class);
DataTable table = new DataTable(step.getRows(), new TableConverter(xStream, parameterInfo));
Type type = parameterInfo.getType();
return table.convert(type);
return table.convert(type, parameterInfo.isTransposed());
}

private CucumberException arityMismatch(int parameterCount) {
Expand Down
17 changes: 14 additions & 3 deletions core/src/main/java/cucumber/runtime/table/TableConverter.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public TableConverter(LocalizedXStreams.LocalizedXStream xStream, ParameterInfo
this.parameterInfo = parameterInfo;
}

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

if (transposed) {
dataTable = dataTable.transpose();
}

Type listItemType = listItemType(itemType);
if (listItemType == null) {
SingleValueConverter singleValueConverter = xStream.getSingleValueConverter(itemType);
Expand Down Expand Up @@ -153,9 +157,16 @@ private List<Map<Object, Object>> toListOfSingleValueMap(DataTable dataTable, Si
*/
public <T> List<T> toList(final Type type, DataTable dataTable) {
if (type == null) {
return convert(new GenericListType(new GenericListType(String.class)), dataTable);
return convert(new GenericListType(new GenericListType(String.class)), dataTable, false);
}
return convert(new GenericListType(type), dataTable, false);
}

public <T> List<T> toList(final Type type, DataTable dataTable, boolean transposed) {
if (type == null) {
return convert(new GenericListType(new GenericListType(String.class)), dataTable, transposed);
}
return convert(new GenericListType(type), dataTable);
return convert(new GenericListType(type), dataTable, transposed);
}

/**
Expand Down
43 changes: 43 additions & 0 deletions core/src/test/java/cucumber/runtime/table/DataTableTest.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package cucumber.runtime.table;

import cucumber.api.DataTable;
import cucumber.runtime.CucumberException;
import cucumber.runtime.xstream.LocalizedXStreams;
import gherkin.formatter.model.Comment;
import gherkin.formatter.model.DataTableRow;
Expand All @@ -26,6 +27,25 @@ public void rawShouldHaveThreeColumnsAndTwoRows() {
}
}

@Test
public void transposedRawShouldHaveTwoColumnsAndThreeRows() {
List<List<String>> raw = createSimpleTable().transpose().raw();
assertEquals("Rows size", 3, raw.size());
for (List<String> list : raw) {
assertEquals("Cols size: " + list, 2, list.size());
}
}

@Test(expected=CucumberException.class)
public void canNotSupportNonRectangularTablesMissingColumn() {
List<List<String>> raw = createNonRectangularTableMissingColumn().raw();
}

@Test(expected=CucumberException.class)
public void canNotSupportNonRectangularTablesExceedingColumn() {
List<List<String>> raw = createNonRectangularTableExceedingColumn().raw();
}

@Test
public void canCreateTableFromListOfListOfString() {
DataTable dataTable = createSimpleTable();
Expand Down Expand Up @@ -67,16 +87,39 @@ public void two_identical_tables_are_considered_equal() {
assertEquals(createSimpleTable().hashCode(), createSimpleTable().hashCode());
}

@Test
public void two_identical_transposed_tables_are_considered_equal() {
assertEquals(createSimpleTable().transpose(), createSimpleTable().transpose());
assertEquals(createSimpleTable().transpose().hashCode(), createSimpleTable().transpose().hashCode());
}

@Test
public void two_different_tables_are_considered_non_equal() {
assertFalse(createSimpleTable().equals(createTable(asList("one"))));
assertNotSame(createSimpleTable().hashCode(), createTable(asList("one")).hashCode());
}

@Test
public void two_different_transposed_tables_are_considered_non_equal() {
assertFalse(createSimpleTable().transpose().equals(createTable(asList("one")).transpose()));
assertNotSame(createSimpleTable().transpose().hashCode(), createTable(asList("one")).transpose().hashCode());
}

public DataTable createSimpleTable() {
return createTable(asList("one", "four", "seven"), asList("4444", "55555", "666666"));
}

public DataTable createNonRectangularTableMissingColumn() {
return createTable(asList("one", "four", "seven"),
asList("a1", "a4444"),
asList("b1"));
}

public DataTable createNonRectangularTableExceedingColumn() {
return createTable(asList("one", "four", "seven"),
asList("a1", "a4444", "b7777777", "zero"));
}

private DataTable createTable(List<String>... rows) {
List<DataTableRow> simpleRows = new ArrayList<DataTableRow>();
for (int i = 0; i < rows.length; i++) {
Expand Down
Loading

0 comments on commit 4baca3d

Please sign in to comment.