StepCandidate.java

  1. package org.jbehave.core.steps;

  2. import static java.text.MessageFormat.format;

  3. import java.lang.reflect.Method;
  4. import java.util.ArrayList;
  5. import java.util.HashMap;
  6. import java.util.List;
  7. import java.util.Map;

  8. import com.thoughtworks.paranamer.Paranamer;

  9. import org.apache.commons.lang3.StringUtils;
  10. import org.jbehave.core.annotations.AfterScenario.Outcome;
  11. import org.jbehave.core.annotations.Given;
  12. import org.jbehave.core.annotations.Pending;
  13. import org.jbehave.core.annotations.Then;
  14. import org.jbehave.core.annotations.When;
  15. import org.jbehave.core.configuration.Keywords;
  16. import org.jbehave.core.configuration.Keywords.StartingWordNotFound;
  17. import org.jbehave.core.parsers.StepMatcher;

  18. /**
  19.  * A StepCandidate is associated to a Java method annotated with {@link Given},
  20.  * {@link When}, {@link Then} in a steps instance class. The StepCandidate is
  21.  * responsible for matching the textual step against the pattern contained in
  22.  * the method annotation via the {@link StepMatcher} and for the creation of the
  23.  * matched executable step via the {@link StepCreator}.  The name of a StepCandidate
  24.  * is the combination of its starting word and its pattern.
  25.  */
  26. public class StepCandidate {

  27.     public static final String NAME_FORMAT = "{0} {1}";
  28.     private final String patternAsString;
  29.     private final Integer priority;
  30.     private final StepType stepType;
  31.     private final Method method;
  32.     private final Class<?> stepsType;
  33.     private final InjectableStepsFactory stepsFactory;
  34.     private final Keywords keywords;
  35.     private final StepMatcher stepMatcher;
  36.     private final StepCreator stepCreator;
  37.     private final String parameterPrefix;
  38.     private final String[] composedSteps;
  39.     private StepMonitor stepMonitor;

  40.     public StepCandidate(String patternAsString, int priority, StepType stepType, Method method, Class<?> stepsType,
  41.             InjectableStepsFactory stepsFactory, Keywords keywords, StepMatcher stepMatcher,
  42.             String parameterPrefixString, StepCreator stepCreator, String[] steps, StepMonitor stepMonitor) {
  43.         this.patternAsString = patternAsString;
  44.         this.priority = priority;
  45.         this.stepType = stepType;
  46.         this.method = method;
  47.         this.stepsType = stepsType;
  48.         this.stepsFactory = stepsFactory;
  49.         this.keywords = keywords;
  50.         this.stepMatcher = stepMatcher;
  51.         this.stepCreator = stepCreator;
  52.         this.parameterPrefix = parameterPrefixString;
  53.         this.composedSteps = steps;
  54.         this.stepMonitor = stepMonitor;
  55.     }

  56.     public Method getMethod() {
  57.         return method;
  58.     }

  59.     public String getName() {
  60.         return format(NAME_FORMAT, getStartingWord(), getPatternAsString());
  61.     }

  62.     public Integer getPriority() {
  63.         return priority;
  64.     }

  65.     public String getPatternAsString() {
  66.         return patternAsString;
  67.     }

  68.     public Object getStepsInstance() {
  69.         return stepsFactory != null ? stepsFactory.createInstanceOfType(stepsType) : null;
  70.     }

  71.     public Class<?> getStepsType() {
  72.         return stepsType;
  73.     }

  74.     public StepType getStepType() {
  75.         return stepType;
  76.     }

  77.     public String getStartingWord() {
  78.         return keywords.startingWordFor(stepType);
  79.     }

  80.     public String getParameterPrefix() {
  81.         return parameterPrefix;
  82.     }

  83.     public void useStepMonitor(StepMonitor stepMonitor) {
  84.         this.stepMonitor = stepMonitor;
  85.         this.stepCreator.useStepMonitor(stepMonitor);
  86.     }

  87.     public void useParanamer(Paranamer paranamer) {
  88.         this.stepCreator.useParanamer(paranamer);
  89.     }

  90.     public boolean isComposite() {
  91.         return composedSteps != null && composedSteps.length > 0;
  92.     }

  93.     public String[] composedSteps() {
  94.         return composedSteps;
  95.     }

  96.     public boolean ignore(String stepAsString) {
  97.         try {
  98.             String ignoreWord = keywords.startingWordFor(StepType.IGNORABLE);
  99.             return isIgnoredStep(stepAsString, ignoreWord);
  100.         } catch (StartingWordNotFound e) {
  101.             return false;
  102.         }
  103.     }

  104.     public boolean comment(String stepAsString) {
  105.         try {
  106.             String ignoreWord = keywords.startingWordFor(StepType.IGNORABLE);
  107.             return keywords.stepStartsWithWord(stepAsString, ignoreWord) && !isIgnoredStep(stepAsString, ignoreWord);
  108.         } catch (StartingWordNotFound e) {
  109.             return false;
  110.         }
  111.     }

  112.     private boolean isIgnoredStep(String stepAsString, String ignoreWord) {
  113.         for (Map.Entry<StepType, String> stepStartingWord : keywords.startingWordsByType().entrySet()) {
  114.             if (stepStartingWord.getKey() != StepType.IGNORABLE) {
  115.                 if (keywords.stepStartsWithWords(stepAsString, ignoreWord, stepStartingWord.getValue())) {
  116.                     return true;
  117.                 }
  118.             }
  119.         }
  120.         return false;
  121.     }

  122.     public boolean isPending() {
  123.         return method != null && method.isAnnotationPresent(Pending.class);
  124.     }

  125.     public boolean matches(String stepAsString) {
  126.         return matches(stepAsString, null);
  127.     }

  128.     public boolean matches(String step, String previousNonAndStep) {
  129.         try {
  130.             boolean matchesType = true;
  131.             if (keywords.isAndStep(step)) {
  132.                 if (previousNonAndStep == null) {
  133.                     // cannot handle AND step with no previous step
  134.                     matchesType = false;
  135.                 } else {
  136.                     // previous step type should match candidate step type
  137.                     matchesType = stepType == keywords.stepTypeFor(previousNonAndStep);
  138.                 }
  139.             }
  140.             stepMonitor.stepMatchesType(step, previousNonAndStep, matchesType, stepType, method, stepsType);
  141.             boolean matchesPattern = stepMatcher.matcher(stripStartingWord(step)).matches();
  142.             stepMonitor.stepMatchesPattern(step, matchesPattern, stepMatcher.pattern(), method, stepsType);
  143.             // must match both type and pattern
  144.             return matchesType && matchesPattern;
  145.         } catch (StartingWordNotFound e) {
  146.             return false;
  147.         }
  148.     }

  149.     public Step createMatchedStep(String stepAsString, Map<String, String> namedParameters, List<Step> composedSteps) {
  150.         return stepCreator.createParametrisedStep(method, stepAsString, stripStartingWord(stepAsString),
  151.                 namedParameters, composedSteps);
  152.     }

  153.     public Step createMatchedStepUponOutcome(String stepAsString, Map<String, String> namedParameters,
  154.             List<Step> composedSteps, Outcome outcome) {
  155.         return stepCreator.createParametrisedStepUponOutcome(method, stepAsString, stripStartingWord(stepAsString),
  156.                 namedParameters, composedSteps, outcome);
  157.     }

  158.     public void addComposedSteps(List<Step> steps, String stepAsString, Map<String, String> namedParameters,
  159.             List<StepCandidate> allCandidates) {
  160.         Map<String, String> matchedParameters = stepCreator.matchedParameters(method,
  161.                 keywords.stepWithoutStartingWord(stepAsString), namedParameters);

  162.         Map<String, String> mergedParameters = new HashMap<>(namedParameters);
  163.         mergedParameters.putAll(matchedParameters);

  164.         String previousNonAndStep = null;
  165.         for (String composedStep : composedSteps) {
  166.             addComposedStep(steps, composedStep, previousNonAndStep, mergedParameters, allCandidates);
  167.             if (!(keywords.isAndStep(composedStep) || keywords.isIgnorableStep(composedStep))) {
  168.                 // only update previous step if not AND or IGNORABLE step
  169.                 previousNonAndStep = composedStep;
  170.             }
  171.         }
  172.     }

  173.     private void addComposedStep(List<Step> steps, String composedStep, String previousNonAndStep,
  174.             Map<String, String> matchedParameters, List<StepCandidate> allCandidates) {
  175.         if (ignore(composedStep)) {
  176.             // ignorable steps are added so they can be reported
  177.             steps.add(StepCreator.createIgnorableStep(composedStep));
  178.         } else if (comment(composedStep)) {
  179.             // comments are added so they can be reported
  180.             steps.add(StepCreator.createComment(composedStep));
  181.         } else {
  182.             StepCandidate candidate = findComposedCandidate(composedStep, previousNonAndStep, allCandidates);
  183.             if (candidate != null) {
  184.                 List<Step> composedSteps = new ArrayList<>();
  185.                 if (candidate.isComposite()) {
  186.                     // candidate is itself composite: recursively add composed steps
  187.                     candidate.addComposedSteps(composedSteps, composedStep, matchedParameters, allCandidates);
  188.                 }
  189.                 steps.add(candidate.createMatchedStep(composedStep, matchedParameters, composedSteps));
  190.             } else {
  191.                 steps.add(StepCreator.createPendingStep(composedStep, previousNonAndStep));
  192.             }
  193.         }
  194.     }

  195.     private StepCandidate findComposedCandidate(String composedStep, String previousNonAndStep,
  196.             List<StepCandidate> allCandidates) {
  197.         StepType stepType;
  198.         if (keywords.isAndStep(composedStep)) {
  199.             if (previousNonAndStep != null) {
  200.                 stepType = keywords.stepTypeFor(previousNonAndStep);
  201.             } else {
  202.                 // cannot handle AND step with no previous step
  203.                 return null;
  204.             }
  205.         } else {
  206.             stepType = keywords.stepTypeFor(composedStep);
  207.         }
  208.         for (StepCandidate candidate : allCandidates) {
  209.             if (stepType == candidate.getStepType() && (StringUtils.endsWith(composedStep,
  210.                     candidate.getPatternAsString()) || candidate.matches(composedStep, previousNonAndStep))) {
  211.                 return candidate;
  212.             }
  213.         }
  214.         return null;
  215.     }

  216.     protected String stripStartingWord(String stepAsString) {
  217.         return keywords.stepWithoutStartingWord(stepAsString, stepType);
  218.     }

  219.     protected StepCreator getStepCreator() {
  220.         return stepCreator;
  221.     }

  222.     protected Keywords getKeywords() {
  223.         return keywords;
  224.     }

  225.     protected StepMatcher getStepMatcher() {
  226.         return stepMatcher;
  227.     }

  228.     protected StepMonitor getStepMonitor() {
  229.         return stepMonitor;
  230.     }

  231.     @Override
  232.     public String toString() {
  233.         return stepType + " " + patternAsString;
  234.     }

  235. }