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

com.technophobia.substeps.execution.ExecutionNode Maven / Gradle / Ivy

/*
 *	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 .
 */
package com.technophobia.substeps.execution;

import java.io.File;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;

import com.google.common.base.Predicate;
import com.google.common.base.Strings;

/**
 * represents a node on the tree of features, scenarios, substeps etc including
 * outlines and backgrounds
 * 
 * @author ian
 * 
 */
public class ExecutionNode {

    private static AtomicLong counter = new AtomicLong(1);

    private final long id; // for uniqueness

    private Feature feature = null;

    /**
     * An {@link ExecutionNode} can be seen as compiled substeps code - ready to
     * run. We include the fileUri and line number to tie this back to the
     * substeps source - the files from which the compiled substeps were
     * generated. This information could be seen as debug information - however
     * it is useful in other places - for example in editor plugins where we
     * have the compiled code but need to show where it came from to the user.
     * Note that these values won't always exist, for example the root node.
     * 
     */
    private String fileUri;
    private int lineNumber;

    private String scenarioName = null;

    private int depth = 0;
    private int rowNumber = -1;

    private List children = null;
    private List backgrounds = null;

    private ExecutionNode parent = null;

    private Class targetClass = null;
    private Method targetMethod = null;
    private Object[] methodArgs = null;

    private String line;
    private boolean background = false;

    private boolean outline = false;

    private final ExecutionNodeResult result = new ExecutionNodeResult();

    private Set tags; // used for analysis

    public ExecutionNode() {
        id = counter.getAndIncrement();
    }

    public void addChild(final ExecutionNode child) {
        child.setParent(this);
        child.setDepth(depth + 1);

        if (children == null) {
            children = new ArrayList();
        }
        children.add(child);
    }

    public void addBackground(final ExecutionNode backgroundNode) {
        if (backgrounds == null) {
            backgrounds = new ArrayList();
        }
        backgroundNode.background = true;
        backgrounds.add(backgroundNode);

        backgroundNode.setDepth(depth + 1);

    }

    /**
     * @return the feature
     */
    public Feature getFeature() {
        return feature;
    }

    /**
     * @param feature
     *            the feature to set
     */
    public void setFeature(final Feature feature) {
        this.feature = feature;
    }

    /**
     * @return the depth
     */
    public int getDepth() {
        return depth;
    }

    /**
     * @param depth
     *            the depth to set
     */
    public void setDepth(final int depth) {
        this.depth = depth;
    }

    /**
     * @return the rowNumber
     */
    public int getRowNumber() {
        return rowNumber;
    }

    /**
     * @param rowNumber
     *            the rowNumber to set
     */
    public void setRowNumber(final int rowNumber) {
        this.rowNumber = rowNumber;
    }

    /**
     * @return the parent
     */
    public ExecutionNode getParent() {
        return parent;
    }

    /**
     * @param parent
     *            the parent to set
     */
    public void setParent(final ExecutionNode parent) {

        if (parent.getId() == getId()) {
            throw new IllegalStateException("don't think so");
        }

        this.parent = parent;
    }

    /**
     * @return the id
     */
    public long getId() {
        return id;
    }

    /**
     * @return the targetClass
     */
    public Class getTargetClass() {
        return targetClass;
    }

    /**
     * @param targetClass
     *            the targetClass to set
     */
    public void setTargetClass(final Class targetClass) {
        this.targetClass = targetClass;
    }

    /**
     * @return the methodArgs
     */
    public Object[] getMethodArgs() {
        return methodArgs;
    }

    /**
     * @param methodArgs
     *            the methodArgs to set
     */
    public void setMethodArgs(final Object[] methodArgs) {
        this.methodArgs = methodArgs;
    }

    /**
     * @return the scenarioName
     */
    public String getScenarioName() {
        return scenarioName;
    }

    /**
     * @param scenarioName
     *            the scenarioName to set
     */
    public void setScenarioName(final String scenarioName) {
        this.scenarioName = scenarioName;
    }

    /**
     * @return the line
     */
    public String getLine() {
        return line;
    }

    /**
     * @param line
     *            the line to set
     */
    public void setLine(final String line) {
        this.line = line;
    }

    /**
     * @return the targetMethod
     */
    public Method getTargetMethod() {
        return targetMethod;
    }

    /**
     * @param targetMethod
     *            the targetMethod to set
     */
    public void setTargetMethod(final Method targetMethod) {
        this.targetMethod = targetMethod;
    }

    /**
     * @return the children
     */
    public List getChildren() {
        return children;
    }

    /**
     * @return the backgrounds
     */
    public List getBackgrounds() {
        return backgrounds;
    }

