##### Coverage Report - org.jbehave.core.steps.PatternVariantBuilder

Classes in this File | Line Coverage | Branch Coverage | Complexity
PatternVariantBuilder |
100% | 39/39
92% | 13/14
| 2.8;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 | 81 |
         private final Pattern regex = Pattern.compile("(.*?)?(\\{((.*?)(\\|)?)*?\\})(.*)");
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 | 81 |
         public PatternVariantBuilder(String input) {
116 | 81 |
             this.input = input;
117 | 81 |
             this.variants = variantsFor(input);
118 | 81 |
         }
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 | 104 |
             Set<String> variants = new HashSet<String>();
139 |  |
140 | 104 |
             Matcher m = regex.matcher(input);
141 | 104 |
             boolean matches = m.matches();
142 |  |
143 | 104 |
             if (!matches) {
144 |  |
                 // if the regex does not find any patterns,
145 |  |
                 // simply add the input as is
146 | 93 |
                 variants.add(input);
147 |  |
                 // end recursion
148 | 93 |
                 return variants;
149 |  |
             }
150 |  |
151 |  |
             // isolate the part before the first pattern
152 | 11 |
             String head = m.group(1);
153 |  |
154 |  |
             // isolate the pattern itself, removing its wrapping {}
155 | 11 |
             String patternGroup = m.group(2).replaceAll("[\\{\\}]", "");
156 |  |
157 |  |
             // isolate the remaining part of the input
158 | 11 |
             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 | 11 |
             List<String> patternParts = new ArrayList<String>();
163 | 11 |
             patternParts.addAll(asList(patternGroup.split("\\|")));
164 | 11 |
             if (patternGroup.endsWith("|")) {
165 | 6 |
                 patternParts.add("");
166 |  |
             }
167 |  |
168 |  |
             // Iterate over the current pattern's
169 |  |
             // variants and construct the result.
170 | 11 |
             for (String part : patternParts) {
171 | 23 |
                 StringBuilder builder = new StringBuilder();
172 | 23 |
                 if (head != null) {
173 | 23 |
                     builder.append(head);
174 |  |
                 }
175 | 23 |
                 builder.append(part);
176 |  |
177 |  |
                 // recurse on the tail of the input
178 |  |
                 // to handle the next pattern
179 | 23 |
                 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 | 23 |
                 for (String tailVariant : tails) {
185 | 29 |
                     StringBuilder tailBuilder = new StringBuilder(builder.toString());
186 | 29 |
                     tailBuilder.append(tailVariant);
187 | 29 |
                     variants.add(tailBuilder.toString());
188 | 29 |
                 }
189 | 23 |
             }
190 | 11 |
             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 | 80 |
             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 | 81 |
             if (!compressWhitespace) {
218 | 80 |
                 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 |  |
             }
224 | 1 |
             return compressed;
225 |  |
         }
226 |  |
227 |  |
     }