Skip to content

Commit

Permalink
JBEHAVE-702: Applied patch from Daniel Scheneller to support variants.
Browse files Browse the repository at this point in the history
  • Loading branch information
maurotalevi committed Jan 19, 2012
1 parent 0da8530 commit 47e49de
Show file tree
Hide file tree
Showing 3 changed files with 668 additions and 358 deletions.
Original file line number Original file line Diff line number Diff line change
@@ -0,0 +1,228 @@
package org.jbehave.core.steps;

import java.util.ArrayList;
import java.util.Arrays;
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)...<br></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)...<br>
* </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("(.*?)?(\\{((.*?)(\\|)?)*?\\})(.*)");

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<String>();

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
String[] tmp = patternGroup.split("\\|");
List<String> split = new ArrayList<String>();
split.addAll(Arrays.asList(tmp));
if (patternGroup.endsWith("|")) {
split.add("");
}

// Iterate over the current pattern's
// variants and construct the result.
for (String s : split) {
StringBuilder b = new StringBuilder();
if (head != null) {
b.append(head);
}
b.append(s);

// 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 b2 = new StringBuilder(b.toString());
b2.append(tailVariant);
variants.add(b2.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<String>(variants);
}
Set<String> compressed = new HashSet<String>();
for (String variant : variants) {
compressed.add(variant.replaceAll("\\s{2,}", " "));
}
return compressed;
}

}
Loading

0 comments on commit 47e49de

Please sign in to comment.