Permalink
Browse files

Merge #635, which fixes #382. Update History.md.

  • Loading branch information...
2 parents 15ade0b + 0c37374 commit 4baca3d88b14022610a64b6b26f603501d13a57a @brasmusson brasmusson committed Dec 11, 2013
View
@@ -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)
@@ -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;
@@ -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.
*
@@ -74,7 +85,11 @@ public DataTable(List<DataTableRow> gherkinRows, TableConverter tableConverter)
}
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);
}
/**
@@ -100,7 +115,7 @@ public DataTable(List<DataTableRow> gherkinRows, TableConverter tableConverter)
* @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;
}
@@ -188,6 +203,25 @@ public TableConverter getTableConverter() {
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;
@@ -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;
+}
@@ -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;
@@ -24,6 +25,7 @@
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) {
@@ -33,6 +35,7 @@
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) {
@@ -41,6 +44,9 @@
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();
@@ -51,15 +57,20 @@
}
}
}
- 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;
}
@@ -79,6 +90,10 @@ public Type getType() {
return type;
}
+ public boolean isTransposed() {
+ return transposed;
+ }
+
@Override
public String toString() {
return type.toString();
@@ -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;
}
@@ -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) {
@@ -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))) {
@@ -56,6 +56,10 @@ public TableConverter(LocalizedXStreams.LocalizedXStream xStream, ParameterInfo
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);
@@ -153,9 +157,16 @@ public TableConverter(LocalizedXStreams.LocalizedXStream xStream, ParameterInfo
*/
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);
}
/**
@@ -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;
@@ -27,6 +28,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();
DataTable other = dataTable.toTable(dataTable.raw());
@@ -68,15 +88,38 @@ public void two_identical_tables_are_considered_equal() {
}
@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++) {
Oops, something went wrong.

0 comments on commit 4baca3d

Please sign in to comment.