##### Coverage Report - org.jbehave.core.steps.StepFinder

Classes in this File | Line Coverage | Branch Coverage | Complexity
StepFinder |
100% | 24/24
91% | 11/12
| 2.125;2.125
StepFinder$1 |
N/A | N/A
N/A | N/A
| 2.125;2.125
StepFinder$ByLevenshteinDistance |
100% | 5/5
N/A | N/A
| 2.125;2.125
StepFinder$ByLevenshteinDistance$1 |
100% | 10/10
100% | 2/2
| 2.125;2.125
StepFinder$ByLevenshteinDistance$LevenshteinDistance |
92% | 25/27
88% | 16/18
| 2.125;2.125
StepFinder$ByPriorityField |
100% | 3/3
N/A | N/A
| 2.125;2.125
StepFinder$ByPriorityField$1 |
100% | 2/2
N/A | N/A
| 2.125;2.125
StepFinder$PrioritisingStrategy |
N/A | N/A
N/A | N/A
| 2.125;2.125

1 |  |
     package org.jbehave.core.steps;
2 |  |
3 |  |
     import java.util.ArrayList;
4 |  |
     import java.util.Collections;
5 |  |
     import java.util.Comparator;
6 |  |
     import java.util.LinkedList;
7 |  |
     import java.util.List;
8 |  |
9 |  |
     import org.apache.commons.lang.StringUtils;
10 |  |
11 |  |
     /**
12 |  |
      * <p>
13 |  |
      * StepFinder is reponsible for finding and prioritising step candidates or
14 |  |
      * finding steps instances from {@link CandidateSteps}, which are created using
15 |  |
      * an {@link InjectableStepsFactory}.
16 |  |
      * </p>
17 |  |
      * <p>
18 |  |
      * The {@link StepCandidate}s are responsible for the matching of a particular
19 |  |
      * textual step and are sometimes represented as {@link Stepdoc}s, each of which
20 |  |
      * is simply a facade documenting a candidate. The candidates can be prioritised
21 |  |
      * via an injectable {@link PrioritisingStrategy}, defaulting to
22 |  |
      * {@link ByPriorityField}. A more sophisticated strategy that can be used is
23 |  |
      * the {@link ByLevenshteinDistance}.
24 |  |
      * </p>
25 |  |
      */
