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