Coverage Report - org.jbehave.core.steps.PatternVariantBuilder
 
Classes in this File Line Coverage Branch Coverage Complexity
PatternVariantBuilder
100%
40/40
92%
13/14
2.8
 
 1  
 package org.jbehave.core.steps;
 2  
 
 3  
 import java.util.ArrayList;
 4  
 import java.util.HashSet;
 5  
 import java.util.List;
 6  
 import java.util.Set;
 7  
 import java.util.regex.Matcher;
 8  
 import java.util.regex.Pattern;
 9  
 
 10  
 import static java.util.Arrays.asList;
 11  
 
 12  
 /**
 13  
  * <p>
 14  
  * Builds a set of pattern variants of given pattern input, supporting a custom
 15  
  * directives. Depending on the directives present, one or more resulting
 16  
  * variants are created.
 17  
  * </p>
 18  
  * <p>
 19  
  * Currently supported directives are
 20  
  * </p>
 21  
  * <table border="1">
 22  
  * <thead>
 23  
  * <tr>
 24  
  * <td>Pattern</td>
 25  
  * <td>Result</td>
 26  
  * </tr>
 27  
  * </thead> <tbody>
 28  
  * <tr>
 29  
  * <td>..A {x|y} B..</td>
 30  
  * <td>
 31  
  * <ul>
 32  
  * <li>..A x B..</li>
 33  
  * <li>..A y B..</li>
 34  
  * </ul>
 35  
  * </td>
 36  
  * </tr>
 37  
  * <tr>
 38  
  * <td>..A {x|y|} B..</td>
 39  
  * <td>
 40  
  * <ul>
 41  
  * <li>..A x B..</li>
 42  
  * <li>..A y B..</li>
 43  
  * <li>..A B..</li>
 44  
  * </ul>
 45  
  * </td>
 46  
  * </tr>
 47  
  * <tr>
 48  
  * <td>..A {x} B..</td>
 49  
  * <td>
 50  
  * <ul>
 51  
  * <li>..A x B..</li>
 52  
  * </ul>
 53  
  * </td>
 54  
  * </tr>
 55  
  * </table>
 56  
  * 
 57  
  * <p>
 58  
  * These directives can be used to conveniently create several variants of a
 59  
  * step pattern, without having to repeat it as a whole as one or more aliases.
 60  
  * </p>
 61  
  * <p>
 62  
  * Examples:
 63  
  * </p>
 64  
  * <ul>
 65  
  * <li>
 66  
  * <p>
 67  
  * <code>
 68  
  * 
 69  
  * @Then("the result {must |has to |}be $x")<br> public void checkResult(int
 70  
  *            x)...<br></code>
 71  
  *            </p>
 72  
  *            <p>
 73  
  *            Would match any of these variants from a story file:
 74  
  *            <ul>
 75  
  *            <li>Then the result must be 42</li> <li>Then the result has to be
 76  
  *            42</li> <li>Then the result be 42</li>
 77  
  *            </ul>
 78  
  *            </p>
 79  
  *            </li> <li>
 80  
  *            <p>
 81  
  *            <code>
 82  
  * @When("$A {+|plus|is added to} $B")<br> public void add(int A, int B)...<br>
 83  
  *           </code>
 84  
  *           </p>
 85  
  *           <p>
 86  
  *           Would match any of these variants from a story file:
 87  
  *           <ul>
 88  
  *           <li>When 42 + 23</li> <li>When 42 plus 23</li> <li>When 42 is added
 89  
  *           to 23</li>
 90  
  *           </ul>
 91  
  *           </p>
 92  
  *           </li>
 93  
  *           </ul>
 94  
  * 
 95  
  * @author Daniel Schneller
 96  
  */
 97  
 public class PatternVariantBuilder {
 98  
 
 99  
     /**
 100  
      * Regular expression that locates patterns to be evaluated in the input
 101  
      * pattern.
 102  
      */
 103  92
     private final Pattern regex = Pattern.compile("([^\\n{]*+)(\\{(([^|}]++)(\\|)?+)*+\\})([^\\n]*+)");
 104  
 
 105  
     private final Set<String> variants;
 106  
 
 107  
     private final String input;
 108  
 
 109  
     /**
 110  
      * Creates a builder and calculates all variants for given input. When there
 111  
      * are no variants found in the input, it will itself be the only result.
 112  
      * 
 113  
      * @param input to be evaluated
 114  
      */
 115  92
     public PatternVariantBuilder(String input) {
 116  92
         this.input = input;
 117  92
         this.variants = variantsFor(input);
 118  92
     }
 119  
 
 120  
     public String getInput() {
 121  2
         return input;
 122  
     }
 123  
 
 124  
     /**
 125  
      * <p>
 126  
      * Parses the {@link #input} received at construction and generates the
 127  
      * variants. When there are multiple patterns in the input, the method will
 128  
      * recurse on itself to generate the variants for the tailing end after the
 129  
      * first matched pattern.
 130  
      * </p>
 131  
      * <p>
 132  
      * Generated variants are stored in a {@link Set}, so there will never be
 133  
      * any duplicates, even if the input's patterns were to result in such.
 134  
      * </p>
 135  
      */
 136  
     private Set<String> variantsFor(String input) {
 137  
         // Store current invocation's results
 138  118
         Set<String> variants = new HashSet<String>();
 139  
 
 140  118
         Matcher m = regex.matcher(input);
 141  118
         boolean matches = m.matches();
 142  
 
 143  118
         if (!matches) {
 144  
             // if the regex does not find any patterns,
 145  
             // simply add the input as is
 146  106
             variants.add(input);
 147  
             // end recursion
 148  106
             return variants;
 149  
         }
 150  
 
 151  
         // isolate the part before the first pattern
 152  12
         String head = m.group(1);
 153  
 
 154  
         // isolate the pattern itself, removing its wrapping {}
 155  12
         String patternGroup = m.group(2).replaceAll("[\\{\\}]", "");
 156  
 
 157  
         // isolate the remaining part of the input
 158  12
         String tail = m.group(6);
 159  
 
 160  
         // split the pattern into its options and add an empty
 161  
         // string if it ends with a separator
 162  12
         List<String> patternParts = new ArrayList<String>();
 163  12
         patternParts.addAll(asList(patternGroup.split("\\|")));
 164  12
         if (patternGroup.endsWith("|")) {
 165  6
             patternParts.add("");
 166  
         }
 167  
 
 168  
         // Iterate over the current pattern's
 169  
         // variants and construct the result.
 170  12
         for (String part : patternParts) {
 171  26
             StringBuilder builder = new StringBuilder();
 172  26
             if (head != null) {
 173  26
                 builder.append(head);
 174  
             }
 175  26
             builder.append(part);
 176  
 
 177  
             // recurse on the tail of the input
 178  
             // to handle the next pattern
 179  26
             Set<String> tails = variantsFor(tail);
 180  
 
 181  
             // append all variants of the tail end
 182  
             // and add each of them to the part we have
 183  
             // built up so far.
 184  26
             for (String tailVariant : tails) {
 185  32
                 StringBuilder tailBuilder = new StringBuilder(builder.toString());
 186  32
                 tailBuilder.append(tailVariant);
 187  32
                 variants.add(tailBuilder.toString());
 188  32
             }
 189  26
         }
 190  12
         return variants;
 191  
     }
 192  
 
 193  
     /**
 194  
      * Returns a new copy set of all variants with no whitespace compression.
 195  
      * 
 196  
      * @return a {@link Set} of all variants without whitespace compression
 197  
      * @see #allVariants(boolean)
 198  
      */
 199  
     public Set<String> allVariants() {
 200  91
         return allVariants(false);
 201  
     }
 202  
 
 203  
     /**
 204  
      * <p>
 205  
      * Returns a new copy set of all variants. Any two or more consecutive white
 206  
      * space characters will be condensed into a single space if boolean flag is
 207  
      * set.
 208  
      * </p>
 209  
      * <p>
 210  
      * Otherwise, any whitespace will be left as is.
 211  
      * </p>
 212  
      * 
 213  
      * @param compressWhitespace whether or not to compress whitespace
 214  
      * @return a {@link Set} of all variants
 215  
      */
 216  
     public Set<String> allVariants(boolean compressWhitespace) {
 217  92
         if (!compressWhitespace) {
 218  91
             return new HashSet<String>(variants);
 219  
         }
 220  1
         Set<String> compressed = new HashSet<String>();
 221  1
         for (String variant : variants) {
 222  4
             compressed.add(variant.replaceAll("\\s{2,}", " "));
 223  4
         }
 224  1
         return compressed;
 225  
     }
 226  
 
 227  
 }