package org.jbehave.core.steps;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
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.AsParameters;
import org.jbehave.core.annotations.ToContext;
import org.jbehave.core.annotations.FromContext;
import org.jbehave.core.annotations.Named;
import org.jbehave.core.configuration.Keywords;
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.parsers.StepMatcher;
import org.jbehave.core.reporters.StoryReporter;
import com.thoughtworks.paranamer.NullParanamer;
import com.thoughtworks.paranamer.Paranamer;
import org.jbehave.core.steps.context.StepsContext;
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.silent;
import static org.jbehave.core.steps.AbstractStepResult.skipped;
import static org.jbehave.core.steps.AbstractStepResult.successful;
public class StepCreator {
public static final String PARAMETER_TABLE_START = "\uff3b";
public static final String PARAMETER_TABLE_END = "\uff3d";
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 ParameterControls parameterControls;
private final Pattern delimitedNamePattern;
private final StepMatcher stepMatcher;
private final StepsContext stepsContext;
private StepMonitor stepMonitor;
private Paranamer paranamer = new NullParanamer();
private boolean dryRun = false;
public StepCreator(Class<?> stepsType, InjectableStepsFactory stepsFactory,
StepsContext stepsContext, ParameterConverters parameterConverters, ParameterControls parameterControls,
StepMatcher stepMatcher, StepMonitor stepMonitor) {
this.stepsType = stepsType;
this.stepsFactory = stepsFactory;
this.stepsContext = stepsContext;
this.parameterConverters = parameterConverters;
this.parameterControls = parameterControls;
this.stepMatcher = stepMatcher;
this.stepMonitor = stepMonitor;
this.delimitedNamePattern = Pattern.compile(parameterControls.nameDelimiterLeft() + "(\\w+?)"
+ parameterControls.nameDelimiterRight());
}
public void useStepMonitor(StepMonitor stepMonitor) {
this.stepMonitor = stepMonitor;
}
public void useParanamer(Paranamer paranamer) {
this.paranamer = paranamer;
}
public void doDryRun(boolean dryRun) {
this.dryRun = dryRun;
}
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) {
switch (outcome) {
case ANY:
default:
return new BeforeOrAfterStep(method, storyAndScenarioMeta);
case SUCCESS:
return new UponSuccessStep(method, storyAndScenarioMeta);
case FAILURE:
return new UponFailureStep(method, storyAndScenarioMeta);
}
}
public Map<String, String> matchedParameters(final Method method, final String stepAsString,
final String stepWithoutStartingWord, final Map<String, String> namedParameters) {
Map<String, String> matchedParameters = new HashMap<String, String>();
if (stepMatcher.find(stepWithoutStartingWord)) {
// we've found a match, populate map
ParameterName[] parameterNames = parameterNames(method);
Type[] types = method.getGenericParameterTypes();
String[] values = parameterValuesForStep(namedParameters, types, parameterNames);
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) {
String[] annotatedNames = annotatedParameterNames(method);
String[] paranamerNames = paranamerParameterNames(method);
String[] contextNames = contextParameterNames(method);
ParameterName[] parameterNames = new ParameterName[annotatedNames.length];
for (int i = 0; i < annotatedNames.length; i++) {
parameterNames[i] = parameterName(annotatedNames, paranamerNames, contextNames, i);
}
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);
}
public Step createParametrisedStep(final Method method, final String stepAsString,
final String stepWithoutStartingWord, final Map<String, String> namedParameters) {
return new ParametrisedStep(stepAsString, method, stepWithoutStartingWord, namedParameters);
}
public Step createParametrisedStepUponOutcome(final Method method, final String stepAsString,
final String stepWithoutStartingWord, final Map<String, String> namedParameters, Outcome outcome) {
switch (outcome) {
case ANY:
return new UponAnyParametrisedStep(stepAsString, method, stepWithoutStartingWord, namedParameters);
case SUCCESS:
return new UponSuccessParametrisedStep(stepAsString, method, stepWithoutStartingWord, namedParameters);
case FAILURE:
return new UponFailureParametrisedStep(stepAsString, method, stepWithoutStartingWord, namedParameters);
default:
return new ParametrisedStep(stepAsString, method, stepWithoutStartingWord, namedParameters);
}
}
private String parametrisedStep(String stepAsString, Map<String, String> namedParameters, Type[] types,
ParameterName[] names, 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 (String name : namedParameters.keySet()) {
parametrisedStep = markNamedParameterValue(parametrisedStep, namedParameters, name);
}
return parametrisedStep;
}
private boolean hasTable(Type[] types) {
for (Type type : types) {
if ( isTable(type) ){
return true;
}
}
return false;
}
private String markNamedParameterValue(String stepText, Map<String, String> namedParameters, String name) {
String value = namedParameter(namedParameters, name);
if (value != null) {
stepText = stepText.replace(delimitedName(name), markedValue(value));
}
return stepText;
}
private String delimitedName(String name) {
return parameterControls.nameDelimiterLeft() + name + parameterControls.nameDelimiterRight();
}
private String markParsedParameterValue(String stepText, Type type, String value, boolean hasTable) {
if (value != null) {
if (isTable(type)) {
stepText = stepText.replace(value, markedTable(value));
} else {
// only mark non-empty string as parameter (JBEHAVE-656)
if (value.trim().length() != 0) {
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 );
stepText = stepText.replace(pad(value, leftPad, rightPad), pad(markedValue, leftPad, rightPad));
}
if ( !hasTable ){
stepText = stepText.replace(NEWLINE, PARAMETER_VALUE_NEWLINE);
}
}
}
return stepText;
}
private String markedTable(String value) {
return pad(value, PARAMETER_TABLE_START, PARAMETER_TABLE_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 isExamplesTable(Type type) {
return type instanceof Class && ((Class<?>) type).isAssignableFrom(ExamplesTable.class);
}
private boolean isExamplesTableParameters(Type type) {
boolean result = false;
if (type instanceof Class) {
((Class) type).isAnnotationPresent(AsParameters.class);
}
else if (type instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) type;
result = isExamplesTableParameters(rawClass(parameterizedType)) || isExamplesTableParameters(argumentClass(parameterizedType));
}
return result;
}
private boolean isExamplesTableParameters(Class type) {
return type != null && type.isAnnotationPresent(AsParameters.class);
}
private Class<?> rawClass(ParameterizedType type) {
Class result = null;
Type rawType = type.getRawType();
if (rawType instanceof Class) {
result = (Class) rawType;
}
return result;
}
private Class<?> argumentClass(ParameterizedType type) {
Class result = null;
Type[] typeArguments = type.getActualTypeArguments();
if (typeArguments.length > 0) {
Type argument = typeArguments[0];
if (argument instanceof Class) {
result = (Class) argument;
}
}
return result;
}
private String[] parameterValuesForStep(Map<String, String> namedParameters, Type[] types, ParameterName[] names) {
final String[] parameters = new String[types.length];
for (int position = 0; position < types.length; position++) {
parameters[position] = parameterForPosition(position, names, namedParameters);
}
return parameters;
}
private Object[] convertParameterValues(String[] valuesAsString, Type[] types, ParameterName[] names) {
final Object[] parameters = new Object[valuesAsString.length];
for (int position = 0; position < valuesAsString.length; position++) {
if (names[position].fromContext) {
parameters[position] = stepsContext.get(valuesAsString[position]);
} else {
parameters[position] = parameterConverters.convert(valuesAsString[position], types[position]);
}
}
return parameters;
}
private String parameterForPosition(int position, ParameterName[] names, Map<String, String> namedParameters) {
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;
boolean delimitedNamedParameters = false;
if (isGroupName(name)) {
parameter = matchedParameter(name);
String delimitedName = delimitedNameFor(parameter);
if (delimitedName != null) {
name = delimitedName;
delimitedNamedParameters = true;
} else {
monitorUsingNameForParameter(name, position, annotated);
}
}
if (delimitedNamedParameters || isTableName(namedParameters, name)) {
parameter = namedParameter(namedParameters, name);
if (parameter != null) {
monitorUsingTableNameForParameter(name, position, annotated);
}
}
if (fromContext && parameter == null) {
parameter = name;
stepMonitor.usingStepsContextParameter(parameter);
}
}
if (parameter == null) {
// This allow parameters to be in different order.
position = position - numberOfPreviousFromContext(names, position);
stepMonitor.usingNaturalOrderForParameter(position);
parameter = matchedParameter(position);
String delimitedName = delimitedNameFor(parameter);
if (delimitedName != null && isTableName(namedParameters, delimitedName)) {
parameter = namedParameter(namedParameters, delimitedName);
}
}
stepMonitor.foundParameter(parameter, position);
return parameter;
}
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 String delimitedNameFor(String parameter) {
if (!parameterControls.delimiterNamedParameters()) {
return null;
}
Matcher matcher = delimitedNamePattern.matcher(parameter);
return matcher.matches() ? matcher.group(1) : null;
}
String matchedParameter(String name) {
String[] parameterNames = stepMatcher.parameterNames();
for (int i = 0; i < parameterNames.length; i++) {
String parameterName = parameterNames[i];
if (name.equals(parameterName)) {
return matchedParameter(i);
}
}
throw new ParameterNotFound(name, parameterNames);
}
private String matchedParameter(int position) {
String[] parameterNames = stepMatcher.parameterNames();
int matchedPosition = position + 1;
if (matchedPosition <= parameterNames.length) {
return stepMatcher.parameter(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;
}
private String namedParameter(Map<String, String> namedParameters, String name) {
return namedParameters.get(name);
}
private boolean isTableName(Map<String, String> namedParameters, String name) {
return namedParameter(namedParameters, name) != null;
}
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 static abstract class AbstractStep implements Step {
public String asString(Keywords keywords) {
return toString();
}
@Override
public String toString() {
return ToStringBuilder.reflectionToString(this, ToStringStyle.SIMPLE_STYLE);
}
}
private class BeforeOrAfterStep extends AbstractStep {
private final Method method;
private final Meta meta;
public BeforeOrAfterStep(Method method, Meta meta) {
this.method = method;
this.meta = meta;
}
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 silent(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));
}
public StepResult doNotPerform(UUIDExceptionWrapper storyFailureIfItHappened) {
return perform(storyFailureIfItHappened);
}
private class UUIDExceptionWrapperInjector implements ParameterConverters.ParameterConverter {
private final UUIDExceptionWrapper storyFailureIfItHappened;
public UUIDExceptionWrapperInjector(UUIDExceptionWrapper storyFailureIfItHappened) {
this.storyFailureIfItHappened = storyFailureIfItHappened;
}
public boolean accept(Type type) {
return UUIDExceptionWrapper.class == type;
}
public Object convertValue(String value, Type type) {
return storyFailureIfItHappened;
}
}
public String asString(Keywords keywords) {
return method.getName()+";"+meta.asString(keywords);
}
}
public class UponSuccessStep extends AbstractStep {
private BeforeOrAfterStep beforeOrAfterStep;
public UponSuccessStep(Method method, Meta storyAndScenarioMeta) {
this.beforeOrAfterStep = new BeforeOrAfterStep(method, storyAndScenarioMeta);
}
public StepResult doNotPerform(UUIDExceptionWrapper storyFailureIfItHappened) {
return skipped();
}
public StepResult perform(UUIDExceptionWrapper storyFailureIfItHappened) {
return beforeOrAfterStep.perform(storyFailureIfItHappened);
}
public String asString(Keywords keywords) {
return beforeOrAfterStep.asString(keywords);
}
}
public class UponFailureStep extends AbstractStep {
private final BeforeOrAfterStep beforeOrAfterStep;
public UponFailureStep(Method method, Meta storyAndScenarioMeta) {
this.beforeOrAfterStep = new BeforeOrAfterStep(method, storyAndScenarioMeta);
}
public StepResult doNotPerform(UUIDExceptionWrapper storyFailureIfItHappened) {
return beforeOrAfterStep.perform(storyFailureIfItHappened);
}
public StepResult perform(UUIDExceptionWrapper storyFailureIfItHappened) {
return skipped();
}
public String asString(Keywords keywords) {
return beforeOrAfterStep.asString(keywords);
}
}
public class ParametrisedStep extends AbstractStep {
private Object[] convertedParameters;
private String parametrisedStep;
private final String stepAsString;
private final Method method;
private final String stepWithoutStartingWord;
private final Map<String, String> namedParameters;
public ParametrisedStep(String stepAsString, Method method, String stepWithoutStartingWord,
Map<String, String> namedParameters) {
this.stepAsString = stepAsString;
this.method = method;
this.stepWithoutStartingWord = stepWithoutStartingWord;
this.namedParameters = namedParameters;
}
public void describeTo(StoryReporter storyReporter) {
storyReporter.beforeStep(stepAsString);
}
public StepResult perform(UUIDExceptionWrapper storyFailureIfItHappened) {
Timer timer = new Timer().start();
try {
parametriseStep();
stepMonitor.performing(parametrisedStep, dryRun);
if (!dryRun) {
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(stepAsString).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());
}
}
public StepResult doNotPerform(UUIDExceptionWrapper storyFailureIfItHappened) {
try {
parametriseStep();
} catch (Throwable t) {
// step parametrisation failed, but still return
// notPerformed StepResult
}
return notPerformed(stepAsString).withParameterValues(parametrisedStep);
}
public String asString(Keywords keywords) {
if ( parametrisedStep == null){
parametriseStep();
}
return parametrisedStep;
}
private void parametriseStep() {
stepMatcher.find(stepWithoutStartingWord);
ParameterName[] names = parameterNames(method);
Type[] types = method.getGenericParameterTypes();
String[] parameterValues = parameterValuesForStep(namedParameters, types, names);
convertedParameters = convertParameterValues(parameterValues, types, names);
addNamedParametersToExamplesTables();
parametrisedStep = parametrisedStep(stepAsString, namedParameters, types, names, parameterValues);
}
private void addNamedParametersToExamplesTables() {
for (Object object : convertedParameters) {
if (object instanceof ExamplesTable) {
((ExamplesTable) object).withNamedParameters(namedParameters);
}
}
}
}
public class UponAnyParametrisedStep extends AbstractStep {
private ParametrisedStep parametrisedStep;
public UponAnyParametrisedStep(String stepAsString, Method method, String stepWithoutStartingWord,
Map<String, String> namedParameters){
this.parametrisedStep = new ParametrisedStep(stepAsString, method, stepWithoutStartingWord, namedParameters);
}
public StepResult doNotPerform(UUIDExceptionWrapper storyFailureIfItHappened) {
return perform(storyFailureIfItHappened);
}
public StepResult perform(UUIDExceptionWrapper storyFailureIfItHappened) {
return parametrisedStep.perform(storyFailureIfItHappened);
}
public String asString(Keywords keywords) {
return parametrisedStep.asString(keywords);
}
}
public class UponSuccessParametrisedStep extends AbstractStep {
private ParametrisedStep parametrisedStep;
public UponSuccessParametrisedStep(String stepAsString, Method method, String stepWithoutStartingWord,
Map<String, String> namedParameters){
this.parametrisedStep = new ParametrisedStep(stepAsString, method, stepWithoutStartingWord, namedParameters);
}
public StepResult doNotPerform(UUIDExceptionWrapper storyFailureIfItHappened) {
return skipped();
}
public StepResult perform(UUIDExceptionWrapper storyFailureIfItHappened) {
return parametrisedStep.perform(storyFailureIfItHappened);
}
public String asString(Keywords keywords) {
return parametrisedStep.asString(keywords);
}
}
public class UponFailureParametrisedStep extends AbstractStep {
private ParametrisedStep parametrisedStep;
public UponFailureParametrisedStep(String stepAsString, Method method, String stepWithoutStartingWord,
Map<String, String> namedParameters){
this.parametrisedStep = new ParametrisedStep(stepAsString, method, stepWithoutStartingWord, namedParameters);
}
public StepResult doNotPerform(UUIDExceptionWrapper storyFailureIfItHappened) {
return parametrisedStep.perform(storyFailureIfItHappened);
}
public StepResult perform(UUIDExceptionWrapper storyFailureIfItHappened) {
return skipped();
}
public String asString(Keywords keywords) {
return parametrisedStep.asString(keywords);
}
}
public static class PendingStep extends AbstractStep {
private final String stepAsString;
private final String previousNonAndStep;
private Method method;
public PendingStep(String stepAsString, String previousNonAndStep) {
this.stepAsString = stepAsString;
this.previousNonAndStep = previousNonAndStep;
}
public StepResult perform(UUIDExceptionWrapper storyFailureIfItHappened) {
return pending(stepAsString);
}
public StepResult doNotPerform(UUIDExceptionWrapper storyFailureIfItHappened) {
return pending(stepAsString);
}
public String stepAsString() {
return stepAsString;
}
public String previousNonAndStepAsString() {
return previousNonAndStep;
}
public void annotatedOn(Method method) {
this.method = method;
}
public boolean annotated() {
return method != null;
}
public String asString(Keywords keywords) {
return stepAsString;
}
}
public static class IgnorableStep extends AbstractStep {
private final String stepAsString;
public IgnorableStep(String stepAsString) {
this.stepAsString = stepAsString;
}
public StepResult perform(UUIDExceptionWrapper storyFailureIfItHappened) {
return ignorable(stepAsString);
}
public StepResult doNotPerform(UUIDExceptionWrapper storyFailureIfItHappened) {
return ignorable(stepAsString);
}
public String asString(Keywords keywords) {
return stepAsString;
}
}
public static class Comment extends AbstractStep {
private final String stepAsString;
public Comment(String stepAsString) {
this.stepAsString = stepAsString;
}
public StepResult perform(UUIDExceptionWrapper storyFailureIfItHappened) {
return comment(stepAsString);
}
public StepResult doNotPerform(UUIDExceptionWrapper storyFailureIfItHappened) {
return comment(stepAsString);
}
public String asString(Keywords keywords) {
return stepAsString;
}
}
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;
}
}
}