StepCreator.java

  1. package org.jbehave.core.steps;

  2. import static java.util.Arrays.asList;
  3. import static org.jbehave.core.steps.AbstractStepResult.comment;
  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. import static org.jbehave.core.steps.ParameterConverters.ExamplesTableParametersConverter.isExamplesTableParameters;

  11. import java.lang.annotation.Annotation;
  12. import java.lang.reflect.AccessibleObject;
  13. import java.lang.reflect.InvocationTargetException;
  14. import java.lang.reflect.Method;
  15. import java.lang.reflect.Type;
  16. import java.util.ArrayList;
  17. import java.util.Collections;
  18. import java.util.HashMap;
  19. import java.util.List;
  20. import java.util.Map;
  21. import java.util.Map.Entry;
  22. import java.util.Optional;
  23. import java.util.function.Function;
  24. import java.util.regex.Matcher;
  25. import java.util.regex.Pattern;
  26. import java.util.stream.Collectors;
  27. import java.util.stream.Stream;

  28. import com.thoughtworks.paranamer.NullParanamer;
  29. import com.thoughtworks.paranamer.Paranamer;

  30. import org.apache.commons.lang3.builder.ToStringBuilder;
  31. import org.apache.commons.lang3.builder.ToStringStyle;
  32. import org.jbehave.core.annotations.AfterScenario.Outcome;
  33. import org.jbehave.core.annotations.Conditional;
  34. import org.jbehave.core.annotations.FromContext;
  35. import org.jbehave.core.annotations.Named;
  36. import org.jbehave.core.annotations.ToContext;
  37. import org.jbehave.core.condition.StepConditionMatchException;
  38. import org.jbehave.core.condition.StepConditionMatcher;
  39. import org.jbehave.core.configuration.Keywords;
  40. import org.jbehave.core.expressions.ExpressionResolver;
  41. import org.jbehave.core.failures.BeforeOrAfterFailed;
  42. import org.jbehave.core.failures.IgnoringStepsFailure;
  43. import org.jbehave.core.failures.RestartingScenarioFailure;
  44. import org.jbehave.core.failures.UUIDExceptionWrapper;
  45. import org.jbehave.core.model.ExamplesTable;
  46. import org.jbehave.core.model.Meta;
  47. import org.jbehave.core.model.Verbatim;
  48. import org.jbehave.core.parsers.StepMatcher;
  49. import org.jbehave.core.reporters.StoryReporter;
  50. import org.jbehave.core.steps.ParameterConverters.FromStringParameterConverter;
  51. import org.jbehave.core.steps.context.StepsContext;

  52. @SuppressWarnings("checkstyle:AvoidEscapedUnicodeCharacters")
  53. public class StepCreator {

  54.     public static final String PARAMETER_TABLE_START = "\uff3b";
  55.     public static final String PARAMETER_TABLE_END = "\uff3d";
  56.     public static final String PARAMETER_VERBATIM_START = "\u301a";
  57.     public static final String PARAMETER_VERBATIM_END = "\u301b";
  58.     public static final String PARAMETER_VALUE_START = "\uFF5F";
  59.     public static final String PARAMETER_VALUE_END = "\uFF60";
  60.     public static final String PARAMETER_VALUE_NEWLINE = "\u2424";
  61.     public static final UUIDExceptionWrapper NO_FAILURE = new UUIDExceptionWrapper("no failure");
  62.     private static final String NEWLINE = "\n";
  63.     private static final String SPACE = " ";
  64.     private static final String NONE = "";
  65.     private final Class<?> stepsType;
  66.     private final InjectableStepsFactory stepsFactory;
  67.     private final ParameterConverters parameterConverters;
  68.     private final ExpressionResolver expressionResolver;
  69.     private final ParameterControls parameterControls;
  70.     private final Pattern delimitedNamePattern;
  71.     private final StepMatcher stepMatcher;
  72.     private final StepsContext stepsContext;
  73.     private final boolean dryRun;
  74.     private StepMonitor stepMonitor;
  75.     private Paranamer paranamer = new NullParanamer();

  76.     public StepCreator(Class<?> stepsType, InjectableStepsFactory stepsFactory, StepsContext stepsContext,
  77.             ParameterConverters parameterConverters, ExpressionResolver expressionResolver,
  78.             ParameterControls parameterControls, StepMatcher stepMatcher, StepMonitor stepMonitor, boolean dryRun) {
  79.         this.stepsType = stepsType;
  80.         this.stepsFactory = stepsFactory;
  81.         this.stepsContext = stepsContext;
  82.         this.parameterConverters = parameterConverters;
  83.         this.expressionResolver = expressionResolver;
  84.         this.parameterControls = parameterControls;
  85.         this.stepMatcher = stepMatcher;
  86.         this.stepMonitor = stepMonitor;
  87.         this.delimitedNamePattern = Pattern.compile(parameterControls.nameDelimiterLeft() + "([\\w\\-\\h.]+?)"
  88.                 + parameterControls.nameDelimiterRight(), Pattern.DOTALL);
  89.         this.dryRun = dryRun;
  90.     }

  91.     public void useStepMonitor(StepMonitor stepMonitor) {
  92.         this.stepMonitor = stepMonitor;
  93.     }

  94.     public void useParanamer(Paranamer paranamer) {
  95.         this.paranamer = paranamer;
  96.     }

  97.     public Object stepsInstance() {
  98.         return stepsFactory.createInstanceOfType(stepsType);
  99.     }

  100.     public Step createBeforeOrAfterStep(Method method, Meta meta) {
  101.         return new BeforeOrAfterStep(method, meta);
  102.     }

  103.     public Step createAfterStepUponOutcome(final Method method, final Outcome outcome, Meta storyAndScenarioMeta) {
  104.         Step beforeOrAfterStep = createBeforeOrAfterStep(method, storyAndScenarioMeta);
  105.         return wrapStepUponOutcome(outcome, beforeOrAfterStep);
  106.     }

  107.     public Map<String, String> matchedParameters(final Method method, final String stepWithoutStartingWord,
  108.             final Map<String, String> namedParameters) {
  109.         Map<String, String> matchedParameters = new HashMap<>();
  110.         Matcher matcher = stepMatcher.matcher(stepWithoutStartingWord);
  111.         if (matcher.find()) {
  112.             // we've found a match, populate map
  113.             ParameterName[] parameterNames = parameterNames(method);
  114.             Type[] types = parameterTypes(method, parameterNames);

  115.             String[] values = parameterValuesForStep(matcher, namedParameters, types, parameterNames, false);
  116.             for (int i = 0; i < parameterNames.length; i++) {
  117.                 String name = parameterNames[i].name;
  118.                 if (name == null) {
  119.                     name = stepMatcher.parameterNames()[i];
  120.                 }
  121.                 matchedParameters.put(name, values[i]);
  122.             }
  123.         }
  124.         // else return empty map
  125.         return matchedParameters;
  126.     }

  127.     /**
  128.      * Returns the {@link ParameterName} representations for the method,
  129.      * providing an abstraction that supports both annotated and non-annotated
  130.      * parameters.
  131.      *
  132.      * @param method the Method
  133.      * @return The array of {@link ParameterName}s
  134.      */
  135.     private ParameterName[] parameterNames(Method method) {
  136.         ParameterName[] parameterNames;
  137.         if (method != null) {
  138.             String[] annotatedNames = annotatedParameterNames(method);
  139.             String[] paranamerNames = paranamerParameterNames(method);
  140.             String[] contextNames = contextParameterNames(method);

  141.             parameterNames = new ParameterName[annotatedNames.length];
  142.             for (int i = 0; i < annotatedNames.length; i++) {
  143.                 parameterNames[i] = parameterName(annotatedNames, paranamerNames, contextNames, i);
  144.             }
  145.         } else {
  146.             String[] stepMatcherParameterNames = stepMatcher.parameterNames();
  147.             parameterNames = new ParameterName[stepMatcherParameterNames.length];
  148.             for (int i = 0; i < stepMatcherParameterNames.length; i++) {
  149.                 parameterNames[i] = new ParameterName(stepMatcherParameterNames[i], false, false);
  150.             }
  151.         }
  152.         return parameterNames;
  153.     }

  154.     private ParameterName parameterName(String[] annotatedNames, String[] paranamerNames, String[] contextNames,
  155.             int i) {
  156.         boolean annotated = true;
  157.         boolean fromContext = false;

  158.         String name = contextNames[i];
  159.         if (name != null) {
  160.             fromContext = true;
  161.         } else {
  162.             name = annotatedNames[i];
  163.             if (name == null) {
  164.                 name = (paranamerNames.length > i ? paranamerNames[i] : null);
  165.                 annotated = false;
  166.             }
  167.         }
  168.         return new ParameterName(name, annotated, fromContext);
  169.     }

  170.     /**
  171.      * Extract parameter names using {@link Named}-annotated parameters
  172.      *
  173.      * @param method the Method with {@link Named}-annotated parameters
  174.      * @return An array of annotated parameter names, which <b>may</b> include
  175.      *         <code>null</code> values for parameters that are not annotated
  176.      */
  177.     private String[] annotatedParameterNames(Method method) {
  178.         Annotation[][] parameterAnnotations = method.getParameterAnnotations();
  179.         String[] names = new String[parameterAnnotations.length];
  180.         for (int i = 0; i < parameterAnnotations.length; i++) {
  181.             for (Annotation annotation : parameterAnnotations[i]) {
  182.                 names[i] = annotationName(annotation);
  183.             }
  184.         }
  185.         return names;
  186.     }
  187.    
  188.     /**
  189.      * Extract parameter names using {@link FromContext}-annotated parameters
  190.      *
  191.      * @param method the Method with {@link FromContext}-annotated parameters
  192.      * @return An array of annotated parameter names, which <b>may</b> include
  193.      *         <code>null</code> values for parameters that are not annotated
  194.      */
  195.     private String[] contextParameterNames(Method method) {
  196.         Annotation[][] parameterAnnotations = method.getParameterAnnotations();
  197.         String[] names = new String[parameterAnnotations.length];
  198.         for (int i = 0; i < parameterAnnotations.length; i++) {
  199.             for (Annotation annotation : parameterAnnotations[i]) {
  200.                 names[i] = contextName(annotation);
  201.             }
  202.         }
  203.         return names;
  204.     }

  205.     /**
  206.      * Returns either the value of the annotation, either {@link Named} or
  207.      * "javax.inject.Named".
  208.      *
  209.      * @param annotation the Annotation
  210.      * @return The annotated value or <code>null</code> if no annotation is
  211.      *         found
  212.      */
  213.     private String annotationName(Annotation annotation) {
  214.         if (annotation.annotationType().isAssignableFrom(Named.class)) {
  215.             return ((Named) annotation).value();
  216.         } else if ("javax.inject.Named".equals(annotation.annotationType().getName())) {
  217.             return Jsr330Helper.getNamedValue(annotation);
  218.         } else {
  219.             return null;
  220.         }
  221.     }

  222.     /**
  223.      * Returns the value of the annotation {@link FromContext}.
  224.      *
  225.      * @param annotation the Annotation
  226.      * @return The annotated value or <code>null</code> if no annotation is
  227.      *         found
  228.      */
  229.     private String contextName(Annotation annotation) {
  230.         if (annotation.annotationType().isAssignableFrom(FromContext.class)) {
  231.             return ((FromContext) annotation).value();
  232.         } else {
  233.             return null;
  234.         }
  235.     }

  236.     /**
  237.      * Extract parameter names using
  238.      * {@link Paranamer#lookupParameterNames(AccessibleObject, boolean)}
  239.      *
  240.      * @param method the Method inspected by Paranamer
  241.      * @return An array of parameter names looked up by Paranamer
  242.      */
  243.     private String[] paranamerParameterNames(Method method) {
  244.         return paranamer.lookupParameterNames(method, false);
  245.     }

  246.     private Type[] parameterTypes(Method method, ParameterName[] parameterNames) {
  247.         if (method != null) {
  248.             return method.getGenericParameterTypes();
  249.         }
  250.         Type[] types = new Type[parameterNames.length];
  251.         for (int i = 0; i < types.length; i++) {
  252.             types[i] = String.class;
  253.         }
  254.         return types;
  255.     }

  256.     public Step createConditionalStep(final StepConditionMatcher stepConditionMatcher,
  257.             final Map<Method, ParametrisedStep> parametrisedSteps) {
  258.         return new ConditionalStep(stepConditionMatcher, parametrisedSteps);
  259.     }

  260.     public Step createParametrisedStep(final Method method, final String stepAsString,
  261.             final String stepWithoutStartingWord, final Map<String, String> namedParameters,
  262.             final List<Step> composedSteps) {
  263.         return new ParametrisedStep(stepAsString, method, stepWithoutStartingWord, namedParameters, composedSteps);
  264.     }

  265.     public Step createParametrisedStepUponOutcome(final Method method, final String stepAsString,
  266.             final String stepWithoutStartingWord, final Map<String, String> namedParameters,
  267.             final List<Step> composedSteps, Outcome outcome) {
  268.         Step parametrisedStep = createParametrisedStep(method, stepAsString, stepWithoutStartingWord, namedParameters,
  269.                 composedSteps);
  270.         return wrapStepUponOutcome(outcome, parametrisedStep);
  271.     }

  272.     private Step wrapStepUponOutcome(Outcome outcome, Step step) {
  273.         switch (outcome) {
  274.             case ANY:
  275.                 return new UponAnyStep(step);
  276.             case SUCCESS:
  277.                 return new UponSuccessStep(step);
  278.             case FAILURE:
  279.                 return new UponFailureStep(step);
  280.             default:
  281.                 return step;
  282.         }
  283.     }

  284.     private String parametrisedStep(String stepAsString, Map<String, String> namedParameters, Type[] types,
  285.             String[] parameterValues) {
  286.         String parametrisedStep = stepAsString;
  287.         // mark parameter values that are parsed
  288.         boolean hasTable = hasTable(types);
  289.         for (int position = 0; position < types.length; position++) {
  290.             parametrisedStep = markParsedParameterValue(parametrisedStep, types[position], parameterValues[position],
  291.                     hasTable);
  292.         }
  293.         // mark parameter values that are named
  294.         for (Entry<String, String> namedParameter : namedParameters.entrySet()) {
  295.             parametrisedStep = parameterControls.replaceAllDelimitedNames(parametrisedStep, namedParameter.getKey(),
  296.                     markedValue(namedParameter.getValue()));
  297.         }

  298.         return parametrisedStep;
  299.     }

  300.     private boolean hasTable(Type[] types) {
  301.         for (Type type : types) {
  302.             if (isTable(type)) {
  303.                 return true;
  304.             }
  305.         }
  306.         return false;
  307.     }

  308.     private String markParsedParameterValue(String stepText, Type type, String value, boolean hasTable) {
  309.         if (value != null) {
  310.             // only mark non-empty string as parameter (JBEHAVE-656)
  311.             if (value.trim().length() != 0) {
  312.                 if (isTable(type)) {
  313.                     return stepText.replace(value, markedTable(value));
  314.                 }
  315.                 if (isVerbatim(type)) {
  316.                     return stepText.replace(value, markedVerbatim(value));
  317.                 }
  318.                 String markedValue = markedValue(value);
  319.                 // identify parameter values to mark as padded by spaces to avoid duplicated replacements of overlapping
  320.                 // values (JBEHAVE-837)
  321.                 String leftPad = SPACE;
  322.                 String rightPad = stepText.endsWith(value) ? NONE : SPACE;
  323.                 return stepText.replace(pad(value, leftPad, rightPad), pad(markedValue, leftPad, rightPad));
  324.             }
  325.             if (!hasTable) {
  326.                 return stepText.replace(NEWLINE, PARAMETER_VALUE_NEWLINE);
  327.             }
  328.         }
  329.         return stepText;
  330.     }

  331.     private String markedTable(String value) {
  332.         return pad(value, PARAMETER_TABLE_START, PARAMETER_TABLE_END);
  333.     }

  334.     private String markedVerbatim(String value) {
  335.         return pad(value, PARAMETER_VERBATIM_START, PARAMETER_VERBATIM_END);
  336.     }

  337.     private String markedValue(String value) {
  338.         return pad(value, PARAMETER_VALUE_START, PARAMETER_VALUE_END);
  339.     }

  340.     private String pad(String value, String left, String right) {
  341.         return new StringBuilder().append(left).append(value).append(right).toString();
  342.     }

  343.     private boolean isTable(Type type) {
  344.         return isExamplesTable(type) || isExamplesTableParameters(type);
  345.     }

  346.     private boolean isVerbatim(Type type) {
  347.         return type instanceof Class && Verbatim.class.isAssignableFrom((Class<?>) type);
  348.     }

  349.     private boolean isExamplesTable(Type type) {
  350.         return type instanceof Class && ExamplesTable.class.isAssignableFrom((Class<?>) type);
  351.     }

  352.     private String[] parameterValuesForStep(Matcher matcher, Map<String, String> namedParameters, Type[] types,
  353.             ParameterName[] names, boolean overrideWithTableParameters) {
  354.         final String[] parameters = new String[types.length];
  355.         for (int position = 0; position < types.length; position++) {
  356.             parameters[position] = parameterForPosition(matcher, position, names, namedParameters,
  357.                     overrideWithTableParameters);
  358.         }
  359.         return parameters;
  360.     }

  361.     private String parameterForPosition(Matcher matcher, int position, ParameterName[] names,
  362.             Map<String, String> namedParameters, boolean overrideWithTableParameters) {
  363.         int namePosition = parameterPosition(names, position);
  364.         String parameter = null;

  365.         if (namePosition != -1) {
  366.             String name = names[position].name;
  367.             boolean annotated = names[position].annotated;
  368.             boolean fromContext = names[position].fromContext;

  369.             List<String> delimitedNames = Collections.emptyList();

  370.             if (isGroupName(name)) {
  371.                 parameter = matchedParameter(matcher, name);
  372.                 delimitedNames = delimitedNameFor(parameter);

  373.                 if (delimitedNames.isEmpty()) {
  374.                     monitorUsingNameForParameter(name, position, annotated);
  375.                 }
  376.             }

  377.             if (!delimitedNames.isEmpty()) {
  378.                 parameter = replaceAllDelimitedNames(delimitedNames, position, annotated, parameter, namedParameters);
  379.                 delimitedNames = delimitedNameFor(parameter);
  380.                 parameter = replaceAllDelimitedNames(delimitedNames, position, annotated, parameter, namedParameters);
  381.             } else if (overrideWithTableParameters && namedParameters.containsKey(name)) {
  382.                 parameter = namedParameters.get(name);
  383.                 if (parameter != null) {
  384.                     monitorUsingTableNameForParameter(name, position, annotated);
  385.                 }
  386.             }
  387.            
  388.             if (fromContext && parameter == null) {
  389.                 parameter = name;
  390.                 stepMonitor.usingStepsContextParameter(parameter);
  391.             }

  392.         }

  393.         if (parameter == null) {
  394.             // This allows parameters to be in a different order.
  395.             position = position - numberOfPreviousFromContext(names, position);
  396.             stepMonitor.usingNaturalOrderForParameter(position);
  397.             parameter = matchedParameter(matcher, position);

  398.             String previousParameterValue;
  399.             do {
  400.                 previousParameterValue = parameter;

  401.                 for (String delimitedName : delimitedNameFor(parameter)) {
  402.                     parameter = replaceAllDelimitedNames(parameter, delimitedName, namedParameters);
  403.                 }
  404.             } while (parameter != null && !previousParameterValue.equals(parameter));
  405.         }

  406.         stepMonitor.foundParameter(parameter, position);

  407.         return parameter;
  408.     }

  409.     private String replaceAllDelimitedNames(List<String> delimitedNames, int position, boolean annotated,
  410.                                             String parameter, Map<String, String> namedParameters) {
  411.         String parameterWithDelimitedNames = parameter;
  412.         for (String delimitedName : delimitedNames) {
  413.             monitorUsingTableNameForParameter(delimitedName, position, annotated);
  414.             parameterWithDelimitedNames = replaceAllDelimitedNames(parameterWithDelimitedNames, delimitedName,
  415.                     namedParameters);
  416.         }
  417.         return parameterWithDelimitedNames;
  418.     }

  419.     private String replaceAllDelimitedNames(String parameterWithDelimitedNames, String delimitedName,
  420.             Map<String, String> namedParameters) {
  421.         if (namedParameters.containsKey(delimitedName)) {
  422.             return parameterControls.replaceAllDelimitedNames(parameterWithDelimitedNames, delimitedName,
  423.                     namedParameters.get(delimitedName));
  424.         }
  425.         return parameterWithDelimitedNames;
  426.     }

  427.     private int numberOfPreviousFromContext(ParameterName[] names, int currentPosition) {
  428.         int number = 0;

  429.         for (int i = currentPosition - 1; i >= 0; i--) {
  430.             if (names[i].fromContext) {
  431.                 number++;
  432.             }
  433.         }
  434.        
  435.         return number;
  436.     }

  437.     private void monitorUsingTableNameForParameter(String name, int position, boolean usingAnnotationNames) {
  438.         if (usingAnnotationNames) {
  439.             stepMonitor.usingTableAnnotatedNameForParameter(name, position);
  440.         } else {
  441.             stepMonitor.usingTableParameterNameForParameter(name, position);
  442.         }
  443.     }

  444.     private void monitorUsingNameForParameter(String name, int position, boolean usingAnnotationNames) {
  445.         if (usingAnnotationNames) {
  446.             stepMonitor.usingAnnotatedNameForParameter(name, position);
  447.         } else {
  448.             stepMonitor.usingParameterNameForParameter(name, position);
  449.         }
  450.     }

  451.     private List<String> delimitedNameFor(String parameter) {
  452.         List<String> delimitedNames = new ArrayList<>();
  453.         if (parameterControls.delimiterNamedParameters()) {
  454.             Matcher matcher = delimitedNamePattern.matcher(parameter);
  455.             while (matcher.find()) {
  456.                 delimitedNames.add(matcher.group(1));
  457.             }
  458.         }
  459.         return delimitedNames;
  460.     }

  461.     String matchedParameter(Matcher matcher, String name) {
  462.         String[] parameterNames = stepMatcher.parameterNames();
  463.         for (int i = 0; i < parameterNames.length; i++) {
  464.             String parameterName = parameterNames[i];
  465.             if (name.equals(parameterName)) {
  466.                 return matchedParameter(matcher, i);
  467.             }
  468.         }
  469.         throw new ParameterNotFound(name, parameterNames);
  470.     }

  471.     private String matchedParameter(Matcher matcher, int position) {
  472.         String[] parameterNames = stepMatcher.parameterNames();
  473.         int matchedPosition = position + 1;
  474.         if (matchedPosition <= parameterNames.length) {
  475.             return matcher.group(matchedPosition);
  476.         }
  477.         throw new ParameterNotFound(position, parameterNames);
  478.     }

  479.     private int parameterPosition(ParameterName[] names, int position) {
  480.         if (names.length == 0) {
  481.             return -1;
  482.         }
  483.         String positionName = names[position].name;
  484.         for (int i = 0; i < names.length; i++) {
  485.             String name = names[i].name;
  486.             if (name != null && name.equals(positionName)) {
  487.                 return i;
  488.             }
  489.         }
  490.         return -1;
  491.     }

  492.     private boolean isGroupName(String name) {
  493.         String[] groupNames = stepMatcher.parameterNames();
  494.         for (String groupName : groupNames) {
  495.             if (name.equals(groupName)) {
  496.                 return true;
  497.             }
  498.         }
  499.         return false;
  500.     }

  501.     public static Step createPendingStep(final String stepAsString, String previousNonAndStep) {
  502.         return new PendingStep(stepAsString, previousNonAndStep);
  503.     }

  504.     public static Step createIgnorableStep(final String stepAsString) {
  505.         return new IgnorableStep(stepAsString);
  506.     }

  507.     public static Step createComment(final String stepAsString) {
  508.         return new Comment(stepAsString);
  509.     }

  510.     private void storeOutput(Object object, Method method) {
  511.         ToContext annotation = method.getAnnotation(ToContext.class);
  512.         if (annotation != null) {
  513.             stepsContext.put(annotation.value(), object, annotation.retentionLevel());
  514.         }
  515.     }

  516.     /**
  517.      * This is a different class, because the @Inject jar may not be in the
  518.      * classpath.
  519.      */
  520.     public static class Jsr330Helper {

  521.         private static String getNamedValue(Annotation annotation) {
  522.             return ((javax.inject.Named) annotation).value();
  523.         }

  524.     }

  525.     @SuppressWarnings("serial")
  526.     public static class ParameterNotFound extends RuntimeException {

  527.         public ParameterNotFound(String name, String[] parameters) {
  528.             super("Parameter not found for name '" + name + "' amongst '" + asList(parameters) + "'");
  529.         }

  530.         public ParameterNotFound(int position, String[] parameters) {
  531.             super("Parameter not found for position '" + position + "' amongst '" + asList(parameters) + "'");
  532.         }
  533.     }

  534.     public abstract static class AbstractStep implements Step {

  535.         @Override
  536.         public String asString(Keywords keywords) {
  537.             return toString();
  538.         }

  539.         @Override
  540.         public String toString() {
  541.             return ToStringBuilder.reflectionToString(this, ToStringStyle.SIMPLE_STYLE);
  542.         }

  543.         @Override
  544.         public List<Step> getComposedSteps() {
  545.             return Collections.emptyList();
  546.         }
  547.     }

  548.     public abstract static class ReportingAbstractStep extends AbstractStep {

  549.         private final StepExecutionType stepExecutionType;
  550.         private final String stepAsString;

  551.         public ReportingAbstractStep(StepExecutionType stepExecutionType, String stepAsString) {
  552.             this.stepExecutionType = stepExecutionType;
  553.             this.stepAsString = stepAsString;
  554.         }

  555.         @Override
  556.         public final StepResult perform(StoryReporter storyReporter, UUIDExceptionWrapper storyFailure) {
  557.             storyReporter.beforeStep(new org.jbehave.core.model.Step(stepExecutionType, getStepAsString()));
  558.             return perform(storyFailure);
  559.         }

  560.         protected abstract StepResult perform(UUIDExceptionWrapper storyFailure);

  561.         @Override
  562.         public String asString(Keywords keywords) {
  563.             return stepAsString;
  564.         }

  565.         protected String getStepAsString() {
  566.             return stepAsString;
  567.         }
  568.     }

  569.     static class DelegatingStep extends AbstractStep {
  570.         private final Step step;

  571.         DelegatingStep(Step step) {
  572.             this.step = step;
  573.         }

  574.         @Override
  575.         public StepResult perform(StoryReporter storyReporter, UUIDExceptionWrapper storyFailureIfItHappened) {
  576.             return step.perform(storyReporter, storyFailureIfItHappened);
  577.         }

  578.         @Override
  579.         public StepResult doNotPerform(StoryReporter storyReporter, UUIDExceptionWrapper storyFailureIfItHappened) {
  580.             return step.doNotPerform(storyReporter, storyFailureIfItHappened);
  581.         }

  582.         @Override
  583.         public String asString(Keywords keywords) {
  584.             return step.asString(keywords);
  585.         }

  586.         @Override
  587.         public List<Step> getComposedSteps() {
  588.             return step.getComposedSteps();
  589.         }
  590.     }

  591.     private class BeforeOrAfterStep extends ReportingAbstractStep {
  592.         private final Method method;
  593.         private final Meta meta;

  594.         public BeforeOrAfterStep(Method method, Meta meta) {
  595.             super(StepExecutionType.EXECUTABLE, method.getName());
  596.             this.method = method;
  597.             this.meta = meta;
  598.         }

  599.         @Override
  600.         public StepResult perform(UUIDExceptionWrapper storyFailureIfItHappened) {
  601.             ParameterConverters paramConvertersWithExceptionInjector = paramConvertersWithExceptionInjector(
  602.                     storyFailureIfItHappened);
  603.             MethodInvoker methodInvoker = new MethodInvoker(method, paramConvertersWithExceptionInjector, paranamer,
  604.                     meta);
  605.             Timer timer = new Timer().start();
  606.             try {
  607.                 Object outputObject = methodInvoker.invoke();
  608.                 storeOutput(outputObject, method);
  609.                 return successful(method).setTimings(timer.stop());
  610.             } catch (InvocationTargetException e) {
  611.                 return failed(method, new UUIDExceptionWrapper(new BeforeOrAfterFailed(method, e.getCause())))
  612.                         .setTimings(timer.stop());
  613.             } catch (Throwable t) {
  614.                 return failed(method, new UUIDExceptionWrapper(new BeforeOrAfterFailed(method, t)))
  615.                         .setTimings(timer.stop());
  616.             }
  617.         }

  618.         private ParameterConverters paramConvertersWithExceptionInjector(
  619.                 UUIDExceptionWrapper storyFailureIfItHappened) {
  620.             return parameterConverters.newInstanceAdding(new UUIDExceptionWrapperInjector(storyFailureIfItHappened));
  621.         }

  622.         @Override
  623.         public StepResult doNotPerform(StoryReporter storyReporter, UUIDExceptionWrapper storyFailureIfItHappened) {
  624.             return perform(storyReporter, storyFailureIfItHappened);
  625.         }

  626.         @SuppressWarnings("checkstyle:AbbreviationAsWordInName")
  627.         private class UUIDExceptionWrapperInjector extends FromStringParameterConverter<UUIDExceptionWrapper> {
  628.             private final UUIDExceptionWrapper storyFailureIfItHappened;

  629.             public UUIDExceptionWrapperInjector(UUIDExceptionWrapper storyFailureIfItHappened) {
  630.                 this.storyFailureIfItHappened = storyFailureIfItHappened;
  631.             }

  632.             @Override
  633.             public boolean canConvertTo(Type type) {
  634.                 return UUIDExceptionWrapper.class == type;
  635.             }

  636.             @Override
  637.             public UUIDExceptionWrapper convertValue(String value, Type type) {
  638.                 return storyFailureIfItHappened;
  639.             }
  640.         }

  641.         @Override
  642.         public String asString(Keywords keywords) {
  643.             return method.getName() + ";" + meta.asString(keywords);
  644.         }
  645.     }

  646.     class UponAnyStep extends DelegatingStep {

  647.         UponAnyStep(Step step) {
  648.             super(step);
  649.         }

  650.         @Override
  651.         public StepResult doNotPerform(StoryReporter storyReporter, UUIDExceptionWrapper storyFailureIfItHappened) {
  652.             return perform(storyReporter, storyFailureIfItHappened);
  653.         }
  654.     }

  655.     class UponSuccessStep extends DelegatingStep {

  656.         UponSuccessStep(Step step) {
  657.             super(step);
  658.         }

  659.         @Override
  660.         public StepResult doNotPerform(StoryReporter storyReporter, UUIDExceptionWrapper storyFailureIfItHappened) {
  661.             return skipped();
  662.         }
  663.     }

  664.     class UponFailureStep extends DelegatingStep {

  665.         UponFailureStep(Step step) {
  666.             super(step);
  667.         }

  668.         @Override
  669.         public StepResult doNotPerform(StoryReporter storyReporter, UUIDExceptionWrapper storyFailureIfItHappened) {
  670.             return super.perform(storyReporter, storyFailureIfItHappened);
  671.         }

  672.         @Override
  673.         public StepResult perform(StoryReporter storyReporter, UUIDExceptionWrapper storyFailureIfItHappened) {
  674.             return skipped();
  675.         }
  676.     }

  677.     public class ConditionalStep extends AbstractStep {

  678.         private final StepConditionMatcher stepConditionMatcher;
  679.         private final Map<Method, ParametrisedStep> parametrisedSteps;

  680.         public ConditionalStep(StepConditionMatcher stepConditionMatcher,
  681.                 Map<Method, ParametrisedStep> parametrisedSteps) {
  682.             this.stepConditionMatcher = stepConditionMatcher;
  683.             this.parametrisedSteps = parametrisedSteps;
  684.         }

  685.         @Override
  686.         public StepResult perform(StoryReporter storyReporter, UUIDExceptionWrapper storyFailureIfItHappened) {
  687.             return performConditionalStep(storyReporter, step -> step.perform(storyReporter, storyFailureIfItHappened));
  688.         }

  689.         @Override
  690.         public StepResult doNotPerform(StoryReporter storyReporter, UUIDExceptionWrapper storyFailureIfItHappened) {
  691.             return performConditionalStep(storyReporter,
  692.                     step -> step.doNotPerform(storyReporter, storyFailureIfItHappened));
  693.         }

  694.         private StepResult performConditionalStep(StoryReporter storyReporter,
  695.                 Function<ParametrisedStep, StepResult> invoker) {

  696.             try {
  697.                 Map<Method, Conditional> unmatchedSteps = new HashMap<>();
  698.                 Map<Method, Conditional> matchedSteps = new HashMap<>();

  699.                 for (Method method : parametrisedSteps.keySet()) {
  700.                     Conditional conditional = Optional.ofNullable(method.getAnnotation(Conditional.class))
  701.                             .orElseGet(() -> method.getDeclaringClass().getAnnotation(Conditional.class));

  702.                     Map<Method, Conditional> target = stepConditionMatcher.matches(conditional.condition(),
  703.                             conditional.value()) ? matchedSteps : unmatchedSteps;
  704.                     target.put(method, conditional);

  705.                 }

  706.                 if (matchedSteps.isEmpty()) {
  707.                     String message = getStepName() + System.lineSeparator()
  708.                             + "None of the following steps were matched any condition:" + System.lineSeparator()
  709.                             + formatSteps(unmatchedSteps);
  710.                     return reportPending(storyReporter, message);
  711.                 } else if (matchedSteps.size() > 1) {
  712.                     String message = getStepName() + System.lineSeparator()
  713.                             + "More than one conditional step matched the condition:" + System.lineSeparator()
  714.                             + formatSteps(matchedSteps);
  715.                     return reportPending(storyReporter, message);
  716.                 }

  717.                 return invoker.apply(parametrisedSteps.get(matchedSteps.keySet().iterator().next()));
  718.             } catch (StepConditionMatchException e) {
  719.                 return reportPending(storyReporter, getStepName() + System.lineSeparator() + e.getMessage());
  720.             }
  721.         }

  722.         private StepResult reportPending(StoryReporter storyReporter, String message) {
  723.             Step pendingStep = createPendingStep(message, null);
  724.             return pendingStep.perform(storyReporter, null);
  725.         }

  726.         private String formatSteps(Map<Method, Conditional> matchedSteps) {
  727.             return matchedSteps.entrySet().stream()
  728.                     .map(e -> formatStep(e.getKey(), e.getValue()))
  729.                     .collect(Collectors.joining(System.lineSeparator()));
  730.         }

  731.         private String formatStep(Method method, Conditional condition) {
  732.             return method.getDeclaringClass() + "." + method.getName() + " (condition: " + condition.condition() + " "
  733.                     + condition.value() + ")";
  734.         }

  735.         private String getStepName() {
  736.             return parametrisedSteps.values().iterator().next().asString(null);
  737.         }
  738.     }

  739.     public class ParametrisedStep extends ReportingAbstractStep {
  740.         private String parametrisedStep;
  741.         private final Method method;
  742.         private final String stepWithoutStartingWord;
  743.         private final Map<String, String> namedParameters;
  744.         private final List<Step> composedSteps;

  745.         public ParametrisedStep(String stepAsString, Method method, String stepWithoutStartingWord,
  746.                 Map<String, String> namedParameters, List<Step> composedSteps) {
  747.             super(StepExecutionType.EXECUTABLE, stepAsString);
  748.             this.method = method;
  749.             this.stepWithoutStartingWord = stepWithoutStartingWord;
  750.             this.namedParameters = namedParameters;
  751.             this.composedSteps = composedSteps;
  752.         }

  753.         @Override
  754.         public List<Step> getComposedSteps() {
  755.             return composedSteps;
  756.         }

  757.         @Override
  758.         public StepResult perform(UUIDExceptionWrapper storyFailure) {
  759.             String stepAsString = getStepAsString();
  760.             Timer timer = new Timer().start();
  761.             try {
  762.                 Object[] convertedParameters = parametriseStep();
  763.                 stepMonitor.beforePerforming(parametrisedStep, dryRun, method);
  764.                 if (!dryRun && method != null) {
  765.                     Object outputObject = method.invoke(stepsInstance(), convertedParameters);
  766.                     storeOutput(outputObject, method);
  767.                 }
  768.                 return successful(stepAsString).withParameterValues(parametrisedStep)
  769.                         .setTimings(timer.stop());
  770.             } catch (ParameterNotFound e) {
  771.                 // step parametrisation failed, return pending StepResult
  772.                 return pending(new PendingStep(stepAsString, null)).withParameterValues(parametrisedStep);
  773.             } catch (InvocationTargetException e) {
  774.                 if (e.getCause() instanceof RestartingScenarioFailure) {
  775.                     throw (RestartingScenarioFailure) e.getCause();
  776.                 }
  777.                 if (e.getCause() instanceof IgnoringStepsFailure) {
  778.                     throw (IgnoringStepsFailure) e.getCause();
  779.                 }
  780.                 Throwable failureCause = e.getCause();
  781.                 if (failureCause instanceof UUIDExceptionWrapper) {
  782.                     failureCause = failureCause.getCause();
  783.                 }
  784.                 return failed(stepAsString, new UUIDExceptionWrapper(stepAsString, failureCause)).withParameterValues(
  785.                         parametrisedStep).setTimings(timer.stop());
  786.             } catch (Throwable t) {
  787.                 return failed(stepAsString, new UUIDExceptionWrapper(stepAsString, t)).withParameterValues(
  788.                         parametrisedStep).setTimings(timer.stop());
  789.             } finally {
  790.                 stepMonitor.afterPerforming(parametrisedStep, dryRun, method);
  791.             }
  792.         }

  793.         @Override
  794.         public StepResult doNotPerform(StoryReporter storyReporter, UUIDExceptionWrapper storyFailureIfItHappened) {
  795.             storyReporter.beforeStep(
  796.                     new org.jbehave.core.model.Step(StepExecutionType.NOT_PERFORMED, getStepAsString()));
  797.             try {
  798.                 parametriseStep();
  799.             } catch (Throwable t) {
  800.                 // step parametrisation failed, but still return
  801.                 // notPerformed StepResult
  802.             }
  803.             return notPerformed(getStepAsString()).withParameterValues(parametrisedStep);
  804.         }

  805.         @Override
  806.         public String asString(Keywords keywords) {
  807.             if (parametrisedStep == null) {
  808.                 parametriseStep();
  809.             }
  810.             return parametrisedStep;
  811.         }

  812.         private Object[] parametriseStep() {
  813.             Matcher matcher = stepMatcher.matcher(stepWithoutStartingWord);
  814.             matcher.find();
  815.             ParameterName[] names = parameterNames(method);
  816.             Type[] types = parameterTypes(method, names);
  817.             String[] parameterValues = parameterValuesForStep(matcher, namedParameters, types, names, true);
  818.             Object[] convertedParameters;
  819.             if (method == null) {
  820.                 convertedParameters = parameterValues;
  821.             } else {
  822.                 convertedParameters = convertParameterValues(parameterValues, types, names);
  823.             }
  824.             addNamedParametersToExamplesTables(convertedParameters);
  825.             parametrisedStep = parametrisedStep(getStepAsString(), namedParameters, types, parameterValues);
  826.             return convertedParameters;
  827.         }

  828.         private Object[] convertParameterValues(String[] parameterValues, Type[] types, ParameterName[] names) {
  829.             final Object[] parameters = new Object[parameterValues.length];
  830.             for (int i = 0; i < parameterValues.length; i++) {
  831.                 String parameterValue = parameterValues[i];
  832.                 if (names[i].fromContext) {
  833.                     parameters[i] = stepsContext.get(parameterValue);
  834.                 } else {
  835.                     String expressionEvaluationResult = parameterValue != null
  836.                             ? String.valueOf(expressionResolver.resolveExpressions(dryRun, parameterValue)) : null;
  837.                     parameters[i] = parameterConverters.convert(expressionEvaluationResult, types[i]);
  838.                 }
  839.             }
  840.             return parameters;
  841.         }

  842.         private void addNamedParametersToExamplesTables(Object[] convertedParameters) {
  843.             Stream.of(convertedParameters)
  844.                     .filter(ExamplesTable.class::isInstance)
  845.                     .map(ExamplesTable.class::cast)
  846.                     .forEach(examplesTable -> examplesTable.withNamedParameters(namedParameters));
  847.         }
  848.     }

  849.     public static class PendingStep extends ReportingAbstractStep {
  850.         private final String previousNonAndStep;
  851.         private Method method;
  852.         private String pendingMethod;

  853.         public PendingStep(String stepAsString, String previousNonAndStep) {
  854.             super(StepExecutionType.PENDING, stepAsString);
  855.             this.previousNonAndStep = previousNonAndStep;
  856.         }

  857.         @Override
  858.         protected StepResult perform(UUIDExceptionWrapper storyFailure) {
  859.             return pending(this);
  860.         }

  861.         @Override
  862.         public StepResult doNotPerform(StoryReporter storyReporter, UUIDExceptionWrapper storyFailureIfItHappened) {
  863.             return perform(storyReporter, storyFailureIfItHappened);
  864.         }

  865.         public String stepAsString() {
  866.             return getStepAsString();
  867.         }

  868.         public String previousNonAndStepAsString() {
  869.             return previousNonAndStep;
  870.         }

  871.         public void annotatedOn(Method method) {
  872.             this.method = method;
  873.         }

  874.         public boolean annotated() {
  875.             return method != null;
  876.         }

  877.         public String getPendingMethod() {
  878.             return pendingMethod;
  879.         }

  880.         public void setPendingMethod(String pendingMethod) {
  881.             this.pendingMethod = pendingMethod;
  882.         }
  883.     }

  884.     public static class IgnorableStep extends ReportingAbstractStep {

  885.         public IgnorableStep(String stepAsString) {
  886.             super(StepExecutionType.IGNORABLE, stepAsString);
  887.         }

  888.         @Override
  889.         protected StepResult perform(UUIDExceptionWrapper storyFailure) {
  890.             return ignorable(getStepAsString());
  891.         }

  892.         @Override
  893.         public StepResult doNotPerform(StoryReporter storyReporter, UUIDExceptionWrapper storyFailureIfItHappened) {
  894.             return perform(storyReporter, storyFailureIfItHappened);
  895.         }
  896.     }

  897.     public static class Comment extends ReportingAbstractStep {

  898.         public Comment(String stepAsString) {
  899.             super(StepExecutionType.COMMENT, stepAsString);
  900.         }

  901.         @Override
  902.         protected StepResult perform(UUIDExceptionWrapper storyFailure) {
  903.             return comment(getStepAsString());
  904.         }

  905.         @Override
  906.         public StepResult doNotPerform(StoryReporter storyReporter, UUIDExceptionWrapper storyFailureIfItHappened) {
  907.             return perform(storyReporter, storyFailureIfItHappened);
  908.         }
  909.     }

  910.     public static enum StepExecutionType {
  911.         EXECUTABLE,
  912.         PENDING,
  913.         IGNORABLE,
  914.         COMMENT,
  915.         NOT_PERFORMED;
  916.     }

  917.     private class MethodInvoker {
  918.         private final Method method;
  919.         private final ParameterConverters parameterConverters;
  920.         private final Paranamer paranamer;
  921.         private final Meta meta;
  922.         private final Type[] parameterTypes;
  923.        
  924.         public MethodInvoker(Method method, ParameterConverters parameterConverters, Paranamer paranamer, Meta meta) {
  925.             this.method = method;
  926.             this.parameterConverters = parameterConverters;
  927.             this.paranamer = paranamer;
  928.             this.meta = meta;
  929.             this.parameterTypes = method.getGenericParameterTypes();
  930.         }

  931.         public Object invoke() throws InvocationTargetException, IllegalAccessException {
  932.             return method.invoke(stepsInstance(), parameterValuesFrom(meta));
  933.         }

  934.         private Parameter[] methodParameters() {
  935.             Parameter[] parameters = new Parameter[parameterTypes.length];
  936.             String[] annotatedNames = annotatedParameterNames(method);
  937.             String[] paranamerNames = paranamer.lookupParameterNames(method, false);

  938.             for (int position = 0; position < parameterTypes.length; position++) {
  939.                 String name = parameterNameFor(position, annotatedNames, paranamerNames);
  940.                 parameters[position] = new Parameter(position, parameterTypes[position], name);
  941.             }

  942.             return parameters;
  943.         }

  944.         private String parameterNameFor(int position, String[] annotatedNames, String[] paranamerNames) {
  945.             String annotatedName = nameByPosition(annotatedNames, position);
  946.             String paranamerName = nameByPosition(paranamerNames, position);
  947.             if (annotatedName != null) {
  948.                 return annotatedName;
  949.             } else if (paranamerName != null) {
  950.                 return paranamerName;
  951.             }
  952.             return null;
  953.         }

  954.         private String nameByPosition(String[] names, int position) {
  955.             return position < names.length ? names[position] : null;
  956.         }

  957.         private Object[] parameterValuesFrom(Meta meta) {
  958.             Object[] values = new Object[parameterTypes.length];
  959.             for (Parameter parameter : methodParameters()) {
  960.                 values[parameter.position] = parameterConverters.convert(parameter.valueFrom(meta), parameter.type);
  961.             }
  962.             return values;
  963.         }

  964.         private class Parameter {
  965.             private final int position;
  966.             private final Type type;
  967.             private final String name;

  968.             public Parameter(int position, Type type, String name) {
  969.                 this.position = position;
  970.                 this.type = type;
  971.                 this.name = name;
  972.             }

  973.             public String valueFrom(Meta meta) {
  974.                 if (name == null) {
  975.                     return null;
  976.                 }
  977.                 return meta.getProperty(name);
  978.             }
  979.         }
  980.     }

  981.     private static class ParameterName {
  982.         private String name;
  983.         private boolean annotated;
  984.         private boolean fromContext;

  985.         private ParameterName(String name, boolean annotated, boolean fromContext) {
  986.             this.name = name;
  987.             this.annotated = annotated;
  988.             this.fromContext = fromContext;
  989.         }
  990.     }
  991. }