Package com.technophobia.substeps.runner.builder

Source Code of com.technophobia.substeps.runner.builder.SubstepNodeBuilder

/*
*  Copyright Technophobia Ltd 2012
*
*   This file is part of Substeps.
*
*    Substeps is free software: you can redistribute it and/or modify
*    it under the terms of the GNU Lesser General Public License as published by
*    the Free Software Foundation, either version 3 of the License, or
*    (at your option) any later version.
*
*    Substeps is distributed in the hope that it will be useful,
*    but WITHOUT ANY WARRANTY; without even the implied warranty of
*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*    GNU Lesser General Public License for more details.
*
*    You should have received a copy of the GNU Lesser General Public License
*    along with Substeps.  If not, see <http://www.gnu.org/licenses/>.
*/
package com.technophobia.substeps.runner.builder;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.collect.Lists;
import com.technophobia.substeps.execution.node.StepImplementationNode;
import com.technophobia.substeps.execution.node.StepNode;
import com.technophobia.substeps.execution.node.SubstepNode;
import com.technophobia.substeps.model.ExampleParameter;
import com.technophobia.substeps.model.ParentStep;
import com.technophobia.substeps.model.PatternMap;
import com.technophobia.substeps.model.Step;
import com.technophobia.substeps.model.StepImplementation;
import com.technophobia.substeps.model.SubSteps.StepParameter;
import com.technophobia.substeps.model.Util;
import com.technophobia.substeps.model.exception.SubstepsConfigurationException;
import com.technophobia.substeps.model.parameter.Converter;
import com.technophobia.substeps.runner.TestParameters;

public class SubstepNodeBuilder {

    private static final Logger log = LoggerFactory.getLogger(SubstepNodeBuilder.class);
    private final TestParameters parameters;

    SubstepNodeBuilder(final TestParameters parameters) {

        this.parameters = parameters;
    }

    public SubstepNode build(final String scenarioDescription, final List<Step> steps,
            final PatternMap<ParentStep> subStepsMapLocal, final ParentStep parent,
            final ExampleParameter parametersForSteps, final boolean throwExceptionIfUnableToBuildMethodArgs,
            final Set<String> tags, final int depth) {

        if (steps == null || steps.isEmpty()) {

            throw new SubstepsConfigurationException("There are no steps for " + scenarioDescription + " or a substep");
        }

        final List<StepNode> substeps = Lists.newArrayList();

        for (final Step step : steps) {

            substeps.add(buildStepNode(scenarioDescription, step, subStepsMapLocal, parent, parametersForSteps,
                    throwExceptionIfUnableToBuildMethodArgs, tags, depth + 1));

        }

        return new SubstepNode(substeps, tags, depth);
    }

    public StepNode buildStepNode(final String scenarioDescription, final Step step,
            final PatternMap<ParentStep> subStepsMapLocal, final ParentStep parent,
            final ExampleParameter parametersForSteps, final boolean throwExceptionIfUnableToBuildMethodArgs,
            final Set<String> tags, final int depth) {

        substituteStepParametersIntoStep(parametersForSteps, step);

        // is this step defined as a root of some sub steps, ie a parent?
        ParentStep substepsParent = null;

        if (subStepsMapLocal != null) {
            substepsParent = locateSubStepsParent(subStepsMapLocal, step);
        }

        StepNode stepNode;

        if (substepsParent != null) {

            stepNode = buildSubstepNode(scenarioDescription, step, subStepsMapLocal,
                    throwExceptionIfUnableToBuildMethodArgs, tags, depth, substepsParent);
           
            // this step was implemented by a substep as opposed to a step impl

        } else {

            stepNode = buildStepImplementationNode(parent, step, throwExceptionIfUnableToBuildMethodArgs, tags, depth);
        }

        return stepNode;
    }

    private SubstepNode buildSubstepNode(final String scenarioDescription, final Step step,
            final PatternMap<ParentStep> subStepsMapLocal, final boolean throwExceptionIfUnableToBuildMethodArgs,
            final Set<String> tags, final int depth, final ParentStep substepsParent) {
        substepsParent.initialiseParamValues(-1, step.getParameterLine());

        final ExampleParameter parametersForSubSteps = substepsParent.getParamValueMap();

        final List<StepImplementation> list = this.parameters.getSyntax().checkForStepImplementations(
                step.getKeyword(), step.getParameterLine(), step.getSource(), step.getSourceLineNumber());

        if (list != null && !list.isEmpty()) {
            final StepImplementation problem = list.get(0);

            // we've got a step implementation that matches a parent
            // step, ie a step that has substeps
            // fail immediately or mark as parse error

            final String msg = "line: [" + step.getParameterLine() + "] in [" + step.getSource()
                    + "] matches step implementation method: [" + problem.getMethod().toString()
                    + "] AND matches a sub step definition: [" + substepsParent.getParent().getParameterLine()
                    + "] in [" + substepsParent.getSubStepFile() + "]";

            throw new SubstepsConfigurationException(msg);

        }

        final SubstepNode substepNode = build(scenarioDescription, substepsParent.getSteps(), subStepsMapLocal,
                substepsParent, parametersForSubSteps, throwExceptionIfUnableToBuildMethodArgs, tags, depth);
     // Change TPCLA-299
     //  substepNode.setLine(substepsParent.getParent().getParameterLine());
       substepNode.setLine(step.getLine());
        substepNode.setFileUri(substepsParent.getSubStepFileUri());
        substepNode.setLineNumber(substepsParent.getSourceLineNumber());
        return substepNode;
    }

