RegexPrefixCapturingPatternParser.java

  1. package org.jbehave.core.parsers;

  2. import java.util.ArrayList;
  3. import java.util.List;
  4. import java.util.regex.Matcher;
  5. import java.util.regex.Pattern;

  6. import org.apache.commons.lang3.builder.ToStringBuilder;
  7. import org.apache.commons.lang3.builder.ToStringStyle;
  8. import org.jbehave.core.steps.StepType;

  9. /**
  10.  * <p>A step pattern parser that provides a step matcher which will capture
  11.  * parameters starting with the given prefix in any matching step. Default
  12.  * prefix is $.</p>
  13.  *
  14.  * <p>The parameter names are by default assumed to be any unicode-supported
  15.  * alphanumeric sequence, not limited to ASCII (see
  16.  * http://www.regular-expressions.info/unicode.html), i.e. corresponding to
  17.  * character class [\p{L}\p{N}\p{Pc}]. A different character class can optionally be
  18.  * provided.</p>
  19.  *
  20.  * @author Elizabeth Keogh
  21.  * @author Mauro Talevi
  22.  */
  23. public class RegexPrefixCapturingPatternParser implements StepPatternParser {

  24.     /**
  25.      * The default prefix to identify parameter names
  26.      */
  27.     private static final String DEFAULT_PREFIX = "$";
  28.     /**
  29.      * The default character class to match the parameter names.
  30.      */
  31.     private static final String DEFAULT_CHARACTER_CLASS = "[\\p{L}\\p{N}\\p{Pc}]";

  32.     private final String prefix;
  33.     private final String characterClass;

  34.     /**
  35.      * Creates a parser which captures parameters starting with $ in a matching
  36.      * step and whose names are alphanumeric sequences.
  37.      */
  38.     public RegexPrefixCapturingPatternParser() {
  39.         this(DEFAULT_PREFIX);
  40.     }

  41.     /**
  42.      * Creates a parser which captures parameters starting with a given prefix
  43.      * in a matching step and whose names are alphanumeric sequences
  44.      *
  45.      * @param prefix
  46.      *            the prefix to use in capturing parameters
  47.      */
  48.     public RegexPrefixCapturingPatternParser(String prefix) {
  49.         this(prefix, DEFAULT_CHARACTER_CLASS);
  50.     }

  51.     /**
  52.      * Creates a parser which captures parameters starting with a given prefix
  53.      * in a matching step and a given character class.
  54.      *
  55.      * @param prefix
  56.      *            the prefix to use in capturing parameters
  57.      * @param characterClass
  58.      *            the regex character class to find parameter names
  59.      */
  60.     public RegexPrefixCapturingPatternParser(String prefix,
  61.             String characterClass) {
  62.         this.prefix = prefix;
  63.         this.characterClass = characterClass;
  64.     }

  65.     public String getPrefix() {
  66.         return prefix;
  67.     }

  68.     @Override
  69.     public StepMatcher parseStep(StepType stepType, String stepPattern) {
  70.         String escapingPunctuation = escapingPunctuation(stepPattern);
  71.         List<Parameter> parameters = findParameters(escapingPunctuation);
  72.         Pattern regexPattern = buildPattern(escapingPunctuation, parameters);
  73.         return new RegexStepMatcher(stepType, escapingPunctuation, regexPattern,
  74.                 parameterNames(parameters));
  75.     }

  76.     private Pattern buildPattern(String stepPattern, List<Parameter> parameters) {
  77.         return Pattern.compile(
  78.                 parameterCapturingRegex(stepPattern, parameters),
  79.                 Pattern.DOTALL);
  80.     }

  81.     private String[] parameterNames(List<Parameter> parameters) {
  82.         List<String> names = new ArrayList<>();
  83.         for (Parameter parameter : parameters) {
  84.             names.add(parameter.name);
  85.         }
  86.         return names.toArray(new String[names.size()]);
  87.     }

  88.     private List<Parameter> findParameters(String pattern) {
  89.         List<Parameter> parameters = new ArrayList<>();
  90.         Matcher findingAllParameterNames = findingAllParameterNames().matcher(
  91.                 pattern);
  92.         while (findingAllParameterNames.find()) {
  93.             parameters.add(new Parameter(pattern, findingAllParameterNames
  94.                     .start(), findingAllParameterNames.end(),
  95.                     findingAllParameterNames.group(2)));
  96.         }
  97.         return parameters;
  98.     }

  99.     private Pattern findingAllParameterNames() {
  100.         return Pattern.compile("(\\" + prefix + characterClass + "*)(\\W|\\Z)",
  101.                 Pattern.DOTALL);
  102.     }

  103.     private String escapingPunctuation(String pattern) {
  104.         return pattern.replaceAll("([\\[\\]\\{\\}\\?\\^\\.\\*\\(\\)\\+\\\\])",
  105.                 "\\\\$1");
  106.     }

  107.     private String ignoringWhitespace(String pattern) {
  108.         return pattern.replaceAll("\\s+", "\\\\s+");
  109.     }

  110.     private String parameterCapturingRegex(String stepPattern,
  111.             List<Parameter> parameters) {
  112.         String regex = stepPattern;
  113.         String capture = "(.*)";
  114.         for (int i = parameters.size(); i > 0; i--) {
  115.             Parameter parameter = parameters.get(i - 1);
  116.             String start = regex.substring(0, parameter.start);
  117.             String end = regex.substring(parameter.end);
  118.             String whitespaceIfAny = parameter.whitespaceIfAny;
  119.             regex = start + capture + whitespaceIfAny + end;
  120.         }
  121.         return ignoringWhitespace(regex);
  122.     }

  123.     private class Parameter {
  124.         private final int start;
  125.         private final int end;
  126.         private final String whitespaceIfAny;
  127.         private final String name;

  128.         public Parameter(String pattern, int start, int end,
  129.                 String whitespaceIfAny) {
  130.             this.start = start;
  131.             this.end = end;
  132.             this.whitespaceIfAny = whitespaceIfAny;
  133.             this.name = pattern.substring(start + prefix.length(),
  134.                     end - whitespaceIfAny.length()).trim();
  135.         }

  136.         @Override
  137.         public String toString() {
  138.             return name;
  139.         }
  140.     }

  141.     @Override
  142.     public String toString() {
  143.         return ToStringBuilder.reflectionToString(this,
  144.                 ToStringStyle.SHORT_PREFIX_STYLE);
  145.     }
  146. }