PatternVariantBuilder.java
- package org.jbehave.core.steps;
- import static java.util.Arrays.asList;
- import java.util.ArrayList;
- import java.util.HashSet;
- import java.util.List;
- import java.util.Set;
- import java.util.regex.Matcher;
- import java.util.regex.Pattern;
- /**
- * <p>
- * Builds a set of pattern variants of given pattern input, supporting a custom
- * directives. Depending on the directives present, one or more resulting
- * variants are created.
- * </p>
- * <p>
- * Currently supported directives are
- * </p>
- * <table border="1">
- * <thead>
- * <tr>
- * <td>Pattern</td>
- * <td>Result</td>
- * </tr>
- * </thead> <tbody>
- * <tr>
- * <td>..A {x|y} B..</td>
- * <td>
- * <ul>
- * <li>..A x B..</li>
- * <li>..A y B..</li>
- * </ul>
- * </td>
- * </tr>
- * <tr>
- * <td>..A {x|y|} B..</td>
- * <td>
- * <ul>
- * <li>..A x B..</li>
- * <li>..A y B..</li>
- * <li>..A B..</li>
- * </ul>
- * </td>
- * </tr>
- * <tr>
- * <td>..A {x} B..</td>
- * <td>
- * <ul>
- * <li>..A x B..</li>
- * </ul>
- * </td>
- * </tr>
- * </table>
- * <p>
- * These directives can be used to conveniently create several variants of a
- * step pattern, without having to repeat it as a whole as one or more aliases.
- * </p>
- * <p>
- * Examples:
- * </p>
- * <ul>
- * <li>
- * <p>
- * <code>@Then("the result {must |has to |}be $x")<br> public void checkResult(int x)...</code>
- * </p>
- * <p>
- * Would match any of these variants from a story file:
- * <ul>
- * <li>Then the result must be 42</li>
- * <li>Then the result has to be 42</li>
- * <li>Then the result be 42</li>
- * </ul>
- * </p>
- * </li>
- * <li>
- * <p>
- * <code>@When("$A {+|plus|is added to} $B")<br> public void add(int A, int B)...</code>
- * </p>
- * <p>
- * Would match any of these variants from a story file:
- * <ul>
- * <li>When 42 + 23</li>
- * <li>When 42 plus 23</li>
- * <li>When 42 is added to 23</li>
- * </ul>
- * </p>
- * </li>
- * </ul>
- *
- * @author Daniel Schneller
- */
- public class PatternVariantBuilder {
- /**
- * Regular expression that locates patterns to be evaluated in the input
- * pattern.
- */
- private final Pattern regex = Pattern.compile("([^\\n{]*+)(\\{(([^|}]++)(\\|)?+)*+\\})([^\\n]*+)");
- private final Set<String> variants;
- private final String input;
- /**
- * Creates a builder and calculates all variants for given input. When there
- * are no variants found in the input, it will itself be the only result.
- *
- * @param input to be evaluated
- */
- public PatternVariantBuilder(String input) {
- this.input = input;
- this.variants = variantsFor(input);
- }
- public String getInput() {
- return input;
- }
- /**
- * <p>
- * Parses the {@link #input} received at construction and generates the
- * variants. When there are multiple patterns in the input, the method will
- * recurse on itself to generate the variants for the tailing end after the
- * first matched pattern.
- * </p>
- * <p>
- * Generated variants are stored in a {@link Set}, so there will never be
- * any duplicates, even if the input's patterns were to result in such.
- * </p>
- */
- private Set<String> variantsFor(String input) {
- // Store current invocation's results
- Set<String> variants = new HashSet<>();
- Matcher m = regex.matcher(input);
- boolean matches = m.matches();
- if (!matches) {
- // if the regex does not find any patterns,
- // simply add the input as is
- variants.add(input);
- // end recursion
- return variants;
- }
- // isolate the part before the first pattern
- String head = m.group(1);
- // isolate the pattern itself, removing its wrapping {}
- String patternGroup = m.group(2).replaceAll("[\\{\\}]", "");
- // isolate the remaining part of the input
- String tail = m.group(6);
- // split the pattern into its options and add an empty
- // string if it ends with a separator
- List<String> patternParts = new ArrayList<>();
- patternParts.addAll(asList(patternGroup.split("\\|")));
- if (patternGroup.endsWith("|")) {
- patternParts.add("");
- }
- // Iterate over the current pattern's
- // variants and construct the result.
- for (String part : patternParts) {
- StringBuilder builder = new StringBuilder();
- if (head != null) {
- builder.append(head);
- }
- builder.append(part);
- // recurse on the tail of the input
- // to handle the next pattern
- Set<String> tails = variantsFor(tail);
- // append all variants of the tail end
- // and add each of them to the part we have
- // built up so far.
- for (String tailVariant : tails) {
- StringBuilder tailBuilder = new StringBuilder(builder.toString());
- tailBuilder.append(tailVariant);
- variants.add(tailBuilder.toString());
- }
- }
- return variants;
- }
- /**
- * Returns a new copy set of all variants with no whitespace compression.
- *
- * @return a {@link Set} of all variants without whitespace compression
- * @see #allVariants(boolean)
- */
- public Set<String> allVariants() {
- return allVariants(false);
- }
- /**
- * <p>
- * Returns a new copy set of all variants. Any two or more consecutive white
- * space characters will be condensed into a single space if boolean flag is
- * set.
- * </p>
- * <p>
- * Otherwise, any whitespace will be left as is.
- * </p>
- *
- * @param compressWhitespace whether or not to compress whitespace
- * @return a {@link Set} of all variants
- */
- public Set<String> allVariants(boolean compressWhitespace) {
- if (!compressWhitespace) {
- return new HashSet<>(variants);
- }
- Set<String> compressed = new HashSet<>();
- for (String variant : variants) {
- compressed.add(variant.replaceAll("\\s{2,}", " "));
- }
- return compressed;
- }
- }