    private ParentStep locateSubStepsParent(final PatternMap<ParentStep> subStepsMapLocal, final Step step) {
        ParentStep substepsParent = subStepsMapLocal.get(step.getLine(), 0);

        // if we're not strict then we can look for other step defs that fit
        if (!this.parameters.getSyntax().isStrict() && substepsParent == null) {
            final String originalKeyword = step.getKeyword();

            for (final String altKeyword : this.parameters.getSyntax().getNonStrictKeywordPrecedence()) {
                // don't use the same keyword again
                if (altKeyword.compareToIgnoreCase(originalKeyword) != 0) {

                    final String altLine = step.getLine().replaceFirst(originalKeyword, altKeyword);
                    substepsParent = subStepsMapLocal.get(altLine, 0);
                    if (substepsParent != null) {
                        // do we need to modify the parent ??

                        substepsParent = substepsParent.cloneWithAltLine(altLine);

                        break;
                    }
                }
            }
        }

        return substepsParent;
    }

    public void substituteStepParametersIntoStep(final ExampleParameter parametersForSteps, final Step step) {
        // if this is an outline, need to perform token replacement at this
        // level before passing down the chain
        if (parametersForSteps != null && !parametersForSteps.getParameters().isEmpty()) {

            // replace any tokens in this step
            step.setParameterLine(substitutePlaceholders(step.getLine(), parametersForSteps.getParameters()));

            final List<Map<String, String>> inlineTable = step.getInlineTable();
            if (inlineTable != null) {
                log.trace("substituting inline table values");

                final List<Map<String, String>> replacedInlineTable = new ArrayList<Map<String, String>>();

                for (final Map<String, String> row : inlineTable) {
                    final Map<String, String> replacedRow = new HashMap<String, String>();
                    replacedInlineTable.add(replacedRow);
                    final Set<Entry<String, String>> entrySet = row.entrySet();

                    for (final Entry<String, String> e : entrySet) {
                        replacedRow.put(e.getKey(),
                                substitutePlaceholders(e.getValue(), parametersForSteps.getParameters()));
                    }
                }

                step.setSubstitutedInlineTable(replacedInlineTable);
            }
        }
    }

    public StepImplementationNode buildStepImplementationNode(final ParentStep parent, final Step step,
            final boolean throwExceptionIfUnableToBuildMethodArgs, final Set<String> tags, final int depth) {

        log.debug("looking for impl for step: " + step.toString());

        if (parent != null && parent.getParamValueMap() != null) {
            step.setParameterLine(substitutePlaceholders(step.getLine(), parent.getParamValueMap().getParameters()));
        }

        final StepImplementation execImpl = pickImplToExecute(step);

        if (execImpl != null) {

            final StepImplementationNode stepImplementationNode = new StepImplementationNode(
                    execImpl.getImplementedIn(), execImpl.getMethod(), tags, depth);

            stepImplementationNode.setLine(step.getParameterLine());
            stepImplementationNode.setFileUri(step.getSource().getAbsolutePath());
            stepImplementationNode.setLineNumber(step.getSourceLineNumber());

            try {
                setMethodParameters(execImpl, step.getParameterLine(), parent, step.getSubstitutedInlineTable(),
                        stepImplementationNode);

            } catch (final Throwable e) {

                if (throwExceptionIfUnableToBuildMethodArgs) {
                    throw new RuntimeException(e);
                } else {
                    log.debug(e.getMessage(), e);
                }
            } finally {

                // need to clear this out for the next time around
                step.setParameterLine(null);
            }

            return stepImplementationNode;

        } else {

            log.error("Unable to locate an implementation for the step: " + step.toDebugString());

            throw new SubstepsConfigurationException("Unable to locate an implementation for the step: "
                    + step.toDebugString() + " in " + step.getSource());
        }
    }

