TableTransformers.java
- package org.jbehave.core.model;
- import static java.util.stream.Collectors.joining;
- import java.util.ArrayList;
- import java.util.HashMap;
- import java.util.LinkedHashMap;
- import java.util.List;
- import java.util.Map;
- import java.util.regex.Matcher;
- import java.util.regex.Pattern;
- import java.util.stream.Collectors;
- import java.util.stream.IntStream;
- import org.apache.commons.lang3.StringUtils;
- import org.apache.commons.lang3.Validate;
- import org.jbehave.core.model.ExamplesTable.TableProperties;
- import org.jbehave.core.model.ExamplesTable.TableRows;
- import org.jbehave.core.steps.ParameterControls;
- /**
- * <p>
- * Facade responsible for transforming table string representations. It allows
- * the registration of several {@link TableTransformer} instances by name.
- * </p>
- * <p>
- * Some Transformers are provided out-of-the-box:
- * <ul>
- * <li>{@link TableTransformers.FromLandscape FromLandscape}: registered under
- * name {@link TableTransformers#FROM_LANDSCAPE}</li>
- * <li>{@link TableTransformers.Formatting Formatting}: registered under name
- * {@link TableTransformers#FORMATTING}</li>
- * <li>{@link TableTransformers.Replacing Replacing}: registered under name
- * {@link TableTransformers#REPLACING}</li>
- * </ul>
- * Out-of-the-box transformers that must be registered:
- * <ul>
- * <li>{@link ResolvingSelfReferences ResolvingSelfReferences}
- * </ul>
- * </p>
- */
- public class TableTransformers {
- public static final String FROM_LANDSCAPE = "FROM_LANDSCAPE";
- public static final String FORMATTING = "FORMATTING";
- public static final String REPLACING = "REPLACING";
- private final Map<String, TableTransformer> transformers = new HashMap<>();
- public TableTransformers() {
- useTransformer(FROM_LANDSCAPE, new FromLandscape());
- useTransformer(FORMATTING, new Formatting());
- useTransformer(REPLACING, new Replacing());
- }
- public String transform(String transformerName, String tableAsString, TableParsers tableParsers,
- TableProperties properties) {
- TableTransformer tableTransformer = transformers.get(transformerName);
- if (tableTransformer == null) {
- throw new TransformerNotFoundException(transformerName);
- }
- String result = tableTransformer.transform(tableAsString, tableParsers, properties);
- if (result == null) {
- throw new InvalidTransformationResultException(
- String.format("Table transformation using transformer '%s' resulted in 'null'", transformerName));
- }
- return result;
- }
- public void useTransformer(String name, TableTransformer transformer) {
- transformers.put(name, transformer);
- }
- public interface TableTransformer {
- String transform(String tableAsString, TableParsers tableParsers, TableProperties properties);
- }
- public static class FromLandscape implements TableTransformer {
- @Override
- public String transform(String tableAsString, TableParsers tableParsers, TableProperties properties) {
- Map<String, List<String>> data = new LinkedHashMap<>();
- for (String rowAsString : tableAsString.split(properties.getRowSeparator())) {
- if (ignoreRow(rowAsString, properties.getIgnorableSeparator())) {
- continue;
- }
- List<String> values = tableParsers.parseRow(rowAsString, false, properties);
- String header = values.get(0);
- List<String> rowValues = new ArrayList<>(values);
- rowValues.remove(0);
- data.put(header, rowValues);
- }
- if (data.values().stream().mapToInt(List::size).distinct().count() != 1) {
- String errorMessage = data.entrySet()
- .stream()
- .map(e -> {
- int numberOfCells = e.getValue().size();
- StringBuilder rowDescription = new StringBuilder(e.getKey())
- .append(" -> ")
- .append(numberOfCells)
- .append(" cell");
- if (numberOfCells > 1) {
- rowDescription.append('s');
- }
- return rowDescription.toString();
- })
- .collect(joining(", ", "The table rows have unequal numbers of cells: ", ""));
- throw new IllegalArgumentException(errorMessage);
- }
- StringBuilder builder = new StringBuilder();
- builder.append(properties.getHeaderSeparator());
- for (String header : data.keySet()) {
- builder.append(header).append(properties.getHeaderSeparator());
- }
- builder.append(properties.getRowSeparator());
- int numberOfCells = data.values().iterator().next().size();
- for (int c = 0; c < numberOfCells; c++) {
- builder.append(properties.getValueSeparator());
- for (List<String> row : data.values()) {
- builder.append(row.get(c)).append(properties.getValueSeparator());
- }
- builder.append(properties.getRowSeparator());
- }
- return builder.toString();
- }
- private boolean ignoreRow(String rowAsString, String ignorableSeparator) {
- return rowAsString.startsWith(ignorableSeparator)
- || rowAsString.length() == 0;
- }
- }
- public static class Formatting implements TableTransformer {
- @Override
- public String transform(String tableAsString, TableParsers tableParsers, TableProperties properties) {
- List<List<String>> data = new ArrayList<>();
- for (String rowAsString : tableAsString.split(properties.getRowSeparator())) {
- if (ignoreRow(rowAsString, properties.getIgnorableSeparator())) {
- continue;
- }
- data.add(tableParsers.parseRow(rowAsString, rowAsString.contains(properties.getHeaderSeparator()),
- properties));
- }
- StringBuilder builder = new StringBuilder();
- Map<Integer, Integer> maxWidths = maxWidth(data);
- for (int r = 0; r < data.size(); r++) {
- String formattedRow = formatRow(data.get(r), maxWidths,
- r == 0 ? properties.getHeaderSeparator() : properties.getValueSeparator());
- builder.append(formattedRow).append(properties.getRowSeparator());
- }
- return builder.toString();
- }
- private boolean ignoreRow(String rowAsString, String ignorableSeparator) {
- return rowAsString.startsWith(ignorableSeparator)
- || rowAsString.length() == 0;
- }
- private Map<Integer, Integer> maxWidth(List<List<String>> data) {
- Map<Integer, Integer> maxWidths = new HashMap<>();
- for (List<String> row : data) {
- for (int c = 0; c < row.size(); c++) {
- String cell = row.get(c).trim();
- Integer width = maxWidths.get(c);
- int length = cell.length();
- if (width == null || length > width) {
- width = length;
- maxWidths.put(c, width);
- }
- }
- }
- return maxWidths;
- }
- private String formatRow(List<String> row,
- Map<Integer, Integer> maxWidths, String separator) {
- StringBuilder builder = new StringBuilder();
- builder.append(separator);
- for (int c = 0; c < row.size(); c++) {
- builder.append(formatValue(row.get(c).trim(), maxWidths.get(c)))
- .append(separator);
- }
- return builder.toString();
- }
- private String formatValue(String value, int width) {
- if (value.length() < width) {
- return value + padding(width - value.length());
- }
- return value;
- }
- private String padding(int size) {
- StringBuilder builder = new StringBuilder();
- for (int i = 0; i < size; i++) {
- builder.append(' ');
- }
- return builder.toString();
- }
- }
- public static class Replacing implements TableTransformer {
- @Override
- public String transform(String tableAsString, TableParsers tableParsers, TableProperties properties) {
- String replacing = properties.getProperties().getProperty("replacing");
- String replacement = properties.getProperties().getProperty("replacement");
- if (replacing == null || replacement == null) {
- return tableAsString;
- }
- return tableAsString.replace(replacing, replacement);
- }
- }
- public static class ResolvingSelfReferences implements TableTransformer {
- private final ParameterControls parameterControls;
- private final Pattern placeholderPattern;
- public ResolvingSelfReferences(ParameterControls parameterControls) {
- this.parameterControls = parameterControls;
- placeholderPattern = Pattern.compile(
- parameterControls.nameDelimiterLeft() + "(.*?)" + parameterControls.nameDelimiterRight());
- }
- @Override
- public String transform(String tableAsString, TableParsers tableParsers, TableProperties properties) {
- TableRows rows = tableParsers.parseRows(tableAsString, properties);
- List<String> headers = rows.getHeaders();
- List<List<String>> resolvedRows = getNamedRows(rows.getRows(), headers).stream()
- .map(this::resolveRow)
- .collect(Collectors.toList());
- return ExamplesTableStringBuilder.buildExamplesTableString(properties, headers, resolvedRows);
- }
- private List<String> resolveRow(Map<String, String> unresolvedRow) {
- Map<String, String> resolvedRow = new HashMap<>(unresolvedRow.size(), 1);
- return unresolvedRow.keySet().stream()
- .map(name -> resolveCell(name, resolvedRow, unresolvedRow))
- .collect(Collectors.toList());
- }
- private String resolveCell(String name, Map<String, String> resolvedRow, Map<String, String> unresolvedRow) {
- return resolveCell(name, new ArrayList<>(), resolvedRow, unresolvedRow);
- }
- private String resolveCell(String name, List<String> resolutionChain, Map<String, String> resolvedRow,
- Map<String, String> unresolvedRow) {
- if (resolvedRow.containsKey(name)) {
- return resolvedRow.get(name);
- }
- resolutionChain.add(name);
- String result = unresolvedRow.get(name);
- Matcher matcher = placeholderPattern.matcher(result);
- while (matcher.find()) {
- String nestedName = matcher.group(1);
- Validate.validState(!name.equals(nestedName), "Circular self reference is found in column '%s'", name);
- checkForCircularChainOfReferences(resolutionChain, nestedName);
- if (unresolvedRow.containsKey(nestedName)) {
- resolveCell(nestedName, resolutionChain, resolvedRow, unresolvedRow);
- }
- result = StringUtils.replace(result,
- parameterControls.nameDelimiterLeft() + nestedName + parameterControls.nameDelimiterRight(),
- resolvedRow.get(nestedName));
- }
- resolvedRow.put(name, result);
- return result;
- }
- private void checkForCircularChainOfReferences(List<String> resolutionChain, String name) {
- int index = resolutionChain.indexOf(name);
- if (index >= 0) {
- String delimiter = " -> ";
- String truncatedChain = resolutionChain.stream().skip(index).collect(Collectors.joining(delimiter));
- throw new IllegalStateException(
- "Circular chain of references is found: " + truncatedChain + delimiter + name);
- }
- }
- private List<Map<String, String>> getNamedRows(List<List<String>> tableRows, List<String> tableHeaders) {
- List<Map<String, String>> namedRows = new ArrayList<>();
- for (List<String> row : tableRows) {
- Map<String, String> namedRow = new LinkedHashMap<>();
- IntStream.range(0, row.size()).forEach(i -> namedRow.put(tableHeaders.get(i), row.get(i)));
- namedRows.add(namedRow);
- }
- return namedRows;
- }
- }
- public static class TransformerNotFoundException extends RuntimeException {
- private static final long serialVersionUID = 3742264745351414296L;
- public TransformerNotFoundException(String transformerName) {
- super(String.format("Table transformer '%s' does not exist", transformerName));
- }
- }
- public static class InvalidTransformationResultException extends RuntimeException {
- private static final long serialVersionUID = -1242448321325167977L;
- public InvalidTransformationResultException(String message) {
- super(message);
- }
- }
- }