1 | |
package org.jbehave.core.steps; |
2 | |
|
3 | |
import static java.util.Arrays.asList; |
4 | |
import static org.jbehave.core.steps.AbstractStepResult.failed; |
5 | |
import static org.jbehave.core.steps.AbstractStepResult.ignorable; |
6 | |
import static org.jbehave.core.steps.AbstractStepResult.notPerformed; |
7 | |
import static org.jbehave.core.steps.AbstractStepResult.pending; |
8 | |
import static org.jbehave.core.steps.AbstractStepResult.skipped; |
9 | |
import static org.jbehave.core.steps.AbstractStepResult.successful; |
10 | |
|
11 | |
import java.lang.annotation.Annotation; |
12 | |
import java.lang.reflect.InvocationTargetException; |
13 | |
import java.lang.reflect.Method; |
14 | |
import java.lang.reflect.Type; |
15 | |
import java.util.Map; |
16 | |
|
17 | |
import org.jbehave.core.annotations.Named; |
18 | |
import org.jbehave.core.annotations.AfterScenario.Outcome; |
19 | |
import org.jbehave.core.failures.BeforeOrAfterFailed; |
20 | |
import org.jbehave.core.parsers.StepMatcher; |
21 | |
|
22 | |
import com.thoughtworks.paranamer.NullParanamer; |
23 | |
import com.thoughtworks.paranamer.Paranamer; |
24 | |
|
25 | 590 | public class StepCreator { |
26 | |
|
27 | |
public static final String PARAMETER_NAME_START = "<"; |
28 | |
public static final String PARAMETER_NAME_END = ">"; |
29 | |
public static final String PARAMETER_VALUE_START = "\uFF5F"; |
30 | |
public static final String PARAMETER_VALUE_END = "\uFF60"; |
31 | |
public static final String PARAMETER_VALUE_NEWLINE = "\u2424"; |
32 | |
private final Object stepsInstance; |
33 | |
private final ParameterConverters parameterConverters; |
34 | |
private final StepMatcher stepMatcher; |
35 | |
private final StepRunner beforeOrAfter; |
36 | |
private final StepRunner skip; |
37 | |
private StepMonitor stepMonitor; |
38 | 99 | private Paranamer paranamer = new NullParanamer(); |
39 | 99 | private boolean dryRun = false; |
40 | |
|
41 | |
public StepCreator(Object stepsInstance, StepMonitor stepMonitor) { |
42 | 22 | this(stepsInstance, null, null, stepMonitor); |
43 | 22 | } |
44 | |
|
45 | |
public StepCreator(Object stepsInstance, ParameterConverters parameterConverters, StepMatcher stepMatcher, |
46 | 99 | StepMonitor stepMonitor) { |
47 | 99 | this.stepsInstance = stepsInstance; |
48 | 99 | this.parameterConverters = parameterConverters; |
49 | 99 | this.stepMatcher = stepMatcher; |
50 | 99 | this.stepMonitor = stepMonitor; |
51 | 99 | this.beforeOrAfter = new BeforeOrAfter(); |
52 | 99 | this.skip = new Skip(); |
53 | 99 | } |
54 | |
|
55 | |
public void useStepMonitor(StepMonitor stepMonitor) { |
56 | 48 | this.stepMonitor = stepMonitor; |
57 | 48 | } |
58 | |
|
59 | |
public void useParanamer(Paranamer paranamer) { |
60 | 50 | this.paranamer = paranamer; |
61 | 50 | } |
62 | |
|
63 | |
public void doDryRun(boolean dryRun) { |
64 | 47 | this.dryRun = dryRun; |
65 | 47 | } |
66 | |
|
67 | |
public Step createBeforeOrAfterStep(final Method method) { |
68 | 12 | return new Step() { |
69 | |
public StepResult doNotPerform() { |
70 | 1 | return beforeOrAfter.run(method); |
71 | |
} |
72 | |
|
73 | |
public StepResult perform() { |
74 | 11 | return beforeOrAfter.run(method); |
75 | |
} |
76 | |
}; |
77 | |
} |
78 | |
|
79 | |
public Step createAfterStepUponOutcome(final Method method, Outcome outcome) { |
80 | 6 | switch (outcome) { |
81 | |
case ANY: |
82 | |
default: |
83 | 2 | return new Step() { |
84 | |
|
85 | |
public StepResult doNotPerform() { |
86 | 1 | return beforeOrAfter.run(method); |
87 | |
} |
88 | |
|
89 | |
public StepResult perform() { |
90 | 1 | return beforeOrAfter.run(method); |
91 | |
} |
92 | |
|
93 | |
}; |
94 | |
case SUCCESS: |
95 | 2 | return new Step() { |
96 | |
|
97 | |
public StepResult doNotPerform() { |
98 | 1 | return skip.run(method); |
99 | |
} |
100 | |
|
101 | |
public StepResult perform() { |
102 | 1 | return beforeOrAfter.run(method); |
103 | |
} |
104 | |
|
105 | |
}; |
106 | |
case FAILURE: |
107 | 2 | return new Step() { |
108 | |
|
109 | |
public StepResult doNotPerform() { |
110 | 1 | return beforeOrAfter.run(method); |
111 | |
} |
112 | |
|
113 | |
public StepResult perform() { |
114 | 1 | return skip.run(method); |
115 | |
} |
116 | |
|
117 | |
}; |
118 | |
} |
119 | |
} |
120 | |
|
121 | |
public Step createParametrisedStep(final Method method, final String stepAsString, |
122 | |
final String stepWithoutStartingWord, final Map<String, String> tableRow) { |
123 | 58 | return new Step() { |
124 | |
private Object[] convertedParameters; |
125 | |
private String parametrisedStep; |
126 | |
|
127 | |
public StepResult perform() { |
128 | |
try { |
129 | 58 | parametriseStep(); |
130 | 55 | stepMonitor.performing(stepAsString, dryRun); |
131 | 55 | if (!dryRun) { |
132 | 51 | method.invoke(stepsInstance, convertedParameters); |
133 | |
} |
134 | 54 | return successful(stepAsString).withParameterValues(parametrisedStep); |
135 | 1 | } catch (ParameterNotFound e) { |
136 | |
|
137 | 1 | return pending(stepAsString).withParameterValues(parametrisedStep); |
138 | 1 | } catch (InvocationTargetException e) { |
139 | 1 | return failed(stepAsString, e.getCause()).withParameterValues(parametrisedStep); |
140 | 2 | } catch (Throwable t) { |
141 | 2 | return failed(stepAsString, t).withParameterValues(parametrisedStep); |
142 | |
} |
143 | |
} |
144 | |
|
145 | |
public StepResult doNotPerform() { |
146 | |
try { |
147 | 1 | parametriseStep(); |
148 | 1 | } catch (ParameterNotFound e) { |
149 | |
|
150 | |
|
151 | 0 | } |
152 | 1 | return notPerformed(stepAsString).withParameterValues(parametrisedStep); |
153 | |
} |
154 | |
|
155 | |
private void parametriseStep() { |
156 | 59 | stepMatcher.find(stepWithoutStartingWord); |
157 | 57 | String[] annotationNames = annotatedParameterNames(method); |
158 | 57 | String[] parameterNames = paranamer.lookupParameterNames(method, false); |
159 | 57 | Type[] types = method.getGenericParameterTypes(); |
160 | 57 | String[] parameters = parametersForStep(tableRow, types, annotationNames, parameterNames); |
161 | 55 | convertedParameters = convertParameters(parameters, types); |
162 | 55 | parametrisedStep = parametrisedStep(stepAsString, tableRow, types, annotationNames, parameterNames, |
163 | |
parameters); |
164 | 55 | } |
165 | |
|
166 | |
}; |
167 | |
} |
168 | |
|
169 | |
|
170 | |
|
171 | |
|
172 | |
|
173 | |
|
174 | |
|
175 | |
|
176 | |
|
177 | |
|
178 | |
private String[] annotatedParameterNames(Method method) { |
179 | 57 | Annotation[][] parameterAnnotations = method.getParameterAnnotations(); |
180 | 57 | String[] names = new String[parameterAnnotations.length]; |
181 | 101 | for (int x = 0; x < parameterAnnotations.length; x++) { |
182 | 44 | Annotation[] annotations = parameterAnnotations[x]; |
183 | 60 | for (Annotation annotation : annotations) { |
184 | 16 | names[x] = annotationName(annotation); |
185 | |
} |
186 | |
} |
187 | 57 | return names; |
188 | |
} |
189 | |
|
190 | |
private String annotationName(Annotation annotation) { |
191 | 16 | if (annotation.annotationType().isAssignableFrom(Named.class)) { |
192 | 10 | return ((Named) annotation).value(); |
193 | 6 | } else if ("javax.inject.Named".equals(annotation.annotationType().getName())) { |
194 | 6 | return Jsr330Helper.getNamedValue(annotation); |
195 | |
} else { |
196 | 0 | return null; |
197 | |
} |
198 | |
} |
199 | |
|
200 | |
private String parametrisedStep(String stepAsString, Map<String, String> tableRow, Type[] types, |
201 | |
String[] annotationNames, String[] parameterNames, String[] parameters) { |
202 | 55 | String parametrisedStep = stepAsString; |
203 | 95 | for (int position = 0; position < types.length; position++) { |
204 | 40 | parametrisedStep = replaceParameterValuesInStep(parametrisedStep, position, annotationNames, |
205 | |
parameterNames, parameters, tableRow); |
206 | |
} |
207 | 55 | return parametrisedStep; |
208 | |
} |
209 | |
|
210 | |
private String replaceParameterValuesInStep(String stepText, int position, String[] annotationNames, |
211 | |
String[] parameterNames, String[] parameters, Map<String, String> tableRow) { |
212 | 40 | int annotatedNamePosition = parameterPosition(annotationNames, position); |
213 | 40 | int parameterNamePosition = parameterPosition(parameterNames, position); |
214 | 40 | if (annotatedNamePosition != -1) { |
215 | 12 | stepText = replaceTableValue(stepText, tableRow, annotationNames[position]); |
216 | 28 | } else if (parameterNamePosition != -1) { |
217 | 6 | stepText = replaceTableValue(stepText, tableRow, parameterNames[position]); |
218 | |
} |
219 | 40 | stepText = replaceParameterValue(stepText, position, parameters); |
220 | 40 | return stepText; |
221 | |
} |
222 | |
|
223 | |
private String replaceParameterValue(String stepText, int position, String[] parameters) { |
224 | 40 | String value = parameters[position]; |
225 | 40 | if (value != null) { |
226 | 40 | stepText = stepText.replace(value, PARAMETER_VALUE_START + value + PARAMETER_VALUE_END); |
227 | 40 | stepText = stepText.replace("\n", PARAMETER_VALUE_NEWLINE); |
228 | |
} |
229 | 40 | return stepText; |
230 | |
} |
231 | |
|
232 | |
private String replaceTableValue(String stepText, Map<String, String> tableRow, String name) { |
233 | 18 | String value = getTableValue(tableRow, name); |
234 | 18 | if (value != null) { |
235 | 6 | stepText = stepText.replace(PARAMETER_NAME_START + name + PARAMETER_NAME_END, PARAMETER_VALUE_START + value |
236 | |
+ PARAMETER_VALUE_END); |
237 | |
} |
238 | 18 | return stepText; |
239 | |
} |
240 | |
|
241 | |
private String[] parametersForStep(Map<String, String> tableRow, Type[] types, String[] annotationNames, |
242 | |
String[] parameterNames) { |
243 | 57 | final String[] parameters = new String[types.length]; |
244 | 97 | for (int position = 0; position < types.length; position++) { |
245 | 42 | parameters[position] = parameterForPosition(position, annotationNames, parameterNames, tableRow); |
246 | |
} |
247 | 55 | return parameters; |
248 | |
} |
249 | |
|
250 | |
private Object[] convertParameters(String[] parametersAsString, Type[] types) { |
251 | 55 | final Object[] parameters = new Object[parametersAsString.length]; |
252 | 95 | for (int position = 0; position < parametersAsString.length; position++) { |
253 | 40 | parameters[position] = parameterConverters.convert(parametersAsString[position], types[position]); |
254 | |
} |
255 | 55 | return parameters; |
256 | |
} |
257 | |
|
258 | |
private String parameterForPosition(int position, String[] annotationNames, String[] parameterNames, |
259 | |
Map<String, String> tableRow) { |
260 | 42 | int annotatedNamePosition = parameterPosition(annotationNames, position); |
261 | 42 | int parameterNamePosition = parameterPosition(parameterNames, position); |
262 | 42 | String parameter = null; |
263 | 42 | if (annotatedNamePosition != -1 && isGroupName(annotationNames[position])) { |
264 | 8 | String name = annotationNames[position]; |
265 | 8 | stepMonitor.usingAnnotatedNameForParameter(name, position); |
266 | 8 | parameter = matchedParameter(name); |
267 | 8 | } else if (parameterNamePosition != -1 && isGroupName(parameterNames[position])) { |
268 | 4 | String name = parameterNames[position]; |
269 | 4 | stepMonitor.usingParameterNameForParameter(name, position); |
270 | 4 | parameter = matchedParameter(name); |
271 | 4 | } else if (annotatedNamePosition != -1 && isTableFieldName(tableRow, annotationNames[position])) { |
272 | 4 | String name = annotationNames[position]; |
273 | 4 | stepMonitor.usingTableAnnotatedNameForParameter(name, position); |
274 | 4 | parameter = getTableValue(tableRow, name); |
275 | 4 | } else if (parameterNamePosition != -1 && isTableFieldName(tableRow, parameterNames[position])) { |
276 | 2 | String name = parameterNames[position]; |
277 | 2 | stepMonitor.usingTableParameterNameForParameter(name, position); |
278 | 2 | parameter = getTableValue(tableRow, name); |
279 | 2 | } else { |
280 | 24 | stepMonitor.usingNaturalOrderForParameter(position); |
281 | 24 | parameter = matchedParameter(position); |
282 | |
} |
283 | 40 | stepMonitor.foundParameter(parameter, position); |
284 | 40 | return parameter; |
285 | |
} |
286 | |
|
287 | |
String matchedParameter(String name) { |
288 | 13 | String[] parameterNames = stepMatcher.parameterNames(); |
289 | 19 | for (int i = 0; i < parameterNames.length; i++) { |
290 | 18 | String parameterName = parameterNames[i]; |
291 | 18 | if (name.equals(parameterName)) { |
292 | 12 | return matchedParameter(i); |
293 | |
} |
294 | |
} |
295 | 1 | throw new ParameterNotFound(name, parameterNames); |
296 | |
} |
297 | |
|
298 | |
private String matchedParameter(int position) { |
299 | 36 | String[] parameterNames = stepMatcher.parameterNames(); |
300 | 36 | int matchedPosition = position + 1; |
301 | 36 | if (matchedPosition <= parameterNames.length) { |
302 | 34 | return stepMatcher.parameter(matchedPosition); |
303 | |
} |
304 | 2 | throw new ParameterNotFound(position, parameterNames); |
305 | |
} |
306 | |
|
307 | |
private int parameterPosition(String[] names, int position) { |
308 | 164 | if (names.length == 0) { |
309 | 70 | return -1; |
310 | |
} |
311 | 94 | String positionName = names[position]; |
312 | 180 | for (int i = 0; i < names.length; i++) { |
313 | 124 | String name = names[i]; |
314 | 124 | if (name != null && positionName.equals(name)) { |
315 | 38 | return i; |
316 | |
} |
317 | |
} |
318 | 56 | return -1; |
319 | |
} |
320 | |
|
321 | |
private boolean isGroupName(String name) { |
322 | 20 | String[] groupNames = stepMatcher.parameterNames(); |
323 | 26 | for (String groupName : groupNames) { |
324 | 18 | if (name.equals(groupName)) { |
325 | 12 | return true; |
326 | |
} |
327 | |
} |
328 | 8 | return false; |
329 | |
} |
330 | |
|
331 | |
private String getTableValue(Map<String, String> tableRow, String name) { |
332 | 24 | return tableRow.get(name); |
333 | |
} |
334 | |
|
335 | |
private boolean isTableFieldName(Map<String, String> tableRow, String name) { |
336 | 8 | return tableRow.get(name) != null; |
337 | |
} |
338 | |
|
339 | |
public interface StepRunner { |
340 | |
|
341 | |
StepResult run(Method method); |
342 | |
|
343 | |
} |
344 | |
|
345 | 198 | private class BeforeOrAfter implements StepRunner { |
346 | |
public StepResult run(Method method) { |
347 | |
try { |
348 | 16 | method.invoke(stepsInstance); |
349 | 2 | } catch (InvocationTargetException e) { |
350 | 2 | throw new BeforeOrAfterFailed(method, e.getCause()); |
351 | 1 | } catch (Throwable t) { |
352 | 1 | throw new BeforeOrAfterFailed(t); |
353 | 13 | } |
354 | 13 | return skipped(); |
355 | |
} |
356 | |
} |
357 | |
|
358 | 198 | private class Skip implements StepRunner { |
359 | |
public StepResult run(Method method) { |
360 | 2 | return skipped(); |
361 | |
} |
362 | |
} |
363 | |
|
364 | |
public static Step createPendingStep(final String stepAsString) { |
365 | 9 | return new Step() { |
366 | |
public StepResult perform() { |
367 | 2 | return pending(stepAsString); |
368 | |
} |
369 | |
|
370 | |
public StepResult doNotPerform() { |
371 | 1 | return pending(stepAsString); |
372 | |
} |
373 | |
}; |
374 | |
} |
375 | |
|
376 | |
public static Step createIgnorableStep(final String stepAsString) { |
377 | 2 | return new Step() { |
378 | |
public StepResult perform() { |
379 | 2 | return ignorable(stepAsString); |
380 | |
} |
381 | |
|
382 | |
public StepResult doNotPerform() { |
383 | 1 | return ignorable(stepAsString); |
384 | |
} |
385 | |
}; |
386 | |
} |
387 | |
|
388 | |
|
389 | |
|
390 | |
|
391 | |
|
392 | 6 | public static class Jsr330Helper { |
393 | |
|
394 | |
private static String getNamedValue(Annotation annotation) { |
395 | 6 | return ((javax.inject.Named) annotation).value(); |
396 | |
} |
397 | |
|
398 | |
} |
399 | |
|
400 | |
@SuppressWarnings("serial") |
401 | |
public static class ParameterNotFound extends RuntimeException { |
402 | |
|
403 | |
public ParameterNotFound(String name, String[] parameters) { |
404 | 1 | super("Parameter not found for name '" + name + "' amongst '" + asList(parameters) + "'"); |
405 | 1 | } |
406 | |
|
407 | |
public ParameterNotFound(int position, String[] parameters) { |
408 | 2 | super("Parameter not found for position '" + position + "' amongst '" + asList(parameters) + "'"); |
409 | 2 | } |
410 | |
} |
411 | |
|
412 | |
} |