    private StepImplementation pickImplToExecute(final Step step) {

        StepImplementation impl = null;

        // using the specified 'phrase' look for a corresponding impl

        final List<StepImplementation> list = this.parameters.getSyntax().getStepImplementations(step.getKeyword(),
                step.getParameterLine(), step.getSource(), step.getSourceLineNumber());

        if (list != null && list.size() > 1) {
            log.error("found too many impls for line: " + step.getLine());

            for (final StepImplementation si : list) {
                log.error("impl: regex[" + si.getValue() + "] in " + si.getImplementedIn().getSimpleName() + "."
                        + si.getMethod().getName());
            }

            throw new SubstepsConfigurationException("Ambiguity resolving step to impl: " + step.toDebugString());
        }

        if (list != null && !list.isEmpty()) {
            impl = list.get(0);
        }

        return impl;
    }

    private void setMethodParameters(final StepImplementation execImpl, final String stepParameter,
            final ParentStep parent, final List<Map<String, String>> inlineTable, final StepImplementationNode stepNode)
            throws IllegalArgumentException {

        final Method stepImplementationMethod = execImpl.getMethod();

        final Class<?>[] stepImplementationMethodParameterTypes = stepImplementationMethod.getParameterTypes();

        final Class<? extends Converter<?>>[] parameterConverters = getParameterConverters(stepImplementationMethod);

        if (stepImplementationMethodParameterTypes != null && stepImplementationMethodParameterTypes.length > 0) {
            Map<String, String> paramValueMap = null;

            if (parent != null && parent.getParamValueMap() != null) {
                paramValueMap = parent.getParamValueMap().getParameters();
            }

            final Object[] methodParameters = getStepMethodArguments(stepParameter, paramValueMap, execImpl.getValue(),
                    inlineTable, stepImplementationMethodParameterTypes, parameterConverters, stepNode);

            if (methodParameters.length != stepImplementationMethodParameterTypes.length) {
                throw new IllegalArgumentException(
                        "Argument mismatch between what expected for step impl and what found in feature");
            }
        }
    }

    private Object[] getStepMethodArguments(final String stepParameter, final Map<String, String> parentArguments,
            final String stepImplementationPattern, final List<Map<String, String>> inlineTable,
            final Class<?>[] parameterTypes, final Class<? extends Converter<?>>[] converterTypes,
            final StepImplementationNode stepNode) {
        // does the stepParameter contain any <> which require substitution ?
        log.debug("getStepMethodArguments for: " + stepParameter);

        final String substitutedStepParam = substitutePlaceholders(stepParameter, parentArguments);

        stepNode.setLine(substitutedStepParam);
        List<Object> argsList = Util.getArgs(stepImplementationPattern, substitutedStepParam, parameterTypes,
                converterTypes);

        if (inlineTable != null) {
            if (argsList == null) {
                argsList = new ArrayList<Object>();
            }
            argsList.add(inlineTable);
        }

        Object[] arguments = null;

        if (argsList != null) {
            arguments = new Object[argsList.size()];
            arguments = argsList.toArray(arguments);
        }

        stepNode.setMethodArgs(arguments);

        return arguments;
    }

    private Class<? extends Converter<?>>[] getParameterConverters(final Method method) {

        final Annotation[][] annotations = method.getParameterAnnotations();
        final int size = annotations.length;

        @SuppressWarnings("unchecked")
        final Class<? extends Converter<?>>[] result = new Class[size];

        for (int i = 0; i < size; i++) {
            for (final Annotation annotation : annotations[i]) {
                if (annotation instanceof StepParameter) {
                    result[i] = ((StepParameter) annotation).converter();
                }
            }
        }

        return result;
    }

    public String substitutePlaceholders(final String stepParameter, final Map<String, String> parentArguments) {
        // is there anything to replace?
        String rtn;
        final String paramRegEx = ".*<([^>]*)>.*";
        final Pattern findParamPattern = Pattern.compile(paramRegEx);
        if (parentArguments != null && findParamPattern.matcher(stepParameter).matches()) {
            // need to do a replacement, split on >
            rtn = stepParameter;
            final String paramRegEx2 = ".*<(.*)";
            final Pattern p2 = Pattern.compile(paramRegEx2);

            final String[] splits = stepParameter.split(">");

            for (final String s : splits) {
                final Matcher matcher = p2.matcher(s);
                if (matcher.find()) {
                    final String key = matcher.group(1);
                    String val = parentArguments.get(key);
                    log.debug("replacing: <" + key + "> with: " + val + " in string: " + rtn);

                    if (val == null) {
                        val = " ";
                    }

                    rtn = rtn.replaceAll("<" + key + ">", Matcher.quoteReplacement(val));

                }
            }
        } else {
            // nothing to replace
            rtn = stepParameter;
        }

        return rtn;
    }

}
TOP

Related Classes of com.technophobia.substeps.runner.builder.SubstepNodeBuilder

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.