package org.jbehave.core.steps;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.jbehave.core.annotations.AfterScenario.Outcome;
import org.jbehave.core.annotations.Given;
import org.jbehave.core.annotations.Pending;
import org.jbehave.core.annotations.Then;
import org.jbehave.core.annotations.When;
import org.jbehave.core.configuration.Keywords;
import org.jbehave.core.configuration.Keywords.StartingWordNotFound;
import org.jbehave.core.parsers.StepMatcher;
import org.jbehave.core.parsers.StepPatternParser;
import org.jbehave.core.steps.context.StepsContext;
import com.thoughtworks.paranamer.Paranamer;
/**
* A StepCandidate is associated to a Java method annotated with {@link Given},
* {@link When}, {@link Then} in a steps instance class. The StepCandidate is
* responsible for matching the textual step against the pattern contained in
* the method annotation via the {@link StepMatcher} and for the creation of the
* matched executable step via the {@link StepCreator}.
*/
public class StepCandidate {
private final String patternAsString;
private final Integer priority;
private final StepType stepType;
private final Method method;
private final Class<?> stepsType;
private final InjectableStepsFactory stepsFactory;
private final Keywords keywords;
private final StepMatcher stepMatcher;
private final StepCreator stepCreator;
private String[] composedSteps;
private StepMonitor stepMonitor = new SilentStepMonitor();
public StepCandidate(String patternAsString, int priority, StepType stepType, Method method, Class<?> stepsType,
InjectableStepsFactory stepsFactory, StepsContext stepsContext, Keywords keywords,
StepPatternParser stepPatternParser, ParameterConverters parameterConverters, ParameterControls parameterControls) {
this.patternAsString = patternAsString;
this.priority = priority;
this.stepType = stepType;
this.method = method;
this.stepsType = stepsType;
this.stepsFactory = stepsFactory;
this.keywords = keywords;
this.stepMatcher = stepPatternParser.parseStep(stepType, patternAsString);
this.stepCreator = new StepCreator(stepsType, stepsFactory, stepsContext, parameterConverters,
parameterControls, stepMatcher, stepMonitor);
}
public Method getMethod() {
return method;
}
public Integer getPriority() {
return priority;
}
public String getPatternAsString() {
return patternAsString;
}
public Object getStepsInstance() {
return stepsFactory.createInstanceOfType(stepsType);
}
public Class<?> getStepsType() {
return stepsType;
}
public StepType getStepType() {
return stepType;
}
public String getStartingWord() {
return keywords.startingWordFor(stepType);
}
public void useStepMonitor(StepMonitor stepMonitor) {
this.stepMonitor = stepMonitor;
this.stepCreator.useStepMonitor(stepMonitor);
}
public void doDryRun(boolean dryRun) {
this.stepCreator.doDryRun(dryRun);
}
public void useParanamer(Paranamer paranamer) {
this.stepCreator.useParanamer(paranamer);
}
public void composedOf(String[] steps) {
this.composedSteps = steps;
}
public boolean isComposite() {
return composedSteps != null && composedSteps.length > 0;
}
public String[] composedSteps() {
return composedSteps;
}
public boolean ignore(String stepAsString) {
try {
String ignoreWord = keywords.startingWordFor(StepType.IGNORABLE);
return isIgnoredStep(stepAsString, ignoreWord);
} catch (StartingWordNotFound e) {
return false;
}
}
public boolean comment(String stepAsString) {
try {
String ignoreWord = keywords.startingWordFor(StepType.IGNORABLE);
return keywords.stepStartsWithWord(stepAsString, ignoreWord) && !isIgnoredStep(stepAsString, ignoreWord);
} catch (StartingWordNotFound e) {
return false;
}
}
private boolean isIgnoredStep(String stepAsString, String ignoreWord) {
for (Map.Entry<StepType, String> stepStartingWord : keywords.startingWordsByType().entrySet()) {
if (stepStartingWord.getKey() != StepType.IGNORABLE) {
if (keywords.stepStartsWithWords(stepAsString, ignoreWord, stepStartingWord.getValue())) {
return true;
}
}
}
return false;
}
public boolean isPending() {
return method.isAnnotationPresent(Pending.class);
}
public boolean matches(String stepAsString) {
return matches(stepAsString, null);
}
public boolean matches(String step, String previousNonAndStep) {
try {
boolean matchesType = true;
if (isAndStep(step)) {
if (previousNonAndStep == null) {
// cannot handle AND step with no previous step
matchesType = false;
} else {
// previous step type should match candidate step type
matchesType = keywords.startingWordFor(stepType).equals(findStartingWord(previousNonAndStep));
}
}
stepMonitor.stepMatchesType(step, previousNonAndStep, matchesType, stepType, method, stepsType);
boolean matchesPattern = stepMatcher.matches(stripStartingWord(step));
stepMonitor.stepMatchesPattern(step, matchesPattern, stepMatcher.pattern(), method, stepsType);
// must match both type and pattern
return matchesType && matchesPattern;
} catch (StartingWordNotFound e) {
return false;
}
}
public Step createMatchedStep(String stepAsString, Map<String, String> namedParameters) {
return stepCreator.createParametrisedStep(method, stepAsString, stripStartingWord(stepAsString),
namedParameters);
}
public Step createMatchedStepUponOutcome(String stepAsString, Map<String, String> namedParameters, Outcome outcome) {
return stepCreator.createParametrisedStepUponOutcome(method, stepAsString, stripStartingWord(stepAsString),
namedParameters, outcome);
}
public void addComposedSteps(List<Step> steps, String stepAsString, Map<String, String> namedParameters,
List<StepCandidate> allCandidates) {
addComposedStepsRecursively(steps, stepAsString, namedParameters, allCandidates, composedSteps);
}
private void addComposedStepsRecursively(List<Step> steps, String stepAsString,
Map<String, String> namedParameters, List<StepCandidate> allCandidates, String[] composedSteps) {
Map<String, String> matchedParameters = stepCreator.matchedParameters(method, stepAsString,
stripStartingWord(stepAsString), namedParameters);
matchedParameters.putAll(namedParameters);
for (String composedStep : composedSteps) {
addComposedStep(steps, composedStep, matchedParameters, allCandidates);
}
}
private void addComposedStep(List<Step> steps, String composedStep, Map<String, String> matchedParameters,
List<StepCandidate> allCandidates) {
StepCandidate candidate = findComposedCandidate(composedStep, allCandidates);
if (candidate != null) {
steps.add(candidate.createMatchedStep(composedStep, matchedParameters));
if (candidate.isComposite()) {
// candidate is itself composite: recursively add composed steps
addComposedStepsRecursively(steps, composedStep, matchedParameters, allCandidates,
candidate.composedSteps());
}
} else {
steps.add(StepCreator.createPendingStep(composedStep, null));
}
}
private StepCandidate findComposedCandidate(String composedStep, List<StepCandidate> allCandidates) {
for (StepCandidate candidate : allCandidates) {
if (StringUtils.startsWith(composedStep, candidate.getStartingWord())
&& (StringUtils.endsWith(composedStep, candidate.getPatternAsString()) || candidate
.matches(composedStep))) {
return candidate;
}
}
return null;
}
public boolean isAndStep(String stepAsString) {
return keywords.isAndStep(stepAsString);
}
public boolean isIgnorableStep(String stepAsString) {
return keywords.isIgnorableStep(stepAsString);
}
private String findStartingWord(String stepAsString) {
return keywords.startingWord(stepAsString, stepType);
}
private String stripStartingWord(String stepAsString) {
return keywords.stepWithoutStartingWord(stepAsString, stepType);
}
@Override
public String toString() {
return stepType + " " + patternAsString;
}
}