All Downloads are FREE. Search and download functionalities are using the official Maven repository.

cucumber.runtime.StepDefinitionMatch Maven / Gradle / Ivy

There is a newer version: 4.4.0
Show newest version
package cucumber.runtime;

import static gherkin.util.FixJava.map;

import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import cucumber.api.DataTable;
import cucumber.runtime.table.TableConverter;
import cucumber.runtime.xstream.LocalizedXStreams;
import gherkin.I18n;
import gherkin.formatter.Argument;
import gherkin.formatter.model.DataTableRow;
import gherkin.formatter.model.Match;
import gherkin.formatter.model.Step;
import gherkin.util.Mapper;

/**
 * This class wait cucumber PR: https://github.com/cucumber/cucumber-jvm/pull/1056
 * 
 * @author sgrillon
 */
public class StepDefinitionMatch extends Match {
    /**
     *
     */
    private static final long serialVersionUID = -1178845471665348415L;

    private final StepDefinition stepDefinition;
    private final transient String featurePath;
    // The official JSON gherkin format doesn't have a step attribute, so we're marking this as transient
    // to prevent it from ending up in the JSON.
    private final transient Step step;
    private final LocalizedXStreams localizedXStreams;

    public StepDefinitionMatch(List arguments, StepDefinition stepDefinition, String featurePath, Step step, LocalizedXStreams localizedXStreams) {
        super(arguments, stepDefinition.getLocation(false));
        this.stepDefinition = stepDefinition;
        this.featurePath = featurePath;
        this.step = step;
        this.localizedXStreams = localizedXStreams;
    }

    public void runStep(I18n i18n) throws Throwable {
        try {
            stepDefinition.execute(i18n, transformedArgs(step, localizedXStreams.get(i18n.getLocale())));
        } catch (CucumberException e) {
            throw e;
        } catch (Throwable t) {
            throw removeFrameworkFramesAndAppendStepLocation(t, getStepLocation());
        }
    }

    /**
     * @param step
     *            the step to run
     * @param xStream
     *            used to convert a string to declared stepdef arguments
     * @return an Array matching the types or {@code parameterTypes}, or an array of String if {@code parameterTypes} is null
     */
    private Object[] transformedArgs(Step step, LocalizedXStreams.LocalizedXStream xStream) {
        int argumentCount = getArguments().size();

        if (step.getRows() != null) {
            argumentCount++;
        } else if (step.getDocString() != null) {
            argumentCount++;
        }
        Integer parameterCount = stepDefinition.getParameterCount();

        if (parameterCount != null && (argumentCount > parameterCount || argumentCount + 1 < parameterCount)) {
            throw arityMismatch(parameterCount);
        }

        List result = new ArrayList();

        int n = 0;
        for (Argument a : getArguments()) {
            ParameterInfo parameterInfo = getParameterType(n, String.class);
            Object arg = parameterInfo.convert(a.getVal(), xStream);
            result.add(arg);
            n++;
        }

        if (step.getRows() != null) {
            result.add(tableArgument(step, n, xStream));
        } else if (step.getDocString() != null) {
            ParameterInfo parameterInfo = getParameterType(n, String.class);
            Object arg = parameterInfo.convert(step.getDocString().getValue(), xStream);
            result.add(arg);
        }

        if (parameterCount != null && argumentCount + 1 == parameterCount) {
            Object obj;
            if (getParameterType(n, DataTable.class).getType().toString().startsWith("java.util.List<")) {
                obj = new ArrayList();
            } else if (getParameterType(n, DataTable.class).getType().toString().startsWith("java.util.Map<")) {
                obj = new HashMap();
            } else {
                throw arityMismatch(parameterCount);
            }
            result.add(obj);
        }
        return result.toArray(new Object[result.size()]);
    }

    private ParameterInfo getParameterType(int n, Type argumentType) {
        ParameterInfo parameterInfo = stepDefinition.getParameterType(n, argumentType);
        if (parameterInfo == null) {
            // Some backends return null because they don't know
            parameterInfo = new ParameterInfo(argumentType, null, null, false, null);
        }
        return parameterInfo;
    }

    private Object tableArgument(Step step, int argIndex, LocalizedXStreams.LocalizedXStream xStream) {
        ParameterInfo parameterInfo = getParameterType(argIndex, DataTable.class);
        TableConverter tableConverter = new TableConverter(xStream, parameterInfo);
        DataTable table = new DataTable(step.getRows(), tableConverter);
        Type type = parameterInfo.getType();
        return tableConverter.convert(table, type, parameterInfo.isTransposed());
    }

    private CucumberException arityMismatch(int parameterCount) {
        List arguments = createArgumentsForErrorMessage(step);
        return new CucumberException(String.format("Arity mismatch: Step Definition '%s' with pattern [%s] is declared with %s parameters. However, the gherkin step has %s arguments %s. \nStep: %s%s",
                stepDefinition.getLocation(true), stepDefinition.getPattern(), parameterCount, arguments.size(), arguments, step.getKeyword(), step.getName()));
    }

    private List createArgumentsForErrorMessage(Step step) {
        List arguments = new ArrayList(getArguments());
        if (step.getDocString() != null) {
            arguments.add(new Argument(-1, "DocString:" + step.getDocString().getValue()));
        }
        if (step.getRows() != null) {
            List> rows = map(step.getRows(), new Mapper>() {
                @Override
                public List map(DataTableRow row) {
                    return row.getCells();
                }
            });
            arguments.add(new Argument(-1, "Table:" + rows.toString()));
        }
        return arguments;
    }

    private Throwable removeFrameworkFramesAndAppendStepLocation(Throwable error, StackTraceElement stepLocation) {
        StackTraceElement[] stackTraceElements = error.getStackTrace();
        if (stackTraceElements.length == 0 || stepLocation == null) {
            return error;
        }

        int newStackTraceLength;
        for (newStackTraceLength = 1; newStackTraceLength < stackTraceElements.length; ++newStackTraceLength) {
            if (stepDefinition.isDefinedAt(stackTraceElements[newStackTraceLength - 1])) {
                break;
            }
        }
        StackTraceElement[] newStackTrace = new StackTraceElement[newStackTraceLength + 1];
        System.arraycopy(stackTraceElements, 0, newStackTrace, 0, newStackTraceLength);
        newStackTrace[newStackTraceLength] = stepLocation;
        error.setStackTrace(newStackTrace);
        return error;
    }

    public String getPattern() {
        return stepDefinition.getPattern();
    }

    public StackTraceElement getStepLocation() {
        return step.getStackTraceElement(featurePath);
    }

    public String getStepName() {
        return step.getName();
    }
}