Coverage Report - org.jbehave.core.steps.StepCreator
 
Classes in this File Line Coverage Branch Coverage Complexity
StepCreator
93%
160/171
86%
85/98
1.939
StepCreator$1
100%
1/1
N/A
1.939
StepCreator$AbstractStep
33%
1/3
N/A
1.939
StepCreator$BeforeOrAfterStep
77%
14/18
N/A
1.939
StepCreator$BeforeOrAfterStep$UUIDExceptionWrapperInjector
100%
5/5
100%
2/2
1.939
StepCreator$IgnorableStep
83%
5/6
N/A
1.939
StepCreator$Jsr330Helper
100%
2/2
N/A
1.939
StepCreator$MethodInvoker
100%
28/28
100%
10/10
1.939
StepCreator$MethodInvoker$Parameter
100%
9/9
100%
2/2
1.939
StepCreator$ParameterName
100%
5/5
N/A
1.939
StepCreator$ParameterNotFound
100%
4/4
N/A
1.939
StepCreator$ParametrisedStep
91%
43/47
75%
9/12
1.939
StepCreator$PendingStep
66%
8/12
0%
0/2
1.939
StepCreator$UponAnyParametrisedStep
0%
0/6
N/A
1.939
StepCreator$UponFailureParametrisedStep
0%
0/6
N/A
1.939
StepCreator$UponFailureStep
83%
5/6
N/A
1.939
StepCreator$UponSuccessParametrisedStep
0%
0/6
N/A
1.939
StepCreator$UponSuccessStep
83%
5/6
N/A
1.939
 
 1  
 package org.jbehave.core.steps;
 2  
 
 3  
 import java.lang.annotation.Annotation;
 4  
 import java.lang.reflect.InvocationTargetException;
 5  
 import java.lang.reflect.Method;
 6  
 import java.lang.reflect.Type;
 7  
 import java.util.HashMap;
 8  
 import java.util.Map;
 9  
 import java.util.regex.Matcher;
 10  
 import java.util.regex.Pattern;
 11  
 
 12  
 import org.apache.commons.lang.builder.ToStringBuilder;
 13  
 import org.apache.commons.lang.builder.ToStringStyle;
 14  
 import org.jbehave.core.annotations.AfterScenario.Outcome;
 15  
 import org.jbehave.core.annotations.Named;
 16  
 import org.jbehave.core.configuration.Keywords;
 17  
 import org.jbehave.core.failures.BeforeOrAfterFailed;
 18  
 import org.jbehave.core.failures.RestartingScenarioFailure;
 19  
 import org.jbehave.core.failures.UUIDExceptionWrapper;
 20  
 import org.jbehave.core.model.ExamplesTable;
 21  
 import org.jbehave.core.model.Meta;
 22  
 import org.jbehave.core.parsers.StepMatcher;
 23  
 import org.jbehave.core.reporters.StoryReporter;
 24  
 
 25  
 import com.thoughtworks.paranamer.NullParanamer;
 26  
 import com.thoughtworks.paranamer.Paranamer;
 27  
 
 28  
 import static java.util.Arrays.asList;
 29  
 import static org.jbehave.core.steps.AbstractStepResult.failed;
 30  
 import static org.jbehave.core.steps.AbstractStepResult.ignorable;
 31  
 import static org.jbehave.core.steps.AbstractStepResult.notPerformed;
 32  
 import static org.jbehave.core.steps.AbstractStepResult.pending;
 33  
 import static org.jbehave.core.steps.AbstractStepResult.silent;
 34  
 import static org.jbehave.core.steps.AbstractStepResult.skipped;
 35  
 import static org.jbehave.core.steps.AbstractStepResult.successful;
 36  
 
 37  1079
 public class StepCreator {
 38  
 
 39  
         public static final String PARAMETER_TABLE_START = "\uff3b";
 40  
     public static final String PARAMETER_TABLE_END = "\uff3d";
 41  
     public static final String PARAMETER_VALUE_START = "\uFF5F";
 42  
     public static final String PARAMETER_VALUE_END = "\uFF60";
 43  
     public static final String PARAMETER_VALUE_NEWLINE = "\u2424";
 44  1
     public static final UUIDExceptionWrapper NO_FAILURE = new UUIDExceptionWrapper("no failure");
 45  
         private static final String NEWLINE = "\n";
 46  
     private static final String SPACE = " ";
 47  
         private static final String NONE = "";
 48  
     private final Class<?> stepsType;
 49  
     private final InjectableStepsFactory stepsFactory;
 50  
     private final ParameterConverters parameterConverters;
 51  
     private final ParameterControls parameterControls;
 52  
     private final Pattern delimitedNamePattern;
 53  
     private final StepMatcher stepMatcher;
 54  
     private StepMonitor stepMonitor;
 55  408
     private Paranamer paranamer = new NullParanamer();
 56  408
     private boolean dryRun = false;
 57  
 
 58  
     public StepCreator(Class<?> stepsType, InjectableStepsFactory stepsFactory,
 59  
             ParameterConverters parameterConverters, ParameterControls parameterControls, StepMatcher stepMatcher,
 60  408
             StepMonitor stepMonitor) {
 61  408
         this.stepsType = stepsType;
 62  408
         this.stepsFactory = stepsFactory;
 63  408
         this.parameterConverters = parameterConverters;
 64  408
         this.parameterControls = parameterControls;
 65  408
         this.stepMatcher = stepMatcher;
 66  408
         this.stepMonitor = stepMonitor;
 67  816
         this.delimitedNamePattern = Pattern.compile(parameterControls.nameDelimiterLeft() + "(\\w+?)"
 68  408
                 + parameterControls.nameDelimiterRight());
 69  408
     }
 70  
 
 71  
     public void useStepMonitor(StepMonitor stepMonitor) {
 72  360
         this.stepMonitor = stepMonitor;
 73  360
     }
 74  
 
 75  
     public void useParanamer(Paranamer paranamer) {
 76  307
         this.paranamer = paranamer;
 77  307
     }
 78  
 
 79  
     public void doDryRun(boolean dryRun) {
 80  302
         this.dryRun = dryRun;
 81  302
     }
 82  
 
 83  
     public Object stepsInstance() {
 84  153
         return stepsFactory.createInstanceOfType(stepsType);
 85  
     }
 86  
 
 87  
     public Step createBeforeOrAfterStep(Method method, Meta meta) {
 88  30
         return new BeforeOrAfterStep(method, meta);
 89  
     }
 90  
 
 91  
     public Step createAfterStepUponOutcome(final Method method, final Outcome outcome, Meta storyAndScenarioMeta) {
 92  18
         switch (outcome) {
 93  
         case ANY:
 94  
         default:
 95  8
             return new BeforeOrAfterStep(method, storyAndScenarioMeta);
 96  
         case SUCCESS:
 97  5
             return new UponSuccessStep(method, storyAndScenarioMeta);
 98  
         case FAILURE:
 99  5
             return new UponFailureStep(method, storyAndScenarioMeta);
 100  
         }
 101  
     }
 102  
 
 103  
     public Map<String, String> matchedParameters(final Method method, final String stepAsString,
 104  
             final String stepWithoutStartingWord, final Map<String, String> namedParameters) {
 105  8
         Map<String, String> matchedParameters = new HashMap<String, String>(); 
 106  8
         if (stepMatcher.find(stepWithoutStartingWord)) { 
 107  
             // we've found a match, populate map
 108  4
             ParameterName[] parameterNames = parameterNames(method);
 109  4
             Type[] types = method.getGenericParameterTypes();
 110  4
             String[] values = parameterValuesForStep(namedParameters, types, parameterNames);
 111  
     
 112  
             
 113  7
             for (int i = 0; i < parameterNames.length; i++) {
 114  3
                 String name = parameterNames[i].name;
 115  3
                 if (name == null) {
 116  0
                     name = stepMatcher.parameterNames()[i];
 117  
                 }
 118  3
                 matchedParameters.put(name, values[i]);
 119  
             }
 120  
         }
 121  
         // else return empty map
 122  8
         return matchedParameters; 
 123  
     }
 124  
 
 125  
     /**
 126  
      * Returns the {@link ParameterName} representations for the method,
 127  
      * providing an abstraction that supports both annotated and non-annotated
 128  
      * parameters.
 129  
      * 
 130  
      * @param method the Method
 131  
      * @return The array of {@link ParameterName}s
 132  
      */
 133  
     private ParameterName[] parameterNames(Method method) {
 134  126
         String[] annotatedNames = annotatedParameterNames(method);
 135  126
         String[] paranamerNames = paranamerParameterNames(method);
 136  
 
 137  126
         ParameterName[] parameterNames = new ParameterName[annotatedNames.length];
 138  276
         for (int i = 0; i < annotatedNames.length; i++) {
 139  150
             parameterNames[i] = parameterName(annotatedNames, paranamerNames, i);
 140  
         }
 141  126
         return parameterNames;
 142  
     }
 143  
 
 144  
     private ParameterName parameterName(String[] annotatedNames, String[] paranamerNames, int i) {
 145  150
         String name = annotatedNames[i];
 146  150
         boolean annotated = true;
 147  150
         if (name == null) {
 148  107
             name = (paranamerNames.length > i ? paranamerNames[i] : null);
 149  107
             annotated = false;
 150  
         }
 151  150
         return new ParameterName(name, annotated);
 152  
     }
 153  
 
 154  
     /**
 155  
      * Extract parameter names using {@link Named}-annotated parameters
 156  
      * 
 157  
      * @param method the Method with {@link Named}-annotated parameters
 158  
      * @return An array of annotated parameter names, which <b>may</b> include
 159  
      *         <code>null</code> values for parameters that are not annotated
 160  
      */
 161  
     private String[] annotatedParameterNames(Method method) {
 162  165
         Annotation[][] parameterAnnotations = method.getParameterAnnotations();
 163  165
         String[] names = new String[parameterAnnotations.length];
 164  339
         for (int i = 0; i < parameterAnnotations.length; i++) {
 165  233
             for (Annotation annotation : parameterAnnotations[i]) {
 166  59
                 names[i] = annotationName(annotation);
 167  
             }
 168  
         }
 169  165
         return names;
 170  
     }
 171  
 
 172  
     /**
 173  
      * Returns either the value of the annotation, either {@link Named} or
 174  
      * "javax.inject.Named".
 175  
      * 
 176  
      * @param annotation the Annotation
 177  
      * @return The annotated value or <code>null</code> if no annotation is
 178  
      *         found
 179  
      */
 180  
     private String annotationName(Annotation annotation) {
 181  59
         if (annotation.annotationType().isAssignableFrom(Named.class)) {
 182  53
             return ((Named) annotation).value();
 183  6
         } else if ("javax.inject.Named".equals(annotation.annotationType().getName())) {
 184  6
             return Jsr330Helper.getNamedValue(annotation);
 185  
         } else {
 186  0
             return null;
 187  
         }
 188  
     }
 189  
 
 190  
     /**
 191  
      * Extract parameter names using
 192  
      * {@link Paranamer#lookupParameterNames(AccessibleObject, boolean)}
 193  
      * 
 194  
      * @param method the Method inspected by Paranamer
 195  
      * @return An array of parameter names looked up by Paranamer
 196  
      */
 197  
     private String[] paranamerParameterNames(Method method) {
 198  126
         return paranamer.lookupParameterNames(method, false);
 199  
     }
 200  
 
 201  
     public Step createParametrisedStep(final Method method, final String stepAsString,
 202  
             final String stepWithoutStartingWord, final Map<String, String> namedParameters) {
 203  142
         return new ParametrisedStep(stepAsString, method, stepWithoutStartingWord, namedParameters);
 204  
     }
 205  
 
 206  
     public Step createParametrisedStepUponOutcome(final Method method, final String stepAsString,
 207  
             final String stepWithoutStartingWord, final Map<String, String> namedParameters, Outcome outcome) {
 208  0
         switch (outcome) {
 209  
         case ANY:
 210  0
             return new UponAnyParametrisedStep(stepAsString, method, stepWithoutStartingWord, namedParameters);
 211  
         case SUCCESS:
 212  0
             return new UponSuccessParametrisedStep(stepAsString, method, stepWithoutStartingWord, namedParameters);
 213  
         case FAILURE:
 214  0
             return new UponFailureParametrisedStep(stepAsString, method, stepWithoutStartingWord, namedParameters);
 215  
         default:
 216  0
             return new ParametrisedStep(stepAsString, method, stepWithoutStartingWord, namedParameters);
 217  
         }
 218  
     }
 219  
 
 220  
     private String parametrisedStep(String stepAsString, Map<String, String> namedParameters, Type[] types,
 221  
             ParameterName[] names, String[] parameterValues) {
 222  120
             String parametrisedStep = stepAsString;
 223  
             // mark parameter values that are parsed
 224  120
             boolean hasTable = hasTable(types);
 225  263
         for (int position = 0; position < types.length; position++) {
 226  143
             parametrisedStep = markParsedParameterValue(parametrisedStep, types[position], parameterValues[position], hasTable);
 227  
         }
 228  
         // mark parameter values that are named
 229  120
         for (String name : namedParameters.keySet()) {
 230  30
             parametrisedStep = markNamedParameterValue(parametrisedStep, namedParameters, name);
 231  30
         }
 232  
 
 233  120
         return parametrisedStep;
 234  
     }
 235  
 
 236  
     private boolean hasTable(Type[] types) {
 237  263
             for (Type type : types) {
 238  143
                         if ( isTable(type) ){
 239  0
                                 return true;
 240  
                         }
 241  
                 }
 242  120
                 return false;
 243  
         }
 244  
 
 245  
         private String markNamedParameterValue(String stepText, Map<String, String> namedParameters, String name) {
 246  30
         String value = namedParameter(namedParameters, name);
 247  30
         if (value != null) {
 248  30
                         stepText = stepText.replace(delimitedName(name), markedValue(value));
 249  
         }
 250  30
         return stepText;
 251  
     }
 252  
 
 253  
         private String delimitedName(String name) {
 254  30
                 return parameterControls.nameDelimiterLeft() + name + parameterControls.nameDelimiterRight();
 255  
         }
 256  
 
 257  
     private String markParsedParameterValue(String stepText, Type type, String value, boolean hasTable) {
 258  143
         if (value != null) {
 259  143
             if (isTable(type)) {
 260  0
                 stepText = stepText.replace(value, markedTable(value));
 261  
             } else {
 262  
                 // only mark non-empty string as parameter (JBEHAVE-656)                    
 263  143
                 if (value.trim().length() != 0) {
 264  142
                         String markedValue = markedValue(value);
 265  
                         // identify parameter values to mark as padded by spaces to avoid duplicated replacements of overlapping values (JBEHAVE-837)
 266  142
                         String leftPad = SPACE;
 267  142
                         String rightPad = ( stepText.endsWith(value) ? NONE : SPACE );
 268  142
                             stepText = stepText.replace(pad(value, leftPad, rightPad), pad(markedValue, leftPad, rightPad));
 269  
                 }
 270  143
                 if ( !hasTable ){
 271  143
                         stepText = stepText.replace(NEWLINE, PARAMETER_VALUE_NEWLINE);
 272  
                 }
 273  
             }
 274  
         }
 275  143
         return stepText;
 276  
     }
 277  
 
 278  
         private String markedTable(String value) {
 279  0
                 return pad(value, PARAMETER_TABLE_START, PARAMETER_TABLE_END);
 280  
         }
 281  
 
 282  
         private String markedValue(String value) {
 283  172
                 return pad(value, PARAMETER_VALUE_START, PARAMETER_VALUE_END);
 284  
         }
 285  
 
 286  
         private String pad(String value, String left, String right){
 287  456
                 return new StringBuilder().append(left).append(value).append(right).toString();
 288  
         }
 289  
         
 290  
     private boolean isTable(Type type) {
 291  286
         return type instanceof Class && ((Class<?>) type).isAssignableFrom(ExamplesTable.class);
 292  
     }
 293  
 
 294  
     private String[] parameterValuesForStep(Map<String, String> namedParameters, Type[] types, ParameterName[] names) {
 295  126
         final String[] parameters = new String[types.length];
 296  272
         for (int position = 0; position < types.length; position++) {
 297  148
                 parameters[position] = parameterForPosition(position, names, namedParameters);
 298  
         }
 299  124
         return parameters;
 300  
     }
 301  
 
 302  
     private Object[] convertParameterValues(String[] valuesAsString, Type[] types) {
 303  120
         final Object[] parameters = new Object[valuesAsString.length];
 304  263
         for (int position = 0; position < valuesAsString.length; position++) {
 305  143
             parameters[position] = parameterConverters.convert(valuesAsString[position], types[position]);
 306  
         }
 307  120
         return parameters;
 308  
     }
 309  
 
 310  
     private String parameterForPosition(int position, ParameterName[] names, Map<String, String> namedParameters) {
 311  148
         int namePosition = parameterPosition(names, position);
 312  148
         String parameter = null;
 313  
 
 314  148
         if (namePosition != -1) {
 315  47
             String name = names[position].name;
 316  47
             boolean annotated = names[position].annotated;
 317  
 
 318  47
             boolean delimitedNamedParameters = false;
 319  
 
 320  47
             if (isGroupName(name)) {
 321  31
                 parameter = matchedParameter(name);
 322  31
                 String delimitedName = delimitedNameFor(parameter);
 323  
 
 324  31
                 if (delimitedName != null) {
 325  1
                     name = delimitedName;
 326  1
                     delimitedNamedParameters = true;
 327  
                 } else {
 328  30
                     monitorUsingNameForParameter(name, position, annotated);
 329  
                 }
 330  
             }
 331  
 
 332  47
             if (delimitedNamedParameters || isTableName(namedParameters, name)) {
 333  21
                 monitorUsingTableNameForParameter(name, position, annotated);
 334  21
                 parameter = namedParameter(namedParameters, name);
 335  
             }
 336  
 
 337  
         }
 338  
 
 339  148
         if (parameter == null) {
 340  105
             stepMonitor.usingNaturalOrderForParameter(position);
 341  105
             parameter = matchedParameter(position);
 342  103
             String delimitedName = delimitedNameFor(parameter);
 343  
 
 344  103
             if (delimitedName != null && isTableName(namedParameters, delimitedName)) {
 345  3
                 parameter = namedParameter(namedParameters, delimitedName);
 346  
             }
 347  
         }
 348  
 
 349  146
         stepMonitor.foundParameter(parameter, position);
 350  
 
 351  146
         return parameter;
 352  
     }
 353  
 
 354  
     private void monitorUsingTableNameForParameter(String name, int position, boolean usingAnnotationNames) {
 355  21
         if (usingAnnotationNames) {
 356  19
             stepMonitor.usingTableAnnotatedNameForParameter(name, position);
 357  
         } else {
 358  2
             stepMonitor.usingTableParameterNameForParameter(name, position);
 359  
         }
 360  21
     }
 361  
 
 362  
     private void monitorUsingNameForParameter(String name, int position, boolean usingAnnotationNames) {
 363  30
         if (usingAnnotationNames) {
 364  26
             stepMonitor.usingAnnotatedNameForParameter(name, position);
 365  
         } else {
 366  4
             stepMonitor.usingParameterNameForParameter(name, position);
 367  
         }
 368  30
     }
 369  
 
 370  
     private String delimitedNameFor(String parameter) {
 371  134
         if (!parameterControls.delimiterNamedParameters()) {
 372  8
             return null;
 373  
         }
 374  126
         Matcher matcher = delimitedNamePattern.matcher(parameter);
 375  126
         return matcher.matches() ? matcher.group(1) : null;
 376  
     }
 377  
 
 378  
     String matchedParameter(String name) {
 379  32
         String[] parameterNames = stepMatcher.parameterNames();
 380  46
         for (int i = 0; i < parameterNames.length; i++) {
 381  45
             String parameterName = parameterNames[i];
 382  45
             if (name.equals(parameterName)) {
 383  31
                 return matchedParameter(i);
 384  
             }
 385  
         }
 386  1
         throw new ParameterNotFound(name, parameterNames);
 387  
     }
 388  
 
 389  
     private String matchedParameter(int position) {
 390  136
         String[] parameterNames = stepMatcher.parameterNames();
 391  136
         int matchedPosition = position + 1;
 392  136
         if (matchedPosition <= parameterNames.length) {
 393  134
             return stepMatcher.parameter(matchedPosition);
 394  
         }
 395  2
         throw new ParameterNotFound(position, parameterNames);
 396  
     }
 397  
 
 398  
     private int parameterPosition(ParameterName[] names, int position) {
 399  148
         if (names.length == 0) {
 400  0
             return -1;
 401  
         }
 402  148
         String positionName = names[position].name;
 403  344
         for (int i = 0; i < names.length; i++) {
 404  243
             String name = names[i].name;
 405  243
             if (name != null && positionName.equals(name)) {
 406  47
                 return i;
 407  
             }
 408  
         }
 409  101
         return -1;
 410  
     }
 411  
 
 412  
     private boolean isGroupName(String name) {
 413  47
         String[] groupNames = stepMatcher.parameterNames();
 414  65
         for (String groupName : groupNames) {
 415  49
             if (name.equals(groupName)) {
 416  31
                 return true;
 417  
             }
 418  
         }
 419  16
         return false;
 420  
     }
 421  
 
 422  
     private String namedParameter(Map<String, String> namedParameters, String name) {
 423  103
         return namedParameters.get(name);
 424  
     }
 425  
 
 426  
     private boolean isTableName(Map<String, String> namedParameters, String name) {
 427  49
         return namedParameter(namedParameters, name) != null;
 428  
     }
 429  
 
 430  
     public static Step createPendingStep(final String stepAsString, String previousNonAndStep) {
 431  84
         return new PendingStep(stepAsString, previousNonAndStep);
 432  
     }
 433  
 
 434  
     public static Step createIgnorableStep(final String stepAsString) {
 435  2
         return new IgnorableStep(stepAsString);
 436  
     }
 437  
 
 438  
     /**
 439  
      * This is a different class, because the @Inject jar may not be in the
 440  
      * classpath.
 441  
      */
 442  6
     public static class Jsr330Helper {
 443  
 
 444  
         private static String getNamedValue(Annotation annotation) {
 445  6
             return ((javax.inject.Named) annotation).value();
 446  
         }
 447  
 
 448  
     }
 449  
 
 450  
     @SuppressWarnings("serial")
 451  
     public static class ParameterNotFound extends RuntimeException {
 452  
 
 453  
         public ParameterNotFound(String name, String[] parameters) {
 454  1
             super("Parameter not found for name '" + name + "' amongst '" + asList(parameters) + "'");
 455  1
         }
 456  
 
 457  
         public ParameterNotFound(int position, String[] parameters) {
 458  2
             super("Parameter not found for position '" + position + "' amongst '" + asList(parameters) + "'");
 459  2
         }
 460  
     }
 461  
 
 462  287
     public static abstract class AbstractStep implements Step {
 463  
 
 464  
             public String asString(Keywords keywords) {
 465  0
                         return toString();
 466  
                 }
 467  
             
 468  
         @Override
 469  
         public String toString() {
 470  0
             return ToStringBuilder.reflectionToString(this, ToStringStyle.SIMPLE_STYLE);
 471  
         }
 472  
 
 473  
     }
 474  
 
 475  
     private class BeforeOrAfterStep extends AbstractStep {
 476  
         private final Method method;
 477  
         private final Meta meta;
 478  
 
 479  48
         public BeforeOrAfterStep(Method method, Meta meta) {
 480  48
             this.method = method;
 481  48
             this.meta = meta;
 482  48
         }
 483  
 
 484  
         public StepResult perform(UUIDExceptionWrapper storyFailureIfItHappened) {
 485  39
             ParameterConverters paramConvertersWithExceptionInjector = paramConvertersWithExceptionInjector(storyFailureIfItHappened);
 486  39
             MethodInvoker methodInvoker = new MethodInvoker(method, paramConvertersWithExceptionInjector, paranamer,
 487  
                     meta);
 488  39
             Timer timer = new Timer().start();
 489  
             try {
 490  39
                 methodInvoker.invoke();
 491  35
                 return silent(method).withDurationInMillis(timer.stop());
 492  4
             } catch (InvocationTargetException e) {
 493  4
                 return failed(method, new UUIDExceptionWrapper(new BeforeOrAfterFailed(method, e.getCause())))
 494  4
                         .withDurationInMillis(timer.stop());
 495  0
             } catch (Throwable t) {
 496  0
                 return failed(method, new UUIDExceptionWrapper(new BeforeOrAfterFailed(method, t)))
 497  0
                         .withDurationInMillis(timer.stop());
 498  
             }
 499  
         }
 500  
 
 501  
         private ParameterConverters paramConvertersWithExceptionInjector(UUIDExceptionWrapper storyFailureIfItHappened) {
 502  39
             return parameterConverters.newInstanceAdding(new UUIDExceptionWrapperInjector(storyFailureIfItHappened));
 503  
         }
 504  
 
 505  
         public StepResult doNotPerform(UUIDExceptionWrapper storyFailureIfItHappened) {
 506  6
             return perform(storyFailureIfItHappened);
 507  
         }
 508  
 
 509  
         private class UUIDExceptionWrapperInjector implements ParameterConverters.ParameterConverter {
 510  
             private final UUIDExceptionWrapper storyFailureIfItHappened;
 511  
 
 512  39
             public UUIDExceptionWrapperInjector(UUIDExceptionWrapper storyFailureIfItHappened) {
 513  39
                 this.storyFailureIfItHappened = storyFailureIfItHappened;
 514  39
             }
 515  
 
 516  
             public boolean accept(Type type) {
 517  14
                 return UUIDExceptionWrapper.class == type;
 518  
             }
 519  
 
 520  
             public Object convertValue(String value, Type type) {
 521  6
                 return storyFailureIfItHappened;
 522  
             }
 523  
         }
 524  
 
 525  
                 public String asString(Keywords keywords) {
 526  0
                         return method.getName()+";"+meta.asString(keywords);
 527  
                 }
 528  
     }
 529  
 
 530  
     public class UponSuccessStep extends AbstractStep {
 531  
         private BeforeOrAfterStep beforeOrAfterStep;
 532  
 
 533  5
         public UponSuccessStep(Method method, Meta storyAndScenarioMeta) {
 534  5
             this.beforeOrAfterStep = new BeforeOrAfterStep(method, storyAndScenarioMeta);
 535  5
         }
 536  
 
 537  
         public StepResult doNotPerform(UUIDExceptionWrapper storyFailureIfItHappened) {
 538  3
             return skipped();
 539  
         }
 540  
 
 541  
         public StepResult perform(UUIDExceptionWrapper storyFailureIfItHappened) {
 542  2
             return beforeOrAfterStep.perform(storyFailureIfItHappened);
 543  
         }
 544  
 
 545  
                 public String asString(Keywords keywords) {
 546  0
                         return beforeOrAfterStep.asString(keywords);
 547  
                 }
 548  
 
 549  
     }
 550  
 
 551  
     public class UponFailureStep extends AbstractStep {
 552  
         private final BeforeOrAfterStep beforeOrAfterStep;
 553  
 
 554  5
         public UponFailureStep(Method method, Meta storyAndScenarioMeta) {
 555  5
             this.beforeOrAfterStep = new BeforeOrAfterStep(method, storyAndScenarioMeta);
 556  5
         }
 557  
 
 558  
         public StepResult doNotPerform(UUIDExceptionWrapper storyFailureIfItHappened) {
 559  3
             return beforeOrAfterStep.perform(storyFailureIfItHappened);
 560  
         }
 561  
 
 562  
         public StepResult perform(UUIDExceptionWrapper storyFailureIfItHappened) {
 563  2
             return skipped();
 564  
         }
 565  
 
 566  
                 public String asString(Keywords keywords) {
 567  0
                         return beforeOrAfterStep.asString(keywords);
 568  
                 }
 569  
     
 570  
     }
 571  
     
 572  
     public class ParametrisedStep extends AbstractStep {
 573  
         private Object[] convertedParameters;
 574  
         private String parametrisedStep;
 575  
         private final String stepAsString;
 576  
         private final Method method;
 577  
         private final String stepWithoutStartingWord;
 578  
         private final Map<String, String> namedParameters;
 579  
 
 580  
         public ParametrisedStep(String stepAsString, Method method, String stepWithoutStartingWord,
 581  142
                 Map<String, String> namedParameters) {
 582  142
             this.stepAsString = stepAsString;
 583  142
             this.method = method;
 584  142
             this.stepWithoutStartingWord = stepWithoutStartingWord;
 585  142
             this.namedParameters = namedParameters;
 586  142
         }
 587  
 
 588  
         public void describeTo(StoryReporter storyReporter) {
 589  38
             storyReporter.beforeStep(stepAsString);
 590  38
         }
 591  
 
 592  
         public StepResult perform(UUIDExceptionWrapper storyFailureIfItHappened) {
 593  121
             Timer timer = new Timer().start();
 594  
             try {
 595  121
                 parametriseStep();
 596  118
                 stepMonitor.performing(parametrisedStep, dryRun);
 597  118
                 if (!dryRun) {
 598  114
                     method.invoke(stepsInstance(), convertedParameters);
 599  
                 }
 600  103
                 return successful(stepAsString).withParameterValues(parametrisedStep)
 601  102
                         .withDurationInMillis(timer.stop());
 602  1
             } catch (ParameterNotFound e) {
 603  
                 // step parametrisation failed, return pending StepResult
 604  1
                 return pending(stepAsString).withParameterValues(parametrisedStep);
 605  15
             } catch (InvocationTargetException e) {
 606  15
                 if (e.getCause() instanceof RestartingScenarioFailure) {
 607  1
                     throw (RestartingScenarioFailure) e.getCause();
 608  
                 }
 609  14
                 Throwable failureCause = e.getCause();
 610  14
                 if (failureCause instanceof UUIDExceptionWrapper) {
 611  1
                     failureCause = failureCause.getCause();
 612  
                 }
 613  14
                 return failed(stepAsString, new UUIDExceptionWrapper(stepAsString, failureCause)).withParameterValues(
 614  14
                         parametrisedStep).withDurationInMillis(timer.stop());
 615  2
             } catch (Throwable t) {
 616  2
                 return failed(stepAsString, new UUIDExceptionWrapper(stepAsString, t)).withParameterValues(
 617  2
                         parametrisedStep).withDurationInMillis(timer.stop());
 618  
             }
 619  
         }
 620  
 
 621  
         public StepResult doNotPerform(UUIDExceptionWrapper storyFailureIfItHappened) {
 622  
             try {
 623  3
                 parametriseStep();
 624  1
             } catch (Throwable t) {
 625  
                 // step parametrisation failed, but still return
 626  
                 // notPerformed StepResult
 627  2
             }
 628  3
             return notPerformed(stepAsString).withParameterValues(parametrisedStep);
 629  
         }
 630  
 
 631  
                 public String asString(Keywords keywords) {
 632  0
                         if ( parametrisedStep == null){
 633  0
                                 parametriseStep();
 634  
                         }
 635  0
                 return parametrisedStep;
 636  
         }
 637  
         
 638  
         private void parametriseStep() {
 639  124
             stepMatcher.find(stepWithoutStartingWord);
 640  122
             ParameterName[] names = parameterNames(method);
 641  122
             Type[] types = method.getGenericParameterTypes();
 642  122
             String[] parameterValues = parameterValuesForStep(namedParameters, types, names);
 643  120
             convertedParameters = convertParameterValues(parameterValues, types);
 644  120
             addNamedParametersToExamplesTables();
 645  120
             parametrisedStep = parametrisedStep(stepAsString, namedParameters, types, names, parameterValues);
 646  120
         }
 647  
 
 648  
         private void addNamedParametersToExamplesTables() {
 649  263
             for (Object object : convertedParameters) {
 650  143
                 if (object instanceof ExamplesTable) {
 651  0
                     ((ExamplesTable) object).withNamedParameters(namedParameters);
 652  
                 }
 653  
             }
 654  120
         }
 655  
 
 656  
     }
 657  
 
 658  
     public class UponAnyParametrisedStep extends AbstractStep {
 659  
         private ParametrisedStep parametrisedStep;
 660  
 
 661  
         public UponAnyParametrisedStep(String stepAsString, Method method, String stepWithoutStartingWord,
 662  0
                 Map<String, String> namedParameters){
 663  0
             this.parametrisedStep = new ParametrisedStep(stepAsString, method, stepWithoutStartingWord, namedParameters);
 664  0
         }
 665  
 
 666  
         public StepResult doNotPerform(UUIDExceptionWrapper storyFailureIfItHappened) {
 667  0
             return perform(storyFailureIfItHappened);
 668  
         }
 669  
 
 670  
         public StepResult perform(UUIDExceptionWrapper storyFailureIfItHappened) {
 671  0
             return parametrisedStep.perform(storyFailureIfItHappened);
 672  
         }
 673  
 
 674  
                 public String asString(Keywords keywords) {
 675  0
                         return parametrisedStep.asString(keywords);
 676  
                 }
 677  
 
 678  
     }
 679  
 
 680  
     public class UponSuccessParametrisedStep extends AbstractStep {
 681  
         private ParametrisedStep parametrisedStep;
 682  
 
 683  
         public UponSuccessParametrisedStep(String stepAsString, Method method, String stepWithoutStartingWord,
 684  0
                 Map<String, String> namedParameters){
 685  0
             this.parametrisedStep = new ParametrisedStep(stepAsString, method, stepWithoutStartingWord, namedParameters);
 686  0
         }
 687  
 
 688  
         public StepResult doNotPerform(UUIDExceptionWrapper storyFailureIfItHappened) {
 689  0
             return skipped();
 690  
         }
 691  
 
 692  
         public StepResult perform(UUIDExceptionWrapper storyFailureIfItHappened) {
 693  0
             return parametrisedStep.perform(storyFailureIfItHappened);
 694  
         }
 695  
 
 696  
                 public String asString(Keywords keywords) {
 697  0
                         return parametrisedStep.asString(keywords);
 698  
                 }
 699  
 
 700  
     }
 701  
 
 702  
     public class UponFailureParametrisedStep extends AbstractStep {
 703  
         private ParametrisedStep parametrisedStep;
 704  
 
 705  
         public UponFailureParametrisedStep(String stepAsString, Method method, String stepWithoutStartingWord,
 706  0
                 Map<String, String> namedParameters){
 707  0
             this.parametrisedStep = new ParametrisedStep(stepAsString, method, stepWithoutStartingWord, namedParameters);
 708  0
         }
 709  
 
 710  
         public StepResult doNotPerform(UUIDExceptionWrapper storyFailureIfItHappened) {
 711  0
             return parametrisedStep.perform(storyFailureIfItHappened);
 712  
         }
 713  
 
 714  
         public StepResult perform(UUIDExceptionWrapper storyFailureIfItHappened) {
 715  0
             return skipped();
 716  
         }
 717  
 
 718  
                 public String asString(Keywords keywords) {
 719  0
                         return parametrisedStep.asString(keywords);
 720  
                 }
 721  
     
 722  
     }
 723  
 
 724  
     public static class PendingStep extends AbstractStep {
 725  
         private final String stepAsString;
 726  
         private final String previousNonAndStep;
 727  
         private Method method;
 728  
 
 729  84
         public PendingStep(String stepAsString, String previousNonAndStep) {
 730  84
             this.stepAsString = stepAsString;
 731  84
             this.previousNonAndStep = previousNonAndStep;
 732  84
         }
 733  
 
 734  
         public StepResult perform(UUIDExceptionWrapper storyFailureIfItHappened) {
 735  12
             return pending(stepAsString);
 736  
         }
 737  
 
 738  
         public StepResult doNotPerform(UUIDExceptionWrapper storyFailureIfItHappened) {
 739  1
             return pending(stepAsString);
 740  
         }
 741  
 
 742  
         public String stepAsString() {
 743  13
             return stepAsString;
 744  
         }
 745  
 
 746  
         public String previousNonAndStepAsString() {
 747  13
             return previousNonAndStep;
 748  
         }
 749  
 
 750  
         public void annotatedOn(Method method) {
 751  0
             this.method = method;
 752  0
         }
 753  
 
 754  
         public boolean annotated() {
 755  0
             return method != null;
 756  
         }
 757  
 
 758  
                 public String asString(Keywords keywords) {
 759  0
                         return stepAsString;
 760  
                 }
 761  
 
 762  
     }
 763  
 
 764  
     public static class IgnorableStep extends AbstractStep {
 765  
         private final String stepAsString;
 766  
 
 767  2
         public IgnorableStep(String stepAsString) {
 768  2
             this.stepAsString = stepAsString;
 769  2
         }
 770  
 
 771  
         public StepResult perform(UUIDExceptionWrapper storyFailureIfItHappened) {
 772  2
             return ignorable(stepAsString);
 773  
         }
 774  
 
 775  
         public StepResult doNotPerform(UUIDExceptionWrapper storyFailureIfItHappened) {
 776  1
             return ignorable(stepAsString);
 777  
         }
 778  
         
 779  
                 public String asString(Keywords keywords) {
 780  0
                         return stepAsString;
 781  
                 }
 782  
 
 783  
     }
 784  
 
 785  
     private class MethodInvoker {
 786  
         private final Method method;
 787  
         private final ParameterConverters parameterConverters;
 788  
         private final Paranamer paranamer;
 789  
         private final Meta meta;
 790  
         private final Type[] parameterTypes;
 791  
         
 792  39
         public MethodInvoker(Method method, ParameterConverters parameterConverters, Paranamer paranamer, Meta meta) {
 793  39
             this.method = method;
 794  39
             this.parameterConverters = parameterConverters;
 795  39
             this.paranamer = paranamer;
 796  39
             this.meta = meta;
 797  39
             this.parameterTypes = method.getGenericParameterTypes();
 798  39
         }
 799  
 
 800  
         public void invoke() throws InvocationTargetException, IllegalAccessException {
 801  39
             method.invoke(stepsInstance(), parameterValuesFrom(meta));
 802  35
         }
 803  
 
 804  
         private Parameter[] methodParameters() {
 805  39
             Parameter[] parameters = new Parameter[parameterTypes.length];
 806  39
             String[] annotatedNames = annotatedParameterNames(method);
 807  39
             String[] paranamerNames = paranamer.lookupParameterNames(method, false);
 808  
 
 809  63
             for (int position = 0; position < parameterTypes.length; position++) {
 810  24
                 String name = parameterNameFor(position, annotatedNames, paranamerNames);
 811  24
                 parameters[position] = new Parameter(position, parameterTypes[position], name);
 812  
             }
 813  
 
 814  39
             return parameters;
 815  
         }
 816  
 
 817  
         private String parameterNameFor(int position, String[] annotatedNames, String[] paranamerNames) {
 818  24
             String annotatedName = nameByPosition(annotatedNames, position);
 819  24
             String paranamerName = nameByPosition(paranamerNames, position);
 820  24
             if (annotatedName != null) {
 821  16
                 return annotatedName;
 822  8
             } else if (paranamerName != null) {
 823  2
                 return paranamerName;
 824  
             }
 825  6
             return null;
 826  
         }
 827  
 
 828  
         private String nameByPosition(String[] names, int position) {
 829  48
             return position < names.length ? names[position] : null;
 830  
         }
 831  
 
 832  
         private Object[] parameterValuesFrom(Meta meta) {
 833  39
             Object[] values = new Object[parameterTypes.length];
 834  63
             for (Parameter parameter : methodParameters()) {
 835  24
                     values[parameter.position] = parameterConverters.convert(parameter.valueFrom(meta), parameter.type);
 836  
             }
 837  39
             return values;
 838  
         }
 839  
 
 840  48
         private class Parameter {
 841  
             private final int position;
 842  
             private final Type type;
 843  
             private final String name;
 844  
 
 845  24
             public Parameter(int position, Type type, String name) {
 846  24
                 this.position = position;
 847  24
                 this.type = type;
 848  24
                 this.name = name;
 849  24
             }
 850  
 
 851  
             public String valueFrom(Meta meta) {
 852  24
                 if (name == null) {
 853  6
                     return null;
 854  
                 }
 855  18
                 return meta.getProperty(name);
 856  
             }
 857  
         }
 858  
     }
 859  
 
 860  638
     private static class ParameterName {
 861  
         private String name;
 862  
         private boolean annotated;
 863  
 
 864  150
         private ParameterName(String name, boolean annotated) {
 865  150
             this.name = name;
 866  150
             this.annotated = annotated;
 867  150
         }
 868  
     }
 869  
 
 870  
 }