StepCreator.java
- package org.jbehave.core.steps;
- import static java.util.Arrays.asList;
- import static org.jbehave.core.steps.AbstractStepResult.comment;
- import static org.jbehave.core.steps.AbstractStepResult.failed;
- import static org.jbehave.core.steps.AbstractStepResult.ignorable;
- import static org.jbehave.core.steps.AbstractStepResult.notPerformed;
- import static org.jbehave.core.steps.AbstractStepResult.pending;
- import static org.jbehave.core.steps.AbstractStepResult.skipped;
- import static org.jbehave.core.steps.AbstractStepResult.successful;
- import static org.jbehave.core.steps.ParameterConverters.ExamplesTableParametersConverter.isExamplesTableParameters;
- import java.lang.annotation.Annotation;
- import java.lang.reflect.AccessibleObject;
- import java.lang.reflect.InvocationTargetException;
- import java.lang.reflect.Method;
- import java.lang.reflect.Type;
- import java.util.ArrayList;
- import java.util.Collections;
- import java.util.HashMap;
- import java.util.List;
- import java.util.Map;
- import java.util.Map.Entry;
- import java.util.Optional;
- import java.util.function.Function;
- import java.util.regex.Matcher;
- import java.util.regex.Pattern;
- import java.util.stream.Collectors;
- import java.util.stream.Stream;
- import com.thoughtworks.paranamer.NullParanamer;
- import com.thoughtworks.paranamer.Paranamer;
- import org.apache.commons.lang3.builder.ToStringBuilder;
- import org.apache.commons.lang3.builder.ToStringStyle;
- import org.jbehave.core.annotations.AfterScenario.Outcome;
- import org.jbehave.core.annotations.Conditional;
- import org.jbehave.core.annotations.FromContext;
- import org.jbehave.core.annotations.Named;
- import org.jbehave.core.annotations.ToContext;
- import org.jbehave.core.condition.StepConditionMatchException;
- import org.jbehave.core.condition.StepConditionMatcher;
- import org.jbehave.core.configuration.Keywords;
- import org.jbehave.core.expressions.ExpressionResolver;
- import org.jbehave.core.failures.BeforeOrAfterFailed;
- import org.jbehave.core.failures.IgnoringStepsFailure;
- import org.jbehave.core.failures.RestartingScenarioFailure;
- import org.jbehave.core.failures.UUIDExceptionWrapper;
- import org.jbehave.core.model.ExamplesTable;
- import org.jbehave.core.model.Meta;
- import org.jbehave.core.model.Verbatim;
- import org.jbehave.core.parsers.StepMatcher;
- import org.jbehave.core.reporters.StoryReporter;
- import org.jbehave.core.steps.ParameterConverters.FromStringParameterConverter;
- import org.jbehave.core.steps.context.StepsContext;
- @SuppressWarnings("checkstyle:AvoidEscapedUnicodeCharacters")
- public class StepCreator {
- public static final String PARAMETER_TABLE_START = "\uff3b";
- public static final String PARAMETER_TABLE_END = "\uff3d";
- public static final String PARAMETER_VERBATIM_START = "\u301a";
- public static final String PARAMETER_VERBATIM_END = "\u301b";
- public static final String PARAMETER_VALUE_START = "\uFF5F";
- public static final String PARAMETER_VALUE_END = "\uFF60";
- public static final String PARAMETER_VALUE_NEWLINE = "\u2424";
- public static final UUIDExceptionWrapper NO_FAILURE = new UUIDExceptionWrapper("no failure");
- private static final String NEWLINE = "\n";
- private static final String SPACE = " ";
- private static final String NONE = "";
- private final Class<?> stepsType;
- private final InjectableStepsFactory stepsFactory;
- private final ParameterConverters parameterConverters;
- private final ExpressionResolver expressionResolver;
- private final ParameterControls parameterControls;
- private final Pattern delimitedNamePattern;
- private final StepMatcher stepMatcher;
- private final StepsContext stepsContext;
- private final boolean dryRun;
- private StepMonitor stepMonitor;
- private Paranamer paranamer = new NullParanamer();
- public StepCreator(Class<?> stepsType, InjectableStepsFactory stepsFactory, StepsContext stepsContext,
- ParameterConverters parameterConverters, ExpressionResolver expressionResolver,
- ParameterControls parameterControls, StepMatcher stepMatcher, StepMonitor stepMonitor, boolean dryRun) {
- this.stepsType = stepsType;
- this.stepsFactory = stepsFactory;
- this.stepsContext = stepsContext;
- this.parameterConverters = parameterConverters;
- this.expressionResolver = expressionResolver;
- this.parameterControls = parameterControls;
- this.stepMatcher = stepMatcher;
- this.stepMonitor = stepMonitor;
- this.delimitedNamePattern = Pattern.compile(parameterControls.nameDelimiterLeft() + "([\\w\\-\\h.]+?)"
- + parameterControls.nameDelimiterRight(), Pattern.DOTALL);
- this.dryRun = dryRun;
- }
- public void useStepMonitor(StepMonitor stepMonitor) {
- this.stepMonitor = stepMonitor;
- }
- public void useParanamer(Paranamer paranamer) {
- this.paranamer = paranamer;
- }
- public Object stepsInstance() {
- return stepsFactory.createInstanceOfType(stepsType);
- }
- public Step createBeforeOrAfterStep(Method method, Meta meta) {
- return new BeforeOrAfterStep(method, meta);
- }
- public Step createAfterStepUponOutcome(final Method method, final Outcome outcome, Meta storyAndScenarioMeta) {
- Step beforeOrAfterStep = createBeforeOrAfterStep(method, storyAndScenarioMeta);
- return wrapStepUponOutcome(outcome, beforeOrAfterStep);
- }
- public Map<String, String> matchedParameters(final Method method, final String stepWithoutStartingWord,
- final Map<String, String> namedParameters) {
- Map<String, String> matchedParameters = new HashMap<>();
- Matcher matcher = stepMatcher.matcher(stepWithoutStartingWord);
- if (matcher.find()) {
- // we've found a match, populate map
- ParameterName[] parameterNames = parameterNames(method);
- Type[] types = parameterTypes(method, parameterNames);
- String[] values = parameterValuesForStep(matcher, namedParameters, types, parameterNames, false);
- for (int i = 0; i < parameterNames.length; i++) {
- String name = parameterNames[i].name;
- if (name == null) {
- name = stepMatcher.parameterNames()[i];
- }
- matchedParameters.put(name, values[i]);
- }
- }
- // else return empty map
- return matchedParameters;
- }
- /**
- * Returns the {@link ParameterName} representations for the method,
- * providing an abstraction that supports both annotated and non-annotated
- * parameters.
- *
- * @param method the Method
- * @return The array of {@link ParameterName}s
- */
- private ParameterName[] parameterNames(Method method) {
- ParameterName[] parameterNames;
- if (method != null) {
- String[] annotatedNames = annotatedParameterNames(method);
- String[] paranamerNames = paranamerParameterNames(method);
- String[] contextNames = contextParameterNames(method);
- parameterNames = new ParameterName[annotatedNames.length];
- for (int i = 0; i < annotatedNames.length; i++) {
- parameterNames[i] = parameterName(annotatedNames, paranamerNames, contextNames, i);
- }
- } else {
- String[] stepMatcherParameterNames = stepMatcher.parameterNames();
- parameterNames = new ParameterName[stepMatcherParameterNames.length];
- for (int i = 0; i < stepMatcherParameterNames.length; i++) {
- parameterNames[i] = new ParameterName(stepMatcherParameterNames[i], false, false);
- }
- }
- return parameterNames;
- }
- private ParameterName parameterName(String[] annotatedNames, String[] paranamerNames, String[] contextNames,
- int i) {
- boolean annotated = true;
- boolean fromContext = false;
- String name = contextNames[i];
- if (name != null) {
- fromContext = true;
- } else {
- name = annotatedNames[i];
- if (name == null) {
- name = (paranamerNames.length > i ? paranamerNames[i] : null);
- annotated = false;
- }
- }
- return new ParameterName(name, annotated, fromContext);
- }
- /**
- * Extract parameter names using {@link Named}-annotated parameters
- *
- * @param method the Method with {@link Named}-annotated parameters
- * @return An array of annotated parameter names, which <b>may</b> include
- * <code>null</code> values for parameters that are not annotated
- */
- private String[] annotatedParameterNames(Method method) {
- Annotation[][] parameterAnnotations = method.getParameterAnnotations();
- String[] names = new String[parameterAnnotations.length];
- for (int i = 0; i < parameterAnnotations.length; i++) {
- for (Annotation annotation : parameterAnnotations[i]) {
- names[i] = annotationName(annotation);
- }
- }
- return names;
- }
-
- /**
- * Extract parameter names using {@link FromContext}-annotated parameters
- *
- * @param method the Method with {@link FromContext}-annotated parameters
- * @return An array of annotated parameter names, which <b>may</b> include
- * <code>null</code> values for parameters that are not annotated
- */
- private String[] contextParameterNames(Method method) {
- Annotation[][] parameterAnnotations = method.getParameterAnnotations();
- String[] names = new String[parameterAnnotations.length];
- for (int i = 0; i < parameterAnnotations.length; i++) {
- for (Annotation annotation : parameterAnnotations[i]) {
- names[i] = contextName(annotation);
- }
- }
- return names;
- }
- /**
- * Returns either the value of the annotation, either {@link Named} or
- * "javax.inject.Named".
- *
- * @param annotation the Annotation
- * @return The annotated value or <code>null</code> if no annotation is
- * found
- */
- private String annotationName(Annotation annotation) {
- if (annotation.annotationType().isAssignableFrom(Named.class)) {
- return ((Named) annotation).value();
- } else if ("javax.inject.Named".equals(annotation.annotationType().getName())) {
- return Jsr330Helper.getNamedValue(annotation);
- } else {
- return null;
- }
- }
- /**
- * Returns the value of the annotation {@link FromContext}.
- *
- * @param annotation the Annotation
- * @return The annotated value or <code>null</code> if no annotation is
- * found
- */
- private String contextName(Annotation annotation) {
- if (annotation.annotationType().isAssignableFrom(FromContext.class)) {
- return ((FromContext) annotation).value();
- } else {
- return null;
- }
- }
- /**
- * Extract parameter names using
- * {@link Paranamer#lookupParameterNames(AccessibleObject, boolean)}
- *
- * @param method the Method inspected by Paranamer
- * @return An array of parameter names looked up by Paranamer
- */
- private String[] paranamerParameterNames(Method method) {
- return paranamer.lookupParameterNames(method, false);
- }
- private Type[] parameterTypes(Method method, ParameterName[] parameterNames) {
- if (method != null) {
- return method.getGenericParameterTypes();
- }
- Type[] types = new Type[parameterNames.length];
- for (int i = 0; i < types.length; i++) {
- types[i] = String.class;
- }
- return types;
- }
- public Step createConditionalStep(final StepConditionMatcher stepConditionMatcher,
- final Map<Method, ParametrisedStep> parametrisedSteps) {
- return new ConditionalStep(stepConditionMatcher, parametrisedSteps);
- }
- public Step createParametrisedStep(final Method method, final String stepAsString,
- final String stepWithoutStartingWord, final Map<String, String> namedParameters,
- final List<Step> composedSteps) {
- return new ParametrisedStep(stepAsString, method, stepWithoutStartingWord, namedParameters, composedSteps);
- }
- public Step createParametrisedStepUponOutcome(final Method method, final String stepAsString,
- final String stepWithoutStartingWord, final Map<String, String> namedParameters,
- final List<Step> composedSteps, Outcome outcome) {
- Step parametrisedStep = createParametrisedStep(method, stepAsString, stepWithoutStartingWord, namedParameters,
- composedSteps);
- return wrapStepUponOutcome(outcome, parametrisedStep);
- }
- private Step wrapStepUponOutcome(Outcome outcome, Step step) {
- switch (outcome) {
- case ANY:
- return new UponAnyStep(step);
- case SUCCESS:
- return new UponSuccessStep(step);
- case FAILURE:
- return new UponFailureStep(step);
- default:
- return step;
- }
- }
- private String parametrisedStep(String stepAsString, Map<String, String> namedParameters, Type[] types,
- String[] parameterValues) {
- String parametrisedStep = stepAsString;
- // mark parameter values that are parsed
- boolean hasTable = hasTable(types);
- for (int position = 0; position < types.length; position++) {
- parametrisedStep = markParsedParameterValue(parametrisedStep, types[position], parameterValues[position],
- hasTable);
- }
- // mark parameter values that are named
- for (Entry<String, String> namedParameter : namedParameters.entrySet()) {
- parametrisedStep = parameterControls.replaceAllDelimitedNames(parametrisedStep, namedParameter.getKey(),
- markedValue(namedParameter.getValue()));
- }
- return parametrisedStep;
- }
- private boolean hasTable(Type[] types) {
- for (Type type : types) {
- if (isTable(type)) {
- return true;
- }
- }
- return false;
- }
- private String markParsedParameterValue(String stepText, Type type, String value, boolean hasTable) {
- if (value != null) {
- // only mark non-empty string as parameter (JBEHAVE-656)
- if (value.trim().length() != 0) {
- if (isTable(type)) {
- return stepText.replace(value, markedTable(value));
- }
- if (isVerbatim(type)) {
- return stepText.replace(value, markedVerbatim(value));
- }
- String markedValue = markedValue(value);
- // identify parameter values to mark as padded by spaces to avoid duplicated replacements of overlapping
- // values (JBEHAVE-837)
- String leftPad = SPACE;
- String rightPad = stepText.endsWith(value) ? NONE : SPACE;
- return stepText.replace(pad(value, leftPad, rightPad), pad(markedValue, leftPad, rightPad));
- }
- if (!hasTable) {
- return stepText.replace(NEWLINE, PARAMETER_VALUE_NEWLINE);
- }
- }
- return stepText;
- }
- private String markedTable(String value) {
- return pad(value, PARAMETER_TABLE_START, PARAMETER_TABLE_END);
- }
- private String markedVerbatim(String value) {
- return pad(value, PARAMETER_VERBATIM_START, PARAMETER_VERBATIM_END);
- }
- private String markedValue(String value) {
- return pad(value, PARAMETER_VALUE_START, PARAMETER_VALUE_END);
- }
- private String pad(String value, String left, String right) {
- return new StringBuilder().append(left).append(value).append(right).toString();
- }
- private boolean isTable(Type type) {
- return isExamplesTable(type) || isExamplesTableParameters(type);
- }
- private boolean isVerbatim(Type type) {
- return type instanceof Class && Verbatim.class.isAssignableFrom((Class<?>) type);
- }
- private boolean isExamplesTable(Type type) {
- return type instanceof Class && ExamplesTable.class.isAssignableFrom((Class<?>) type);
- }
- private String[] parameterValuesForStep(Matcher matcher, Map<String, String> namedParameters, Type[] types,
- ParameterName[] names, boolean overrideWithTableParameters) {
- final String[] parameters = new String[types.length];
- for (int position = 0; position < types.length; position++) {
- parameters[position] = parameterForPosition(matcher, position, names, namedParameters,
- overrideWithTableParameters);
- }
- return parameters;
- }
- private String parameterForPosition(Matcher matcher, int position, ParameterName[] names,
- Map<String, String> namedParameters, boolean overrideWithTableParameters) {
- int namePosition = parameterPosition(names, position);
- String parameter = null;
- if (namePosition != -1) {
- String name = names[position].name;
- boolean annotated = names[position].annotated;
- boolean fromContext = names[position].fromContext;
- List<String> delimitedNames = Collections.emptyList();
- if (isGroupName(name)) {
- parameter = matchedParameter(matcher, name);
- delimitedNames = delimitedNameFor(parameter);
- if (delimitedNames.isEmpty()) {
- monitorUsingNameForParameter(name, position, annotated);
- }
- }
- if (!delimitedNames.isEmpty()) {
- parameter = replaceAllDelimitedNames(delimitedNames, position, annotated, parameter, namedParameters);
- delimitedNames = delimitedNameFor(parameter);
- parameter = replaceAllDelimitedNames(delimitedNames, position, annotated, parameter, namedParameters);
- } else if (overrideWithTableParameters && namedParameters.containsKey(name)) {
- parameter = namedParameters.get(name);
- if (parameter != null) {
- monitorUsingTableNameForParameter(name, position, annotated);
- }
- }
-
- if (fromContext && parameter == null) {
- parameter = name;
- stepMonitor.usingStepsContextParameter(parameter);
- }
- }
- if (parameter == null) {
- // This allows parameters to be in a different order.
- position = position - numberOfPreviousFromContext(names, position);
- stepMonitor.usingNaturalOrderForParameter(position);
- parameter = matchedParameter(matcher, position);
- String previousParameterValue;
- do {
- previousParameterValue = parameter;
- for (String delimitedName : delimitedNameFor(parameter)) {
- parameter = replaceAllDelimitedNames(parameter, delimitedName, namedParameters);
- }
- } while (parameter != null && !previousParameterValue.equals(parameter));
- }
- stepMonitor.foundParameter(parameter, position);
- return parameter;
- }
- private String replaceAllDelimitedNames(List<String> delimitedNames, int position, boolean annotated,
- String parameter, Map<String, String> namedParameters) {
- String parameterWithDelimitedNames = parameter;
- for (String delimitedName : delimitedNames) {
- monitorUsingTableNameForParameter(delimitedName, position, annotated);
- parameterWithDelimitedNames = replaceAllDelimitedNames(parameterWithDelimitedNames, delimitedName,
- namedParameters);
- }
- return parameterWithDelimitedNames;
- }
- private String replaceAllDelimitedNames(String parameterWithDelimitedNames, String delimitedName,
- Map<String, String> namedParameters) {
- if (namedParameters.containsKey(delimitedName)) {
- return parameterControls.replaceAllDelimitedNames(parameterWithDelimitedNames, delimitedName,
- namedParameters.get(delimitedName));
- }
- return parameterWithDelimitedNames;
- }
- private int numberOfPreviousFromContext(ParameterName[] names, int currentPosition) {
- int number = 0;
- for (int i = currentPosition - 1; i >= 0; i--) {
- if (names[i].fromContext) {
- number++;
- }
- }
-
- return number;
- }
- private void monitorUsingTableNameForParameter(String name, int position, boolean usingAnnotationNames) {
- if (usingAnnotationNames) {
- stepMonitor.usingTableAnnotatedNameForParameter(name, position);
- } else {
- stepMonitor.usingTableParameterNameForParameter(name, position);
- }
- }
- private void monitorUsingNameForParameter(String name, int position, boolean usingAnnotationNames) {
- if (usingAnnotationNames) {
- stepMonitor.usingAnnotatedNameForParameter(name, position);
- } else {
- stepMonitor.usingParameterNameForParameter(name, position);
- }
- }
- private List<String> delimitedNameFor(String parameter) {
- List<String> delimitedNames = new ArrayList<>();
- if (parameterControls.delimiterNamedParameters()) {
- Matcher matcher = delimitedNamePattern.matcher(parameter);
- while (matcher.find()) {
- delimitedNames.add(matcher.group(1));
- }
- }
- return delimitedNames;
- }
- String matchedParameter(Matcher matcher, String name) {
- String[] parameterNames = stepMatcher.parameterNames();
- for (int i = 0; i < parameterNames.length; i++) {
- String parameterName = parameterNames[i];
- if (name.equals(parameterName)) {
- return matchedParameter(matcher, i);
- }
- }
- throw new ParameterNotFound(name, parameterNames);
- }
- private String matchedParameter(Matcher matcher, int position) {
- String[] parameterNames = stepMatcher.parameterNames();
- int matchedPosition = position + 1;
- if (matchedPosition <= parameterNames.length) {
- return matcher.group(matchedPosition);
- }
- throw new ParameterNotFound(position, parameterNames);
- }
- private int parameterPosition(ParameterName[] names, int position) {
- if (names.length == 0) {
- return -1;
- }
- String positionName = names[position].name;
- for (int i = 0; i < names.length; i++) {
- String name = names[i].name;
- if (name != null && name.equals(positionName)) {
- return i;
- }
- }
- return -1;
- }
- private boolean isGroupName(String name) {
- String[] groupNames = stepMatcher.parameterNames();
- for (String groupName : groupNames) {
- if (name.equals(groupName)) {
- return true;
- }
- }
- return false;
- }
- public static Step createPendingStep(final String stepAsString, String previousNonAndStep) {
- return new PendingStep(stepAsString, previousNonAndStep);
- }
- public static Step createIgnorableStep(final String stepAsString) {
- return new IgnorableStep(stepAsString);
- }
- public static Step createComment(final String stepAsString) {
- return new Comment(stepAsString);
- }
- private void storeOutput(Object object, Method method) {
- ToContext annotation = method.getAnnotation(ToContext.class);
- if (annotation != null) {
- stepsContext.put(annotation.value(), object, annotation.retentionLevel());
- }
- }
- /**
- * This is a different class, because the @Inject jar may not be in the
- * classpath.
- */
- public static class Jsr330Helper {
- private static String getNamedValue(Annotation annotation) {
- return ((javax.inject.Named) annotation).value();
- }
- }
- @SuppressWarnings("serial")
- public static class ParameterNotFound extends RuntimeException {
- public ParameterNotFound(String name, String[] parameters) {
- super("Parameter not found for name '" + name + "' amongst '" + asList(parameters) + "'");
- }
- public ParameterNotFound(int position, String[] parameters) {
- super("Parameter not found for position '" + position + "' amongst '" + asList(parameters) + "'");
- }
- }
- public abstract static class AbstractStep implements Step {
- @Override
- public String asString(Keywords keywords) {
- return toString();
- }
- @Override
- public String toString() {
- return ToStringBuilder.reflectionToString(this, ToStringStyle.SIMPLE_STYLE);
- }
- @Override
- public List<Step> getComposedSteps() {
- return Collections.emptyList();
- }
- }
- public abstract static class ReportingAbstractStep extends AbstractStep {
- private final StepExecutionType stepExecutionType;
- private final String stepAsString;
- public ReportingAbstractStep(StepExecutionType stepExecutionType, String stepAsString) {
- this.stepExecutionType = stepExecutionType;
- this.stepAsString = stepAsString;
- }
- @Override
- public final StepResult perform(StoryReporter storyReporter, UUIDExceptionWrapper storyFailure) {
- storyReporter.beforeStep(new org.jbehave.core.model.Step(stepExecutionType, getStepAsString()));
- return perform(storyFailure);
- }
- protected abstract StepResult perform(UUIDExceptionWrapper storyFailure);
- @Override
- public String asString(Keywords keywords) {
- return stepAsString;
- }
- protected String getStepAsString() {
- return stepAsString;
- }
- }
- static class DelegatingStep extends AbstractStep {
- private final Step step;
- DelegatingStep(Step step) {
- this.step = step;
- }
- @Override
- public StepResult perform(StoryReporter storyReporter, UUIDExceptionWrapper storyFailureIfItHappened) {
- return step.perform(storyReporter, storyFailureIfItHappened);
- }
- @Override
- public StepResult doNotPerform(StoryReporter storyReporter, UUIDExceptionWrapper storyFailureIfItHappened) {
- return step.doNotPerform(storyReporter, storyFailureIfItHappened);
- }
- @Override
- public String asString(Keywords keywords) {
- return step.asString(keywords);
- }
- @Override
- public List<Step> getComposedSteps() {
- return step.getComposedSteps();
- }
- }
- private class BeforeOrAfterStep extends ReportingAbstractStep {
- private final Method method;
- private final Meta meta;
- public BeforeOrAfterStep(Method method, Meta meta) {
- super(StepExecutionType.EXECUTABLE, method.getName());
- this.method = method;
- this.meta = meta;
- }
- @Override
- public StepResult perform(UUIDExceptionWrapper storyFailureIfItHappened) {
- ParameterConverters paramConvertersWithExceptionInjector = paramConvertersWithExceptionInjector(
- storyFailureIfItHappened);
- MethodInvoker methodInvoker = new MethodInvoker(method, paramConvertersWithExceptionInjector, paranamer,
- meta);
- Timer timer = new Timer().start();
- try {
- Object outputObject = methodInvoker.invoke();
- storeOutput(outputObject, method);
- return successful(method).setTimings(timer.stop());
- } catch (InvocationTargetException e) {
- return failed(method, new UUIDExceptionWrapper(new BeforeOrAfterFailed(method, e.getCause())))
- .setTimings(timer.stop());
- } catch (Throwable t) {
- return failed(method, new UUIDExceptionWrapper(new BeforeOrAfterFailed(method, t)))
- .setTimings(timer.stop());
- }
- }
- private ParameterConverters paramConvertersWithExceptionInjector(
- UUIDExceptionWrapper storyFailureIfItHappened) {
- return parameterConverters.newInstanceAdding(new UUIDExceptionWrapperInjector(storyFailureIfItHappened));
- }
- @Override
- public StepResult doNotPerform(StoryReporter storyReporter, UUIDExceptionWrapper storyFailureIfItHappened) {
- return perform(storyReporter, storyFailureIfItHappened);
- }
- @SuppressWarnings("checkstyle:AbbreviationAsWordInName")
- private class UUIDExceptionWrapperInjector extends FromStringParameterConverter<UUIDExceptionWrapper> {
- private final UUIDExceptionWrapper storyFailureIfItHappened;
- public UUIDExceptionWrapperInjector(UUIDExceptionWrapper storyFailureIfItHappened) {
- this.storyFailureIfItHappened = storyFailureIfItHappened;
- }
- @Override
- public boolean canConvertTo(Type type) {
- return UUIDExceptionWrapper.class == type;
- }
- @Override
- public UUIDExceptionWrapper convertValue(String value, Type type) {
- return storyFailureIfItHappened;
- }
- }
- @Override
- public String asString(Keywords keywords) {
- return method.getName() + ";" + meta.asString(keywords);
- }
- }
- class UponAnyStep extends DelegatingStep {
- UponAnyStep(Step step) {
- super(step);
- }
- @Override
- public StepResult doNotPerform(StoryReporter storyReporter, UUIDExceptionWrapper storyFailureIfItHappened) {
- return perform(storyReporter, storyFailureIfItHappened);
- }
- }
- class UponSuccessStep extends DelegatingStep {
- UponSuccessStep(Step step) {
- super(step);
- }
- @Override
- public StepResult doNotPerform(StoryReporter storyReporter, UUIDExceptionWrapper storyFailureIfItHappened) {
- return skipped();
- }
- }
- class UponFailureStep extends DelegatingStep {
- UponFailureStep(Step step) {
- super(step);
- }
- @Override
- public StepResult doNotPerform(StoryReporter storyReporter, UUIDExceptionWrapper storyFailureIfItHappened) {
- return super.perform(storyReporter, storyFailureIfItHappened);
- }
- @Override
- public StepResult perform(StoryReporter storyReporter, UUIDExceptionWrapper storyFailureIfItHappened) {
- return skipped();
- }
- }
- public class ConditionalStep extends AbstractStep {
- private final StepConditionMatcher stepConditionMatcher;
- private final Map<Method, ParametrisedStep> parametrisedSteps;
- public ConditionalStep(StepConditionMatcher stepConditionMatcher,
- Map<Method, ParametrisedStep> parametrisedSteps) {
- this.stepConditionMatcher = stepConditionMatcher;
- this.parametrisedSteps = parametrisedSteps;
- }
- @Override
- public StepResult perform(StoryReporter storyReporter, UUIDExceptionWrapper storyFailureIfItHappened) {
- return performConditionalStep(storyReporter, step -> step.perform(storyReporter, storyFailureIfItHappened));
- }
- @Override
- public StepResult doNotPerform(StoryReporter storyReporter, UUIDExceptionWrapper storyFailureIfItHappened) {
- return performConditionalStep(storyReporter,
- step -> step.doNotPerform(storyReporter, storyFailureIfItHappened));
- }
- private StepResult performConditionalStep(StoryReporter storyReporter,
- Function<ParametrisedStep, StepResult> invoker) {
- try {
- Map<Method, Conditional> unmatchedSteps = new HashMap<>();
- Map<Method, Conditional> matchedSteps = new HashMap<>();
- for (Method method : parametrisedSteps.keySet()) {
- Conditional conditional = Optional.ofNullable(method.getAnnotation(Conditional.class))
- .orElseGet(() -> method.getDeclaringClass().getAnnotation(Conditional.class));
- Map<Method, Conditional> target = stepConditionMatcher.matches(conditional.condition(),
- conditional.value()) ? matchedSteps : unmatchedSteps;
- target.put(method, conditional);
- }
- if (matchedSteps.isEmpty()) {
- String message = getStepName() + System.lineSeparator()
- + "None of the following steps were matched any condition:" + System.lineSeparator()
- + formatSteps(unmatchedSteps);
- return reportPending(storyReporter, message);
- } else if (matchedSteps.size() > 1) {
- String message = getStepName() + System.lineSeparator()
- + "More than one conditional step matched the condition:" + System.lineSeparator()
- + formatSteps(matchedSteps);
- return reportPending(storyReporter, message);
- }
- return invoker.apply(parametrisedSteps.get(matchedSteps.keySet().iterator().next()));
- } catch (StepConditionMatchException e) {
- return reportPending(storyReporter, getStepName() + System.lineSeparator() + e.getMessage());
- }
- }
- private StepResult reportPending(StoryReporter storyReporter, String message) {
- Step pendingStep = createPendingStep(message, null);
- return pendingStep.perform(storyReporter, null);
- }
- private String formatSteps(Map<Method, Conditional> matchedSteps) {
- return matchedSteps.entrySet().stream()
- .map(e -> formatStep(e.getKey(), e.getValue()))
- .collect(Collectors.joining(System.lineSeparator()));
- }
- private String formatStep(Method method, Conditional condition) {
- return method.getDeclaringClass() + "." + method.getName() + " (condition: " + condition.condition() + " "
- + condition.value() + ")";
- }
- private String getStepName() {
- return parametrisedSteps.values().iterator().next().asString(null);
- }
- }
- public class ParametrisedStep extends ReportingAbstractStep {
- private String parametrisedStep;
- private final Method method;
- private final String stepWithoutStartingWord;
- private final Map<String, String> namedParameters;
- private final List<Step> composedSteps;
- public ParametrisedStep(String stepAsString, Method method, String stepWithoutStartingWord,
- Map<String, String> namedParameters, List<Step> composedSteps) {
- super(StepExecutionType.EXECUTABLE, stepAsString);
- this.method = method;
- this.stepWithoutStartingWord = stepWithoutStartingWord;
- this.namedParameters = namedParameters;
- this.composedSteps = composedSteps;
- }
- @Override
- public List<Step> getComposedSteps() {
- return composedSteps;
- }
- @Override
- public StepResult perform(UUIDExceptionWrapper storyFailure) {
- String stepAsString = getStepAsString();
- Timer timer = new Timer().start();
- try {
- Object[] convertedParameters = parametriseStep();
- stepMonitor.beforePerforming(parametrisedStep, dryRun, method);
- if (!dryRun && method != null) {
- Object outputObject = method.invoke(stepsInstance(), convertedParameters);
- storeOutput(outputObject, method);
- }
- return successful(stepAsString).withParameterValues(parametrisedStep)
- .setTimings(timer.stop());
- } catch (ParameterNotFound e) {
- // step parametrisation failed, return pending StepResult
- return pending(new PendingStep(stepAsString, null)).withParameterValues(parametrisedStep);
- } catch (InvocationTargetException e) {
- if (e.getCause() instanceof RestartingScenarioFailure) {
- throw (RestartingScenarioFailure) e.getCause();
- }
- if (e.getCause() instanceof IgnoringStepsFailure) {
- throw (IgnoringStepsFailure) e.getCause();
- }
- Throwable failureCause = e.getCause();
- if (failureCause instanceof UUIDExceptionWrapper) {
- failureCause = failureCause.getCause();
- }
- return failed(stepAsString, new UUIDExceptionWrapper(stepAsString, failureCause)).withParameterValues(
- parametrisedStep).setTimings(timer.stop());
- } catch (Throwable t) {
- return failed(stepAsString, new UUIDExceptionWrapper(stepAsString, t)).withParameterValues(
- parametrisedStep).setTimings(timer.stop());
- } finally {
- stepMonitor.afterPerforming(parametrisedStep, dryRun, method);
- }
- }
- @Override
- public StepResult doNotPerform(StoryReporter storyReporter, UUIDExceptionWrapper storyFailureIfItHappened) {
- storyReporter.beforeStep(
- new org.jbehave.core.model.Step(StepExecutionType.NOT_PERFORMED, getStepAsString()));
- try {
- parametriseStep();
- } catch (Throwable t) {
- // step parametrisation failed, but still return
- // notPerformed StepResult
- }
- return notPerformed(getStepAsString()).withParameterValues(parametrisedStep);
- }
- @Override
- public String asString(Keywords keywords) {
- if (parametrisedStep == null) {
- parametriseStep();
- }
- return parametrisedStep;
- }
- private Object[] parametriseStep() {
- Matcher matcher = stepMatcher.matcher(stepWithoutStartingWord);
- matcher.find();
- ParameterName[] names = parameterNames(method);
- Type[] types = parameterTypes(method, names);
- String[] parameterValues = parameterValuesForStep(matcher, namedParameters, types, names, true);
- Object[] convertedParameters;
- if (method == null) {
- convertedParameters = parameterValues;
- } else {
- convertedParameters = convertParameterValues(parameterValues, types, names);
- }
- addNamedParametersToExamplesTables(convertedParameters);
- parametrisedStep = parametrisedStep(getStepAsString(), namedParameters, types, parameterValues);
- return convertedParameters;
- }
- private Object[] convertParameterValues(String[] parameterValues, Type[] types, ParameterName[] names) {
- final Object[] parameters = new Object[parameterValues.length];
- for (int i = 0; i < parameterValues.length; i++) {
- String parameterValue = parameterValues[i];
- if (names[i].fromContext) {
- parameters[i] = stepsContext.get(parameterValue);
- } else {
- String expressionEvaluationResult = parameterValue != null
- ? String.valueOf(expressionResolver.resolveExpressions(dryRun, parameterValue)) : null;
- parameters[i] = parameterConverters.convert(expressionEvaluationResult, types[i]);
- }
- }
- return parameters;
- }
- private void addNamedParametersToExamplesTables(Object[] convertedParameters) {
- Stream.of(convertedParameters)
- .filter(ExamplesTable.class::isInstance)
- .map(ExamplesTable.class::cast)
- .forEach(examplesTable -> examplesTable.withNamedParameters(namedParameters));
- }
- }
- public static class PendingStep extends ReportingAbstractStep {
- private final String previousNonAndStep;
- private Method method;
- private String pendingMethod;
- public PendingStep(String stepAsString, String previousNonAndStep) {
- super(StepExecutionType.PENDING, stepAsString);
- this.previousNonAndStep = previousNonAndStep;
- }
- @Override
- protected StepResult perform(UUIDExceptionWrapper storyFailure) {
- return pending(this);
- }
- @Override
- public StepResult doNotPerform(StoryReporter storyReporter, UUIDExceptionWrapper storyFailureIfItHappened) {
- return perform(storyReporter, storyFailureIfItHappened);
- }
- public String stepAsString() {
- return getStepAsString();
- }
- public String previousNonAndStepAsString() {
- return previousNonAndStep;
- }
- public void annotatedOn(Method method) {
- this.method = method;
- }
- public boolean annotated() {
- return method != null;
- }
- public String getPendingMethod() {
- return pendingMethod;
- }
- public void setPendingMethod(String pendingMethod) {
- this.pendingMethod = pendingMethod;
- }
- }
- public static class IgnorableStep extends ReportingAbstractStep {
- public IgnorableStep(String stepAsString) {
- super(StepExecutionType.IGNORABLE, stepAsString);
- }
- @Override
- protected StepResult perform(UUIDExceptionWrapper storyFailure) {
- return ignorable(getStepAsString());
- }
- @Override
- public StepResult doNotPerform(StoryReporter storyReporter, UUIDExceptionWrapper storyFailureIfItHappened) {
- return perform(storyReporter, storyFailureIfItHappened);
- }
- }
- public static class Comment extends ReportingAbstractStep {
- public Comment(String stepAsString) {
- super(StepExecutionType.COMMENT, stepAsString);
- }
- @Override
- protected StepResult perform(UUIDExceptionWrapper storyFailure) {
- return comment(getStepAsString());
- }
- @Override
- public StepResult doNotPerform(StoryReporter storyReporter, UUIDExceptionWrapper storyFailureIfItHappened) {
- return perform(storyReporter, storyFailureIfItHappened);
- }
- }
- public static enum StepExecutionType {
- EXECUTABLE,
- PENDING,
- IGNORABLE,
- COMMENT,
- NOT_PERFORMED;
- }
- private class MethodInvoker {
- private final Method method;
- private final ParameterConverters parameterConverters;
- private final Paranamer paranamer;
- private final Meta meta;
- private final Type[] parameterTypes;
-
- public MethodInvoker(Method method, ParameterConverters parameterConverters, Paranamer paranamer, Meta meta) {
- this.method = method;
- this.parameterConverters = parameterConverters;
- this.paranamer = paranamer;
- this.meta = meta;
- this.parameterTypes = method.getGenericParameterTypes();
- }
- public Object invoke() throws InvocationTargetException, IllegalAccessException {
- return method.invoke(stepsInstance(), parameterValuesFrom(meta));
- }
- private Parameter[] methodParameters() {
- Parameter[] parameters = new Parameter[parameterTypes.length];
- String[] annotatedNames = annotatedParameterNames(method);
- String[] paranamerNames = paranamer.lookupParameterNames(method, false);
- for (int position = 0; position < parameterTypes.length; position++) {
- String name = parameterNameFor(position, annotatedNames, paranamerNames);
- parameters[position] = new Parameter(position, parameterTypes[position], name);
- }
- return parameters;
- }
- private String parameterNameFor(int position, String[] annotatedNames, String[] paranamerNames) {
- String annotatedName = nameByPosition(annotatedNames, position);
- String paranamerName = nameByPosition(paranamerNames, position);
- if (annotatedName != null) {
- return annotatedName;
- } else if (paranamerName != null) {
- return paranamerName;
- }
- return null;
- }
- private String nameByPosition(String[] names, int position) {
- return position < names.length ? names[position] : null;
- }
- private Object[] parameterValuesFrom(Meta meta) {
- Object[] values = new Object[parameterTypes.length];
- for (Parameter parameter : methodParameters()) {
- values[parameter.position] = parameterConverters.convert(parameter.valueFrom(meta), parameter.type);
- }
- return values;
- }
- private class Parameter {
- private final int position;
- private final Type type;
- private final String name;
- public Parameter(int position, Type type, String name) {
- this.position = position;
- this.type = type;
- this.name = name;
- }
- public String valueFrom(Meta meta) {
- if (name == null) {
- return null;
- }
- return meta.getProperty(name);
- }
- }
- }
- private static class ParameterName {
- private String name;
- private boolean annotated;
- private boolean fromContext;
- private ParameterName(String name, boolean annotated, boolean fromContext) {
- this.name = name;
- this.annotated = annotated;
- this.fromContext = fromContext;
- }
- }
- }