Coverage Report - org.jbehave.core.steps.StepCandidate
 
Classes in this File Line Coverage Branch Coverage Complexity
StepCandidate
98%
75/76
86%
26/30
1.786
StepCandidate$StartingWordNotFound
100%
4/4
N/A
1.786
 
 1  
 package org.jbehave.core.steps;
 2  
 
 3  
 import java.lang.reflect.Method;
 4  
 import java.util.ArrayList;
 5  
 import java.util.List;
 6  
 import java.util.Map;
 7  
 
 8  
 import org.apache.commons.lang.StringUtils;
 9  
 import org.jbehave.core.annotations.Given;
 10  
 import org.jbehave.core.annotations.Pending;
 11  
 import org.jbehave.core.annotations.Then;
 12  
 import org.jbehave.core.annotations.When;
 13  
 import org.jbehave.core.parsers.StepMatcher;
 14  
 import org.jbehave.core.parsers.StepPatternParser;
 15  
 
 16  
 import com.thoughtworks.paranamer.Paranamer;
 17  
 
 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}.
 24  
  */
 25  
 public class StepCandidate {
 26  
 
 27  
     private final String patternAsString;
 28  
     private final Integer priority;
 29  
     private final StepType stepType;
 30  
     private final Method method;
 31  
     private final Object stepsInstance;
 32  
     private final Map<StepType, String> startingWordsByType;
 33  
     private final StepMatcher stepMatcher;
 34  
     private final StepCreator stepCreator;
 35  84
     private StepMonitor stepMonitor = new SilentStepMonitor();
 36  
     private String[] composedSteps;
 37  
 
 38  
     public StepCandidate(String patternAsString, int priority, StepType stepType, Method method, Object stepsInstance,
 39  
             Map<StepType, String> startingWordsByType, StepPatternParser stepPatternParser,
 40  84
             ParameterConverters parameterConverters) {
 41  84
         this.patternAsString = patternAsString;
 42  84
         this.priority = priority;
 43  84
         this.stepType = stepType;
 44  84
         this.method = method;
 45  84
         this.stepsInstance = stepsInstance;
 46  84
         this.startingWordsByType = startingWordsByType;
 47  84
         this.stepMatcher = stepPatternParser.parseStep(stepType, patternAsString);
 48  84
         this.stepCreator = new StepCreator(stepsInstance, parameterConverters, stepMatcher, stepMonitor);
 49  84
     }
 50  
 
 51  
     public Method getMethod() {
 52  7
         return method;
 53  
     }
 54  
 
 55  
     public Integer getPriority() {
 56  1
         return priority;
 57  
     }
 58  
 
 59  
     public String getPatternAsString() {
 60  32
         return patternAsString;
 61  
     }
 62  
 
 63  
     public Object getStepsInstance() {
 64  7
         return stepsInstance;
 65  
     }
 66  
 
 67  
     public StepType getStepType() {
 68  102
         return stepType;
 69  
     }
 70  
 
 71  
     public String getStartingWord() {
 72  17
         return startingWordFor(stepType);
 73  
     }
 74  
 
 75  
     public void useStepMonitor(StepMonitor stepMonitor) {
 76  55
         this.stepMonitor = stepMonitor;
 77  55
         this.stepCreator.useStepMonitor(stepMonitor);
 78  55
     }
 79  
 
 80  
     public void doDryRun(boolean dryRun) {
 81  55
         this.stepCreator.doDryRun(dryRun);
 82  55
     }
 83  
 
 84  
     public void useParanamer(Paranamer paranamer) {
 85  58
         this.stepCreator.useParanamer(paranamer);
 86  58
     }
 87  
 
 88  
     public void composedOf(String[] steps) {
 89  2
         this.composedSteps = steps;        
 90  2
     }
 91  
     
 92  
     public boolean isComposite() {
 93  2
         return composedSteps != null && composedSteps.length > 0;
 94  
     }
 95  
 
 96  
     public boolean ignore(String stepAsString) {
 97  
         try {
 98  2
             String ignoreWord = startingWordFor(StepType.IGNORABLE);
 99  1
             return stepStartsWithWord(stepAsString, ignoreWord);
 100  1
         } catch (StartingWordNotFound e) {
 101  1
             return false;
 102  
         }
 103  
     }
 104  
     
 105  
     public boolean isPending(){
 106  2
         return method.isAnnotationPresent(Pending.class);
 107  
     }
 108  
 
 109  
     public boolean matches(String stepAsString) {
 110  14
         return matches(stepAsString, null);
 111  
     }
 112  
 
 113  
     public boolean matches(String step, String previousNonAndStep) {
 114  
         try {
 115  15
             boolean matchesType = true;
 116  15
             if (isAndStep(step)) {
 117  2
                 if (previousNonAndStep == null) {
 118  
                     // cannot handle AND step with no previous step
 119  1
                     matchesType = false;
 120  
                 } else {
 121  
                     // previous step type should match candidate step type
 122  1
                     matchesType = startingWordFor(stepType).equals(findStartingWord(previousNonAndStep));
 123  
                 }
 124  
             }
 125  14
             stepMonitor.stepMatchesType(step, previousNonAndStep, matchesType, stepType, method, stepsInstance);
 126  14
             boolean matchesPattern = stepMatcher.matches(stripStartingWord(step));
 127  10
             stepMonitor.stepMatchesPattern(step, matchesPattern, stepMatcher.pattern(), method, stepsInstance);
 128  
             // must match both type and pattern 
 129  10
             return matchesType && matchesPattern;
 130  5
         } catch (StartingWordNotFound e) {
 131  5
             return false;
 132  
         }
 133  
     }
 134  
 
 135  
     public boolean isAndStep(String stepAsString) {
 136  15
         String andWord = startingWordFor(StepType.AND);
 137  14
         return stepStartsWithWord(stepAsString, andWord);
 138  
     }
 139  
 
 140  
     public Step createMatchedStep(String stepAsString, Map<String, String> namedParameters) {
 141  62
         return stepCreator.createParametrisedStep(method, stepAsString, stripStartingWord(stepAsString), namedParameters);
 142  
     }
 143  
     
 144  
     public List<Step> createComposedSteps(String stepAsString, Map<String, String> namedParameters,
 145  
             List<StepCandidate> allCandidates) {
 146  
         Map<String, String> matchedParameters;
 147  2
         if ( namedParameters.isEmpty() ){
 148  1
             matchedParameters = stepCreator.matchedParameters(method, stepAsString, stripStartingWord(stepAsString), namedParameters);
 149  
         } else {
 150  1
             matchedParameters = namedParameters;
 151  
         }
 152  2
         List<Step> steps = new ArrayList<Step>();
 153  6
         for (String composedStep : composedSteps) {
 154  4
             StepCandidate candidate = findComposedCandidate(composedStep, allCandidates);
 155  4
             if ( candidate != null ){
 156  4
                 steps.add(candidate.createMatchedStep(composedStep, matchedParameters));
 157  
             }
 158  
         }
 159  2
         return steps;
 160  
     }
 161  
 
 162  
     private StepCandidate findComposedCandidate(String composedStep, List<StepCandidate> allCandidates) {
 163  4
         for (StepCandidate candidate : allCandidates) {
 164  10
             if ( StringUtils.startsWith(composedStep, candidate.getStartingWord()) &&
 165  
                  StringUtils.endsWith(composedStep, candidate.getPatternAsString()) ){
 166  4
                 return candidate;
 167  
             }
 168  
         }
 169  0
         return null;
 170  
     }
 171  
 
 172  
     private String stripStartingWord(final String stepAsString) {
 173  77
         String startingWord = findStartingWord(stepAsString);
 174  71
         return trimStartingWord(startingWord, stepAsString);
 175  
     }
 176  
 
 177  
     private String findStartingWord(final String stepAsString) throws StartingWordNotFound {
 178  78
         String wordForType = startingWordFor(stepType);
 179  78
         if (stepStartsWithWord(stepAsString, wordForType)) {
 180  66
             return wordForType;
 181  
         }
 182  12
         String andWord = startingWordFor(StepType.AND);
 183  12
         if (stepStartsWithWord(stepAsString, andWord)) {
 184  6
             return andWord;
 185  
         }
 186  6
         throw new StartingWordNotFound(stepAsString, stepType, startingWordsByType);
 187  
     }
 188  
 
 189  
     private boolean stepStartsWithWord(String step, String word) {
 190  105
         return step.startsWith(word + " "); // space after qualifies it as word
 191  
     }
 192  
 
 193  
     private String trimStartingWord(String word, String step) {
 194  71
         return step.substring(word.length() + 1); // 1 for the space after
 195  
     }
 196  
 
 197  
     private String startingWordFor(StepType stepType) {
 198  125
         String startingWord = startingWordsByType.get(stepType);
 199  125
         if (startingWord == null) {
 200  2
             throw new StartingWordNotFound(stepType, startingWordsByType);
 201  
         }
 202  123
         return startingWord;
 203  
     }
 204  
 
 205  
     @Override
 206  
     public String toString() {
 207  93
         return stepType + " " + patternAsString;
 208  
     }
 209  
 
 210  
     @SuppressWarnings("serial")
 211  
     public static class StartingWordNotFound extends RuntimeException {
 212  
 
 213  
         public StartingWordNotFound(String step, StepType stepType, Map<StepType, String> startingWordsByType) {
 214  6
             super("No starting word found for step '" + step + "' of type '" + stepType + "' amongst '"
 215  
                     + startingWordsByType + "'");
 216  6
         }
 217  
 
 218  
         public StartingWordNotFound(StepType stepType, Map<StepType, String> startingWordsByType) {
 219  2
             super("No starting word found of type '" + stepType + "' amongst '" + startingWordsByType + "'");
 220  2
         }
 221  
 
 222  
     }
 223  
 
 224  
 
 225  
 }