Coverage Report - org.jbehave.core.model.ExamplesTable
 
Classes in this File Line Coverage Branch Coverage Complexity
ExamplesTable
99%
107/108
100%
30/30
1.586
 
 1  
 package org.jbehave.core.model;
 2  
 
 3  
 import java.io.ByteArrayInputStream;
 4  
 import java.io.IOException;
 5  
 import java.util.ArrayList;
 6  
 import java.util.Collections;
 7  
 import java.util.HashMap;
 8  
 import java.util.LinkedHashMap;
 9  
 import java.util.List;
 10  
 import java.util.Map;
 11  
 import java.util.Properties;
 12  
 import java.util.regex.Matcher;
 13  
 import java.util.regex.Pattern;
 14  
 
 15  
 import org.apache.commons.lang.StringUtils;
 16  
 import org.apache.commons.lang.builder.ToStringBuilder;
 17  
 import org.apache.commons.lang.builder.ToStringStyle;
 18  
 import org.jbehave.core.steps.ChainedRow;
 19  
 import org.jbehave.core.steps.ConvertedParameters;
 20  
 import org.jbehave.core.steps.ParameterConverters;
 21  
 import org.jbehave.core.steps.Parameters;
 22  
 import org.jbehave.core.steps.Row;
 23  
 
 24  
 import static java.lang.Boolean.parseBoolean;
 25  
 import static java.util.regex.Pattern.DOTALL;
 26  
 import static java.util.regex.Pattern.compile;
 27  
 
 28  
 /**
 29  
  * <p>
 30  
  * Represents a tabular structure that holds rows of example data for parameters
 31  
  * named via the column headers:
 32  
  * <p/>
 33  
  * 
 34  
  * <pre>
 35  
  * |header 1|header 2| .... |header n|
 36  
  * |value 11|value 12| .... |value 1n|
 37  
  * ...
 38  
  * |value m1|value m2| .... |value mn|
 39  
  * </pre>
 40  
  * <p>
 41  
  * Different header and value column separators can be specified to replace the
 42  
  * default separator "|":
 43  
  * </p>
 44  
  * 
 45  
  * <pre>
 46  
  * !!header 1!!header 2!! .... !!header n!!
 47  
  * !value 11!value 12! .... !value 1n!
 48  
  * ...
 49  
  * !value m1!value m2| .... !value mn!
 50  
  * </pre>
 51  
  * <p>
 52  
  * Rows starting with an ignorable separator are allowed and ignored:
 53  
  * </p>
 54  
  * 
 55  
  * <pre>
 56  
  * |header 1|header 2| .... |header n|
 57  
  * |-- A commented row --|
 58  
  * |value 11|value 12| .... |value 1n|
 59  
  * ...
 60  
  * |-- Another commented row --|
 61  
  * |value m1|value m2| .... |value mn|
 62  
  * </pre>
 63  
  * <p>
 64  
  * Ignorable separator is configurable and defaults to "|--".
 65  
  * </p>
 66  
  * <p>
 67  
  * By default all column values are trimmed. To avoid trimming the values:
 68  
  * 
 69  
  * <pre>
 70  
  * {trim=false}
 71  
  * | header 1 | header 2 | .... | header n |
 72  
  * | value 11 | value 12 | .... | value 1n |
 73  
  * </pre>
 74  
  * 
 75  
  * </p>
 76  
  * 
 77  
  * <p>
 78  
  * The table also allows the retrieval of row values as converted parameters.
 79  
  * Use {@link #getRowAsParameters(int)} and invoke
 80  
  * {@link Parameters#valueAs(String, Class)} specifying the header and the class
 81  
  * type of the parameter.
 82  
  * </p>
 83  
  */
 84  
 public class ExamplesTable {
 85  1
     private static final Map<String, String> EMPTY_MAP = Collections.emptyMap();
 86  
 
 87  1
     public static final ExamplesTable EMPTY = new ExamplesTable("");
 88  
 
 89  
     private static final String HEADER_SEPARATOR = "|";
 90  
     private static final String VALUE_SEPARATOR = "|";
 91  
     private static final String IGNORABLE_SEPARATOR = "|--";
 92  165
     private final List<Map<String, String>> data = new ArrayList<Map<String, String>>();
 93  
     private final String tableAsString;
 94  
     private final String headerSeparator;
 95  
     private final String valueSeparator;
 96  
     private final String ignorableSeparator;
 97  
     private final ParameterConverters parameterConverters;
 98  165
     private final List<String> headers = new ArrayList<String>();
 99  165
     private final Properties properties = new Properties();
 100  165
     private Map<String, String> namedParameters = new HashMap<String, String>();
 101  165
     private boolean trim = true;
 102  
 
 103  
     private final Row defaults;
 104  
 
 105  
     public ExamplesTable(String tableAsString) {
 106  31
         this(tableAsString, HEADER_SEPARATOR, VALUE_SEPARATOR);
 107  31
     }
 108  
 
 109  
     public ExamplesTable(String tableAsString, String headerSeparator, String valueSeparator) {
 110  33
         this(tableAsString, headerSeparator, valueSeparator, IGNORABLE_SEPARATOR, new ParameterConverters());
 111  33
     }
 112  
 
 113  
     public ExamplesTable(String tableAsString, String headerSeparator, String valueSeparator,
 114  164
             String ignorableSeparator, ParameterConverters parameterConverters) {
 115  164
         this.tableAsString = tableAsString;
 116  164
         this.headerSeparator = headerSeparator;
 117  164
         this.valueSeparator = valueSeparator;
 118  164
         this.ignorableSeparator = ignorableSeparator;
 119  164
         this.parameterConverters = parameterConverters;
 120  164
         this.defaults = new ConvertedParameters(EMPTY_MAP, parameterConverters);
 121  164
         parse();
 122  164
     }
 123  
 
 124  1
     private ExamplesTable(ExamplesTable other, Row defaults) {
 125  1
         this.data.addAll(other.data);
 126  1
         this.tableAsString = other.tableAsString;
 127  1
         this.headerSeparator = other.headerSeparator;
 128  1
         this.valueSeparator = other.valueSeparator;
 129  1
         this.ignorableSeparator = other.ignorableSeparator;
 130  1
         this.parameterConverters = other.parameterConverters;
 131  1
         this.headers.addAll(other.headers);
 132  1
         this.properties.putAll(other.properties);
 133  1
         this.defaults = defaults;
 134  1
     }
 135  
 
 136  
     private void parse() {
 137  164
         data.clear();
 138  164
         headers.clear();
 139  164
         String[] rows = splitInRows(stripProperties(tableAsString.trim()));
 140  2405
         for (int row = 0; row < rows.length; row++) {
 141  2241
             String rowAsString = rows[row];
 142  2241
             if (rowAsString.startsWith(ignorableSeparator)) {
 143  
                 // skip rows that start with ignorable separator
 144  2
                 continue;
 145  2239
             } else if (row == 0) {
 146  164
                 List<String> columns = columnsFor(rowAsString, headerSeparator);
 147  164
                 headers.addAll(columns);
 148  164
             } else {
 149  2075
                 List<String> columns = columnsFor(rowAsString, valueSeparator);
 150  2075
                 Map<String, String> map = createRowMap();
 151  22230
                 for (int column = 0; column < columns.size(); column++) {
 152  20155
                     map.put(headers.get(column), columns.get(column));
 153  
                 }
 154  2075
                 data.add(map);
 155  
             }
 156  
         }
 157  164
     }
 158  
 
 159  
     private String stripProperties(String tableAsString) {
 160  164
         Pattern pattern = compile("\\{(.*?)\\}\\s*(.*)", DOTALL);
 161  164
         Matcher matcher = pattern.matcher(tableAsString);
 162  164
         if (matcher.matches()) {
 163  1
             parseProperties(matcher.group(1));
 164  1
             return matcher.group(2);
 165  
         }
 166  163
         return tableAsString;
 167  
     }
 168  
 
 169  
     private void parseProperties(String propertiesAsString) {
 170  1
         properties.clear();
 171  
         try {
 172  1
             properties.load(new ByteArrayInputStream(propertiesAsString.replace(",", "\n").getBytes()));
 173  0
         } catch (IOException e) {
 174  
             // carry on
 175  1
         }
 176  1
         trim = parseBoolean(properties.getProperty("trim", "true"));
 177  1
     }
 178  
 
 179  
     private String[] splitInRows(String table) {
 180  164
         return table.split("\n");
 181  
     }
 182  
 
 183  
     private List<String> columnsFor(String row, String separator) {
 184  2239
         List<String> columns = new ArrayList<String>();
 185  
         // use split limit -1 to ensure that empty strings will not be discarted
 186  26832
         for (String column : row.split(buildRegex(separator), -1)) {
 187  24593
             columns.add(valueOf(column));
 188  
         }
 189  
         // there may be a leading and a trailing empty column which we ignore
 190  2239
         if (StringUtils.isBlank(columns.get(0))) {
 191  2233
             columns.remove(0);
 192  
         }
 193  2239
         int lastIndex = columns.size() - 1;
 194  2239
         if (lastIndex != -1 && StringUtils.isBlank(columns.get(lastIndex))) {
 195  2111
             columns.remove(lastIndex);
 196  
         }
 197  2239
         return columns;
 198  
     }
 199  
 
 200  
     private String valueOf(String column) {
 201  24593
         return trim ? column.trim() : column;
 202  
     }
 203  
 
 204  
     private String buildRegex(String separator) {
 205  2239
         char[] chars = separator.toCharArray();
 206  2239
         StringBuffer sb = new StringBuffer();
 207  4480
         for (char c : chars) {
 208  2241
             sb.append("\\").append(c);
 209  
         }
 210  2239
         return sb.toString();
 211  
     }
 212  
 
 213  
     protected Map<String, String> createRowMap() {
 214  2075
         return new LinkedHashMap<String, String>();
 215  
     }
 216  
 
 217  
     public ExamplesTable withDefaults(Parameters defaults) {
 218  1
         return new ExamplesTable(this, new ChainedRow(defaults, this.defaults));
 219  
     }
 220  
 
 221  
     public ExamplesTable withNamedParameters(Map<String, String> namedParameters) {
 222  1
         this.namedParameters = namedParameters;
 223  1
         return this;
 224  
     }
 225  
 
 226  
     public Properties getProperties() {
 227  1
         return properties;
 228  
     }
 229  
 
 230  
     public List<String> getHeaders() {
 231  22
         return headers;
 232  
     }
 233  
 
 234  
     public Map<String, String> getRow(int row) {
 235  105
         return data.get(row);
 236  
     }
 237  
 
 238  
     public Parameters getRowAsParameters(int row) {
 239  5
         return getRowAsParameters(row, false);
 240  
     }
 241  
 
 242  
     public Parameters getRowAsParameters(int row, boolean replaceNamedParameters) {
 243  9
         Map<String, String> rowValues = getRow(row);
 244  9
         return createParameters((replaceNamedParameters ? replaceNamedParameters(rowValues) : rowValues));
 245  
     }
 246  
 
 247  
     private Map<String, String> replaceNamedParameters(Map<String, String> row) {
 248  1
         Map<String, String> replaced = new HashMap<String, String>();
 249  1
         for (String key : row.keySet()) {
 250  2
             String replacedValue = row.get(key);
 251  2
             for (String namedKey : namedParameters.keySet()) {
 252  2
                 String namedValue = namedParameters.get(namedKey);
 253  2
                 replacedValue = replacedValue.replaceAll(namedKey, namedValue);
 254  2
             }
 255  2
             replaced.put(key, replacedValue);
 256  2
         }
 257  1
         return replaced;
 258  
     }
 259  
 
 260  
     public int getRowCount() {
 261  48
         return data.size();
 262  
     }
 263  
 
 264  
     public List<Map<String, String>> getRows() {
 265  23
         return data;
 266  
     }
 267  
 
 268  
 
 269  
     public List<Parameters> getRowsAsParameters() {
 270  2
         return getRowsAsParameters(false);
 271  
     }
 272  
 
 273  
     public List<Parameters> getRowsAsParameters(boolean replaceNamedParameters) {
 274  3
         List<Parameters> rows = new ArrayList<Parameters>();
 275  
         
 276  7
         for ( int row = 0; row < getRowCount(); row++ ){
 277  4
             rows.add(getRowAsParameters(row, replaceNamedParameters));
 278  
         }
 279  
 
 280  3
         return rows;
 281  
     }
 282  
 
 283  
     private Parameters createParameters(Map<String, String> values) {
 284  9
         return new ConvertedParameters(new ChainedRow(new ConvertedParameters(values, parameterConverters), defaults),
 285  
                 parameterConverters);
 286  
     }
 287  
 
 288  
     public String getHeaderSeparator() {
 289  2
         return headerSeparator;
 290  
     }
 291  
 
 292  
     public String getValueSeparator() {
 293  2
         return valueSeparator;
 294  
     }
 295  
 
 296  
     public String asString() {
 297  45
         return tableAsString;
 298  
     }
 299  
 
 300  
     @Override
 301  
     public String toString() {
 302  31
         return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
 303  
     }
 304  
 
 305  
 }