    public String printTree() {
        // traverse the tree
        final StringBuilder buf = new StringBuilder();

        buf.append("Execution tree:\n");

        buf.append(toDebugString()).append("\n");

        if (children != null) {
            for (final ExecutionNode child : children) {
                buf.append(child.toDebugString());
            }
        }

        return buf.toString();
    }

    @Override
    public String toString() {
        return id + ":" + getDescription() + " children size: " + getChildrenSize();
    }

    public String treeToString() {
        final StringBuilder buf = new StringBuilder();

        buf.append(toString());
        buf.append("\n");
        if (hasChildren()) {

            for (final ExecutionNode child : getChildren()) {

                buf.append(Strings.repeat("\t", depth));
                buf.append(child.treeToString());
                buf.append("\n");
            }
        }

        return buf.toString();
    }

    public String getDebugStringForThisNode() {
        final StringBuilder buf = new StringBuilder();

        buf.append(id);

        if (parent != null) {
            buf.append(Strings.repeat("\t", depth));

            if (feature != null) {
                buf.append(feature.getName()).append(" in ").append(feature.getFilename()).append("\n");
            } else if (scenarioName != null) {
                buf.append(scenarioName).append("\n");
            }

            if (rowNumber > -1) {
                buf.append("outline #: ").append(rowNumber).append("\n");
            }

            if (background) {
                buf.append("BACKGROUND\n");
            }

            if (line != null) {
                buf.append(line).append("\n");
            }

            appendMethodInfo("  -  ", buf);
        } else {
            buf.append(": Root");
        }
        return buf.toString();
    }

    /**
     * @return
     */
    public String toDebugString() {
        final StringBuilder buf = new StringBuilder();

        if (parent != null) {

            buf.append(id).append(Strings.repeat("\t", depth)).append("file: ").append(getFilename()).append(" ")
                    .append(parent.getId()).append(" ");

            if (feature != null) {
                buf.append(feature.getName()).append("\n");
            } else if (scenarioName != null) {
                buf.append(scenarioName).append("\n");
            }

            if (rowNumber > -1) {
                buf.append(" outline #: ").append(rowNumber).append("\n");
            }

            if (background) {
                buf.append("BACKGROUND\n");
            }

            if (backgrounds != null) {
                for (final ExecutionNode backgroundNode : backgrounds) {
                    buf.append(backgroundNode.toDebugString());
                }
            }

            boolean printedLine = false;
            if (line != null) {
                buf.append(line);
                printedLine = true;
            }

            appendMethodInfo("  -  ", buf);

            if (printedLine) {
                buf.append("\n");
            }
        }

        if (children != null) {
            for (final ExecutionNode child : children) {
                buf.append(child.toDebugString());
            }
        }

        // else we're root
        return buf.toString();
    }

    public void appendMethodInfo(final StringBuilder buf) {
        appendMethodInfo(null, buf);
    }

    /**
     * @param buf
     */
    public void appendMethodInfo(final String prefix, final StringBuilder buf) {
        if (targetClass != null && targetMethod != null) {

            if (prefix != null) {
                buf.append(prefix);
            }

            buf.append(targetClass.getSimpleName()).append(".").append(targetMethod.getName()).append("(");

            if (methodArgs != null) {
                boolean commaRequired = false;
                for (final Object arg : methodArgs) {
                    if (commaRequired) {
                        buf.append(", ");
                    }

                    boolean quotes = false;
                    if (arg instanceof String) {
                        quotes = true;
                        buf.append("\"");
                    }
                    buf.append(arg.toString());
                    if (quotes) {
                        buf.append("\"");
                    }
                    commaRequired = true;
                }
            }

            buf.append(")").append("\n");
        }
    }

    /**
     * @return
     */
    public boolean isOutlineScenario() {
        return outline;
    }

    /**
     * @param b
     */
    public void setOutline(final boolean isOutline) {
        outline = isOutline;

    }

    /**
     * @return
     */
    public boolean hasBackground() {
        return backgrounds != null && !backgrounds.isEmpty();
    }

    /**
     * @return
     */
    public boolean isExecutable() {
        return getTargetClass() != null && getTargetMethod() != null;
    }

    /**
     * @return
     */
    public boolean hasChildren() {
        return children != null && !children.isEmpty();
    }

    /**
     * @return
     */
    public Long getLongId() {
        return Long.valueOf(id);
    }

    /**
     * @return
     */
    public boolean shouldHaveChildren() {
        return isFeature() || isScenario() || isOutlineScenario() || this.parent == null;
    }

    /**
     * @return
     */
    public boolean isScenario() {
        return scenarioName != null;
    }

