/* Generated By:JJTree: Do not edit this line. SimpleNode.java */
package net.sourceforge.pmd.ast;

import net.sourceforge.pmd.dfa.IDataFlowNode;
import net.sourceforge.pmd.jaxen.Attribute;
import net.sourceforge.pmd.jaxen.DocumentNavigator;
import net.sourceforge.pmd.symboltable.Scope;
import org.jaxen.BaseXPath;
import org.jaxen.JaxenException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.ParserConfigurationException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public abstract class SimpleNode implements Node {

    protected Node parent;
    protected Node[] children;
    protected int id;
    protected JavaParser parser;
    private String image;
    protected int beginLine = -1;
    protected int endLine;
    protected int beginColumn = -1;
    protected int endColumn;
    private Scope scope;
    private IDataFlowNode dataFlowNode;

    public IDataFlowNode getDataFlowNode() {
        if (this.dataFlowNode == null) {
            if (this.parent != null) {
                return ((SimpleNode) parent).getDataFlowNode();
            }
            return null; //TODO wise?
        }
        return dataFlowNode;
    }

    public void setDataFlowNode(IDataFlowNode dataFlowNode) {
        this.dataFlowNode = dataFlowNode;
    }

    public SimpleNode(int i) {
        id = i;
    }

    public SimpleNode(JavaParser p, int i) {
        this(i);
        parser = p;
    }

    public void setScope(Scope scope) {
        this.scope = scope;
    }

    public Scope getScope() {
        if (scope == null) {
            return ((SimpleNode) parent).getScope();
        }
        return scope;
    }

    public int getBeginLine() {
        return beginLine;
    }

    // A label is a more visually useful image, e.g.
    // int[[ for a primary suffix that's a 2D array of ints
    // this is used only by the Designer to show nodes more helpfully
    public String getLabel() {
        return null;        
    }

    public boolean hasImageEqualTo(String arg) {
    	return image != null && image.equals(arg);
    }
    
    public void testingOnly__setBeginLine(int i) {
        this.beginLine = i;
    }

    public void testingOnly__setBeginColumn(int i) {
        this.beginColumn = i;
    }

    public int getBeginColumn() {
        if (beginColumn != -1) {
            return beginColumn;
        } else {
            if ((children != null) && (children.length > 0)) {
                return ((SimpleNode) children[0]).getBeginColumn();
            } else {
                throw new RuntimeException("Unable to determine begining line of Node.");
            }
        }
    }

    public String getImage() {
        return image;
    }

    public void setImage(String image) {
        this.image = image;
    }

    public int getEndLine() {
        return endLine;
    }

    public int getEndColumn() {
        return endColumn;
    }

    public Node getNthParent(int n) {
        Node result = null;
        for (int i = 0; i < n; i++) {
            if (result == null) {
                result = this.jjtGetParent();
            } else {
                result = result.jjtGetParent();
            }
        }
        return result;
    }

    /**
     * Traverses up the tree to find the first parent instance of type parentType
     *
     * @param parentType class which you want to find.
     * @return Node of type parentType.  Returns null if none found.
     */
    public <T> T getFirstParentOfType(Class<T> parentType) {
        Node parentNode = jjtGetParent();
        while (parentNode != null && parentNode.getClass() != parentType) {
            parentNode = parentNode.jjtGetParent();
        }
        return (T) parentNode;
    }

    /**
     * Traverses up the tree to find all of the parent instances of type parentType
     *
     * @param parentType classes which you want to find.
     * @return List of parentType instances found.
     */
    public <T> List<T> getParentsOfType(Class<T> parentType) {
        List<T> parents = new ArrayList<T>();
        Node parentNode = jjtGetParent();
        while (parentNode != null) {
            if (parentNode.getClass() == parentType) {
                parents.add((T) parentNode);
            }
            parentNode = parentNode.jjtGetParent();
        }
        return parents;
    }

    public <T> List<T> findChildrenOfType(Class<T> targetType) {
        List<T> list = new ArrayList<T>();
        findChildrenOfType(targetType, list);
        return list;
    }

    public <T> void findChildrenOfType(Class<T> targetType, List<T> results) {
        findChildrenOfType(this, targetType, results, true);
    }

    public <T> void findChildrenOfType(Class<T> targetType, List<T> results, boolean descendIntoNestedClasses) {
        this.findChildrenOfType(this, targetType, results, descendIntoNestedClasses);
    }

    private <T> void findChildrenOfType(Node node, Class<T> targetType, List<T> results, boolean descendIntoNestedClasses) {
        if (node.getClass().equals(targetType)) {
            results.add((T) node);
        }

        if (!descendIntoNestedClasses) {
            if (node instanceof ASTClassOrInterfaceDeclaration && ((ASTClassOrInterfaceDeclaration) node).isNested()) {
                return;
            }

            if (node instanceof ASTClassOrInterfaceBodyDeclaration && ((ASTClassOrInterfaceBodyDeclaration) node).isAnonymousInnerClass()) {
                return;
            }
        }

        for (int i = 0; i < node.jjtGetNumChildren(); i++) {
            Node child = node.jjtGetChild(i);
            if (child.jjtGetNumChildren() > 0) {
                findChildrenOfType(child, targetType, results, descendIntoNestedClasses);
            } else {
                if (child.getClass().equals(targetType)) {
                    results.add((T) child);
                }
            }
        }
    }

    public void jjtSetParent(Node n) {
        parent = n;
    }

    public Node jjtGetParent() {
        return parent;
    }

    public void jjtAddChild(Node n, int i) {
        if (children == null) {
            children = new Node[i + 1];
        } else if (i >= children.length) {
            Node c[] = new Node[i + 1];
            System.arraycopy(children, 0, c, 0, children.length);
            children = c;
        }
        children[i] = n;
    }

    public Node jjtGetChild(int i) {
        return children[i];
    }

    public int jjtGetNumChildren() {
        return (children == null) ? 0 : children.length;
    }

    public String toString(String prefix) {
        return prefix + toString();
    }

    public Document asXml() {
        try {
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            DocumentBuilder db = dbf.newDocumentBuilder();
            Document document = db.newDocument();
            appendElement(document);
            return document;
        } catch (ParserConfigurationException pce) {
            throw new RuntimeException(pce);
        }
    }

    protected void appendElement(org.w3c.dom.Node parentNode) {
        DocumentNavigator docNav = new DocumentNavigator();
        Document ownerDocument = parentNode.getOwnerDocument();
        if (ownerDocument == null) {
            //If the parentNode is a Document itself, it's ownerDocument is null
            ownerDocument = (Document) parentNode;
        }
        String elementName = docNav.getElementName(this);
        Element element = ownerDocument.createElement(elementName);
        parentNode.appendChild(element);
        for (Iterator<Attribute> iter = docNav.getAttributeAxisIterator(this); iter.hasNext();) {
            Attribute attr = iter.next();
            element.setAttribute(attr.getName(), attr.getValue());
        }
        for (Iterator iter = docNav.getChildAxisIterator(this); iter.hasNext();) {
            SimpleNode child = (SimpleNode) iter.next();
            child.appendElement(element);
        }
    }

    /* Override this method if you want to customize how the node dumps
       out its children. */
    public void dump(String prefix) {
        System.out.println(toString(prefix) + (image == null ? "" : ":" + image));
        dumpChildren(prefix);
    }

    protected void dumpChildren(String prefix) {
        if (children != null) {
            for (int i = 0; i < children.length; ++i) {
                SimpleNode n = (SimpleNode) children[i];
                if (n != null) {
                    n.dump(prefix + " ");
                }
            }
        }
    }


    /**
     * Traverses down the tree to find the first child instance of type childType
     *
     * @param childType class which you want to find.
     * @return Node of type childType.  Returns <code>null</code> if none found.
     */
    public <T> T getFirstChildOfType(Class<T> childType) {
        return getFirstChildOfType(childType, this);
    }

    private <T> T getFirstChildOfType(Class<T> childType, Node node) {
        for (int i = 0; i < node.jjtGetNumChildren(); i++) {
            Node n = node.jjtGetChild(i);
            if (n != null) {
                if (n.getClass().equals(childType))
                    return (T) n;
                T n2 = getFirstChildOfType(childType, n);
                if (n2 != null)
                    return n2;
            }
        }
        return null;
    }

    /**
     * Finds if this node contains a child of the given type.
     * This is an utility method that uses {@link #findChildrenOfType(Class)}
     *
     * @param type the node type to search
     * @return <code>true</code> if there is at lease on child of the given type and <code>false</code> in any other case
     */
    public final <T> boolean containsChildOfType(Class<T> type) {
        return !findChildrenOfType(type).isEmpty();
    }

    public List findChildNodesWithXPath(String xpathString) throws JaxenException {
        return new BaseXPath(xpathString, new DocumentNavigator()).selectNodes(this);
    }
}