26 |  |
     public class StepFinder {
27 |  |
28 |  |
         private PrioritisingStrategy prioritisingStrategy;
29 |  |
30 |  |
         /**
31 |  |
          * Creates a StepFinder with a {@link ByPriorityField} strategy
32 |  |
          */
33 |  |
         public StepFinder() {
34 | 1317 |
             this(new ByPriorityField());
35 | 1317 |
         }
36 |  |
37 |  |
         /**
38 |  |
          * Creates a StepFinder with a custom strategy
39 |  |
          *
40 |  |
          * @param prioritisingStrategy
41 |  |
          *            the PrioritisingStrategy
42 |  |
          */
43 | 1318 |
         public StepFinder(PrioritisingStrategy prioritisingStrategy) {
44 | 1318 |
             this.prioritisingStrategy = prioritisingStrategy;
45 | 1318 |
         }
46 |  |
47 |  |
         /**
48 |  |
          * Returns the stepdocs for the candidates collected from the given
49 |  |
          * {@link CandidateSteps}.
50 |  |
          *
51 |  |
          * @param candidateSteps
52 |  |
          *            the List of CandidateSteps
53 |  |
          * @return The List of Stepdocs, one for each {@link StepCandidate}.
54 |  |
          */
55 |  |
         public List<Stepdoc> stepdocs(List<CandidateSteps> candidateSteps) {
56 | 2 |
             List<Stepdoc> stepdocs = new LinkedList<Stepdoc>();
57 | 2 |
             for (StepCandidate candidate : collectCandidates(candidateSteps)) {
58 | 6 |
                 stepdocs.add(new Stepdoc(candidate));
59 |  |
             }
60 | 2 |
             return stepdocs;
61 |  |
         }
62 |  |
63 |  |
         /**
64 |  |
          * Finds matching steps, represented as {@link Stepdoc}s, for a given
65 |  |
          * textual step and a list of {@link CandidateSteps}.
66 |  |
          *
67 |  |
          * @param stepAsText
68 |  |
          *            the textual step
69 |  |
          * @param candidateSteps
70 |  |
          *            the List of CandidateSteps
71 |  |
          * @return The list of Stepdocs, one for each matched {@link StepCandidate}.
72 |  |
          */
73 |  |
         public List<Stepdoc> findMatching(String stepAsText, List<CandidateSteps> candidateSteps) {
74 | 3 |
             List<Stepdoc> matching = new ArrayList<Stepdoc>();
75 | 3 |
             for (StepCandidate candidate : collectCandidates(candidateSteps)) {
76 | 6 |
                 if (candidate.matches(stepAsText)) {
77 | 1 |
                     matching.add(new Stepdoc(candidate));
78 |  |
                 }
79 |  |
             }
80 | 3 |
             return matching;
81 |  |
         }
82 |  |
83 |  |
         /**
84 |  |
          * Returns the steps instances associated to CandidateSteps
85 |  |
          *
86 |  |
          * @param candidateSteps
87 |  |
          *            the List of CandidateSteps
88 |  |
          * @return The List of steps instances
89 |  |
          */
90 |  |
         public List<Object> stepsInstances(List<CandidateSteps> candidateSteps) {
91 | 4 |
             List<Object> instances = new ArrayList<Object>();
92 | 4 |
             for (CandidateSteps steps : candidateSteps) {
93 | 3 |
                 if (steps instanceof Steps) {
94 | 3 |
                     instances.add(((Steps) steps).instance());
95 |  |
                 }
96 |  |
             }
97 | 4 |
             return instances;
98 |  |
         }
99 |  |
100 |  |
         /**
101 |  |
          * Collects a list of step candidates from {@link CandidateSteps} instances.
102 |  |
          *
103 |  |
          * @param candidateSteps
104 |  |
          *            the list {@link CandidateSteps} instances
105 |  |
          * @return A List of {@link StepCandidate}
106 |  |
          */
107 |  |
         public List<StepCandidate> collectCandidates(List<CandidateSteps> candidateSteps) {
108 | 15 |
             List<StepCandidate> collected = new ArrayList<StepCandidate>();
109 | 15 |
             for (CandidateSteps steps : candidateSteps) {
110 | 16 |
                 collected.addAll(steps.listCandidates());
111 |  |
             }
112 | 15 |
             return collected;
113 |  |
         }
114 |  |
115 |  |
         /**
116 |  |
          * Prioritises the list of step candidates that match a given step.
117 |  |
          *
118 |  |
          * @param stepAsText
119 |  |
          *            the textual step to match
120 |  |
          * @param candidates
121 |  |
          *            the List of StepCandidate
122 |  |
          * @return The prioritised list according to the
123 |  |
          *         {@link PrioritisingStrategy}.
124 |  |
          */
125 |  |
         public List<StepCandidate> prioritise(String stepAsText, List<StepCandidate> candidates) {
126 | 17 |
             return prioritisingStrategy.prioritise(stepAsText, candidates);
127 |  |
         }
128 |  |
129 |  |
         /**
130 |  |
          * Defines the priorising strategy of step candidates
131 |  |
          */
132 |  |
         public static interface PrioritisingStrategy {
133 |  |
134 |  |
             List<StepCandidate> prioritise(String stepAsString, List<StepCandidate> candidates);
135 |  |
136 |  |
         }
137 |  |
138 |  |
         /**
139 |  |
          * Strategy to priorise step candidates by the
140 |  |
          * {@link StepCandidate#getPriority()} field which is settable in the
141 |  |
          * {@link Given}, {@link When}, {@link Then} annotations.
142 |  |
          */
143 | 1317 |
         public static class ByPriorityField implements PrioritisingStrategy {
144 |  |
145 |  |
             public List<StepCandidate> prioritise(String stepAsText, List<StepCandidate> candidates) {
146 | 16 |
                 Collections.sort(candidates, new Comparator<StepCandidate>() {
147 |  |
                     public int compare(StepCandidate o1, StepCandidate o2) {
148 | 22 |
                         return o2.getPriority().compareTo(o1.getPriority());
149 |  |
                     }
150 |  |
                 });
151 | 16 |
                 return candidates;
152 |  |
             }
153 |  |
154 |  |
         }
155 |  |
156 |  |
         /**
157 |  |
          * Strategy to priorise candidate steps by <a
158 |  |
          * href="http://en.wikipedia.org/wiki/Levenshtein_distance">Levenshtein Distance</a>
159 |  |
          */
160 | 13 |
         public static class ByLevenshteinDistance implements PrioritisingStrategy {
161 |  |
162 | 1 |
             private LevenshteinDistance ld = new LevenshteinDistance();
163 |  |
164 |  |
             public List<StepCandidate> prioritise(final String stepAsText, List<StepCandidate> candidates) {
165 | 7 |
                 Collections.sort(candidates, new Comparator<StepCandidate>() {
166 |  |
                     public int compare(StepCandidate o1, StepCandidate o2) {
167 | 6 |
                         String scoringPattern1 = scoringPattern(o1);
168 | 6 |
                         String scoringPattern2 = scoringPattern(o2);
169 | 6 |
                         String stepWithoutStartingWord = trimStartingWord(stepAsText);
170 | 6 |
                         Integer score1 = 0 - ld.calculate(scoringPattern1, stepWithoutStartingWord);
171 | 6 |
                         Integer score2 = 0 - ld.calculate(scoringPattern2, stepWithoutStartingWord);
172 | 6 |
                         int result = score2.compareTo(score1);
173 |  |
                         // default to strategy by priority if no score result
174 | 6 |
                         return result != 0 ? result : o2.getPriority().compareTo(o1.getPriority());
175 |  |
                     }
176 |  |
177 |  |
                     private String scoringPattern(StepCandidate candidate) {
178 | 12 |
                         return candidate.getPatternAsString().replaceAll("\\s\\$\\w+\\s", " ").replaceAll("\\$\\w+", "");
179 |  |
                     }
180 |  |
181 |  |
                     private String trimStartingWord(String stepAsString) {
182 | 6 |
                         return StringUtils.substringAfter(stepAsString, " ");
183 |  |
                     }
184 |  |
185 |  |
                 });
186 | 1 |
                 return candidates;
187 |  |
             }
188 |  |
189 | 2 |
             private class LevenshteinDistance {
190 |  |
191 |  |
                 public int calculate(String s, String t) {
192 |  |
                     int d[][]; // matrix
193 |  |
                     int n; // length of s
194 |  |
                     int m; // length of t
195 |  |
                     int i; // iterates through s
196 |  |
                     int j; // iterates through t
197 |  |
                     char s_i; // ith character of s
198 |  |
                     char t_j; // jth character of t
199 |  |
                     int cost; // cost
200 |  |
201 |  |
                     // Step 1
202 | 12 |
                     n = s.length();
203 | 12 |
                     m = t.length();
204 | 12 |
                     if (n == 0) {
205 | 0 |
                         return m;
206 |  |
                     }
207 | 12 |
                     if (m == 0) {
208 | 0 |
                         return n;
209 |  |
                     }
210 | 12 |
                     d = new int[n + 1][m + 1];
211 |  |
                     // Step 2
212 | 255 |
                     for (i = 0; i <= n; i++) {
213 | 243 |
                         d[i][0] = i;
214 |  |
                     }
215 | 96 |
                     for (j = 0; j <= m; j++) {
216 | 84 |
                         d[0][j] = j;
217 |  |
                     }
218 |  |
                     // Step 3
219 | 243 |
                     for (i = 1; i <= n; i++) {
220 | 231 |
                         s_i = s.charAt(i - 1);
221 |  |
                         // Step 4
222 | 1617 |
                         for (j = 1; j <= m; j++) {
223 | 1386 |
                             t_j = t.charAt(j - 1);
224 |  |
                             // Step 5
225 | 1386 |
                             if (s_i == t_j) {
226 | 84 |
                                 cost = 0;
227 |  |
                             } else {
228 | 1302 |
                                 cost = 1;
229 |  |
                             }
230 |  |
                             // Step 6
231 | 1386 |
                             d[i][j] = minimum(d[i - 1][j] + 1, d[i][j - 1] + 1, d[i - 1][j - 1] + cost);
232 |  |
                         }
233 |  |
                     }
234 |  |
                     // Step 7
235 | 12 |
                     return d[n][m];
236 |  |
                 }
237 |  |
238 |  |
                 private int minimum(int a, int b, int c) {
239 | 1386 |
                     int mi = a;
240 | 1386 |
                     if (b < mi) {
241 | 171 |
                         mi = b;
242 |  |
                     }
243 | 1386 |
                     if (c < mi) {
244 | 216 |
                         mi = c;
245 |  |
                     }
246 | 1386 |
                     return mi;
247 |  |
                 }
248 |  |
249 |  |
             }
250 |  |
251 |  |
         }
252 |  |
253 |  |
     }