package cucumber.api; import cucumber.runtime.CucumberException; import cucumber.runtime.ParameterInfo; import cucumber.runtime.table.DiffableRow; import cucumber.runtime.table.TableConverter; import cucumber.runtime.table.TableDiffException; import cucumber.runtime.table.TableDiffer; import cucumber.runtime.xstream.LocalizedXStreams; import gherkin.formatter.PrettyFormatter; import gherkin.formatter.model.DataTableRow; import gherkin.formatter.model.Row; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.Map; /** * Represents the data from a <a href="http://cucumber.info/gherkin.html#data-tables">Gherkin DataTable</a>. Cucumber will convert the table in Gherkin * to a DataTable instance and pass it to a step definition. */ public class DataTable { private final List<List<String>> raw; private final List<DataTableRow> gherkinRows; private final TableConverter tableConverter; public static DataTable create(List<?> raw) { return create(raw, Locale.getDefault(), null, new String[0]); } public static DataTable create(List<?> raw, String format, String... columnNames) { return create(raw, Locale.getDefault(), format, columnNames); } public static DataTable create(List<?> raw, Locale locale, String... columnNames) { return create(raw, locale, null, columnNames); } private static DataTable create(List<?> raw, Locale locale, String format, String... columnNames) { ParameterInfo parameterInfo = new ParameterInfo(null, format, null, null); TableConverter tableConverter = new TableConverter(new LocalizedXStreams(Thread.currentThread().getContextClassLoader()).get(locale), parameterInfo); return tableConverter.toTable(raw, columnNames); } /** * Creates a new DataTable. This constructor should not be called by Cucumber users - it's used internally only. * * @param gherkinRows the underlying rows. * @param tableConverter how to convert the rows. */ public DataTable(List<DataTableRow> gherkinRows, TableConverter tableConverter) { this.gherkinRows = gherkinRows; this.tableConverter = tableConverter; int columns = gherkinRows.isEmpty() ? 0 : 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); } /** * @return a List of List of String. */ public List<List<String>> raw() { return this.raw; } /** * Converts the table to a List of Map. The top row is used as keys in the maps, * and the rows below are used as values. * * @param <K> key type * @param <V> value type * @param keyType key type * @param valueType value type * * @return a List of Map. */ public <K, V> List<Map<K, V>> asMaps(Class<K> keyType, Class<V> valueType) { return tableConverter.toMaps(this, keyType, valueType); } /** * Converts the table to a single Map. The left column is used as keys, the right column as values. * * @param <K> key type * @param <V> value type * @param keyType key type * @param valueType value type * @return a Map. * @throws cucumber.runtime.CucumberException if the table doesn't have 2 columns. */ public <K, V> Map<K, V> asMap(Class<K> keyType, Class<V> valueType) { return tableConverter.toMap(this, keyType, valueType); } /** * Converts the table to a List. * * If {@code itemType} is a scalar type the table is flattened. * * Otherwise, the top row is used to name the fields/properties and the remaining * rows are turned into list items. * * @param itemType the type of the list items * @param <T> the type of the list items * @return a List of objects */ public <T> List<T> asList(Class<T> itemType) { return tableConverter.toList(this, itemType); } /** * Converts the table to a List of List of scalar. * * @param itemType the type of the list items * @param <T> the type of the list items * @return a List of List of objects */ public <T> List<List<T>> asLists(Class<T> itemType) { return tableConverter.toLists(this, itemType); } public List<String> topCells() { return raw.isEmpty() ? Collections.<String>emptyList() : raw.get(0); } public List<List<String>> cells(int firstRow) { return raw.subList(firstRow, raw.size()); } /** * Creates another table using the same {@link Locale} and {@link Format} that was used to create this table. * * @param raw a list of objects * @param columnNames optional explicit header columns * @return a new table */ public DataTable toTable(List<?> raw, String... columnNames) { return tableConverter.toTable(raw, columnNames); } /** * Diffs this table with {@code other}, which can be a {@code List<List<String>>} or a * {@code List<YourType>}. * * @param other the other table to diff with. * @throws cucumber.runtime.table.TableDiffException if the tables are different. */ public void diff(List<?> other) throws TableDiffException { List<String> topCells = topCells(); DataTable otherTable = toTable(other, topCells.toArray(new String[topCells.size()])); diff(otherTable); } /** * Diffs this table with {@code other}. * * @param other the other table to diff with. * @throws TableDiffException if the tables are different. */ public void diff(DataTable other) throws TableDiffException { new TableDiffer(this, other).calculateDiffs(); } /** * Diffs this table with {@code other}. * The order is not important. A set-difference is applied. * @param other the other table to diff with. * @throws TableDiffException if the tables are different. */ public void unorderedDiff(DataTable other) throws TableDiffException { new TableDiffer(this, other).calculateUnorderedDiffs(); } /** * Diffs this table with {@code other}, which can be a {@code List<List<String>>} or a * {@code List<YourType>}. * * @param other the other table to diff with. * @throws cucumber.runtime.table.TableDiffException if the tables are different. */ public void unorderedDiff(List<?> other) throws TableDiffException { List<String> topCells = topCells(); DataTable otherTable = toTable(other, topCells.toArray(new String[topCells.size()])); unorderedDiff(otherTable); } /** * Internal method. Do not use. * * @return a list of raw rows. */ public List<DataTableRow> getGherkinRows() { return Collections.unmodifiableList(gherkinRows); } @Override public String toString() { StringBuilder result = new StringBuilder(); PrettyFormatter pf = new PrettyFormatter(result, true, false); pf.table(getGherkinRows()); pf.eof(); return result.toString(); } public List<DiffableRow> diffableRows() { List<DiffableRow> result = new ArrayList<DiffableRow>(); List<List<String>> convertedRows = raw(); for (int i = 0; i < convertedRows.size(); i++) { result.add(new DiffableRow(getGherkinRows().get(i), convertedRows.get(i))); } return result; } public TableConverter getTableConverter() { return tableConverter; } 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; if (!(o instanceof DataTable)) return false; DataTable dataTable = (DataTable) o; if (!raw.equals(dataTable.raw)) return false; return true; } @Override public int hashCode() { return raw.hashCode(); } }