    /**
     * @return
     */
    public boolean isFeature() {
        return feature != null;
    }

    /**
     * @return the result
     */
    public ExecutionNodeResult getResult() {
        return result;
    }

    /**
     * @return the tags
     */
    public Set getTags() {
        return tags;
    }

    /**
     * @param tags
     *            the tags to set
     */
    public void setTags(final Set tags) {
        this.tags = tags;
    }

    /**
     * 
     */
    public boolean isStep() {

        return depth == 3 && !isOutlineScenario() || depth == 4 && parent.isOutlineScenario();
    }

    /*
     * (non-Javadoc)
     * 
     * @see java.lang.Object#hashCode()
     */
    @Override
    public int hashCode() {
        final int prime = 31;
        int res = 1;
        res = prime * res + (int) (id ^ (id >>> 32));
        return res;
    }

    /*
     * (non-Javadoc)
     * 
     * @see java.lang.Object#equals(java.lang.Object)
     */
    @Override
    public boolean equals(final Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final ExecutionNode other = (ExecutionNode) obj;
        if (id != other.id) {
            return false;
        }
        return true;
    }

    /**
     * @return the filename
     */
    public String getFilename() {
        return new File(getFileUri()).getName();
    }

    public String getFileUri() {
        // Use this filename if specified, or go up through the tree till we get
        // an answer
        if (fileUri != null) {
            return fileUri;
        } else if (parent != null) {
            fileUri = parent.getFileUri();
            return fileUri;
        } else {
            fileUri = "";
        }

        return fileUri;
    }

    public void setFileUri(final String fileUri) {
        this.fileUri = fileUri;
    }

    public int getLineNumber() {
        return lineNumber;
    }

    public void setLineNumber(final int lineNumber) {
        this.lineNumber = lineNumber;
    }

    public String getType() {

        String rtn = null;
        if (parent == null) {
            rtn = "Root node";
        } else if (isFeature()) {
            rtn = "Feature";
        } else if (isScenario()) {
            rtn = "Scenario";
        } else if (isOutlineScenario()) {
            rtn = "Scenario Outline";
        } else if (isStep()) {
            rtn = "Step";
        } else if (targetMethod != null) {
            rtn = "Step Implementation";
        }
        return rtn;

    }

    public String getDescription() {

        // return a string that represents what this is
        String rtn = null;

        if (line != null) {
            rtn = line;
        } else {
            if (isFeature()) {

                rtn = feature.getName();
            } else if (isScenario()) {

                rtn = scenarioName;
            } else if (parent != null && parent.isOutlineScenario()) {
                rtn = parent.scenarioName + " [" + rowNumber + "]";
            }
        }
        return rtn;
    }

    public boolean hasError() {
        return result.getResult() == ExecutionResult.FAILED || result.getResult() == ExecutionResult.PARSE_FAILURE;
    }

    public boolean hasPassed() {
        return result.getResult() == ExecutionResult.PASSED;
    }

    public Set getTagsFromHierarchy() {
        Set allTags = null;

        ExecutionNode node = this;

        while (node != null) {

            if (node.tags != null) {

                if (allTags == null) {
                    allTags = new HashSet();
                }
                allTags.addAll(node.tags);
            }

            node = node.parent;
        }

        return allTags;
    }

    public List getFailedChildNodes() {

        // TODO - how should we handle background or setup and tear down
        // failures ?

        final List failed = filterNodes(children, new Predicate() {

            public boolean apply(final ExecutionNode input) {

                return input.hasFailed();
            }
        });

        return failed;
    }

    private List filterNodes(final List sourceList,
            final Predicate predicate) {

        List filtered = null;
        if (sourceList != null) {

            for (final ExecutionNode node : sourceList) {

                if (predicate.apply(node)) {
                    if (filtered == null) {
                        filtered = new ArrayList();
                    }
                    filtered.add(node);
                }
            }
        }
        return filtered;
    }

    /**
     * @return
     */
    private boolean hasFailed() {

        // this node has failed if any of this node's backgrounds have failed,
        // this node's state is failed, or any of this node's children's state
        // has failed
        // TODO include backgrounds
        return result.getResult() == ExecutionResult.FAILED || getFailedChildNodes() != null;
    }

    /**
     * @param i
     */
    public ExecutionNode getChild(final int i) {
        ExecutionNode rtn = null;
        if (children != null && children.size() > i) {
            rtn = children.get(i);
        }
        return rtn;
    }

    /**
     * @return
     */
    public int getChildrenSize() {
        int rtn = 0;

        if (children != null) {
            rtn = children.size();
        }

        return rtn;
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy