/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.ast;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.jaxen.BaseXPath;
import org.jaxen.JaxenException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import net.sourceforge.pmd.lang.ast.xpath.Attribute;
import net.sourceforge.pmd.lang.ast.xpath.DocumentNavigator;
import net.sourceforge.pmd.lang.dfa.DataFlowNode;
public abstract class AbstractNode implements Node {
protected Node parent;
protected Node[] children;
protected int childIndex;
protected int id;
private String image;
protected int beginLine = -1;
protected int endLine;
protected int beginColumn = -1;
protected int endColumn;
private DataFlowNode dataFlowNode;
private Object userData;
protected GenericToken firstToken;
protected GenericToken lastToken;
public AbstractNode(int id) {
this.id = id;
}
public AbstractNode(int id, int theBeginLine, int theEndLine, int theBeginColumn, int theEndColumn) {
this(id);
beginLine = theBeginLine;
endLine = theEndLine;
beginColumn = theBeginColumn;
endColumn = theEndColumn;
}
public boolean isSingleLine() {
return beginLine == endLine;
}
@Override
public void jjtOpen() {
// to be overridden by subclasses
}
@Override
public void jjtClose() {
// to be overridden by subclasses
}
@Override
public void jjtSetParent(Node parent) {
this.parent = parent;
}
@Override
public Node jjtGetParent() {
return parent;
}
@Override
public void jjtAddChild(Node child, int index) {
if (children == null) {
children = new Node[index + 1];
} else if (index >= children.length) {
Node[] newChildren = new Node[index + 1];
System.arraycopy(children, 0, newChildren, 0, children.length);
children = newChildren;
}
children[index] = child;
child.jjtSetChildIndex(index);
}
@Override
public void jjtSetChildIndex(int index) {
childIndex = index;
}
@Override
public int jjtGetChildIndex() {
return childIndex;
}
@Override
public Node jjtGetChild(int index) {
return children[index];
}
@Override
public int jjtGetNumChildren() {
return children == null ? 0 : children.length;
}
@Override
public int jjtGetId() {
return id;
}
/**
* Subclasses should implement this method to return a name usable with
* XPathRule for evaluating Element Names.
*/
@Override
public abstract String toString();
@Override
public String getImage() {
return image;
}
@Override
public void setImage(String image) {
this.image = image;
}
@Override
public boolean hasImageEqualTo(String image) {
return this.getImage() != null && this.getImage().equals(image);
}
@Override
public int getBeginLine() {
return beginLine;
}
public void testingOnlySetBeginLine(int i) {
this.beginLine = i;
}
@Override
public int getBeginColumn() {
if (beginColumn != -1) {
return beginColumn;
} else {
if (children != null && children.length > 0) {
return children[0].getBeginColumn();
} else {
throw new RuntimeException("Unable to determine beginning line of Node.");
}
}
}
public void testingOnlySetBeginColumn(int i) {
this.beginColumn = i;
}
@Override
public int getEndLine() {
return endLine;
}
public void testingOnlySetEndLine(int i) {
this.endLine = i;
}
@Override
public int getEndColumn() {
return endColumn;
}
public void testingOnlySetEndColumn(int i) {
this.endColumn = i;
}
@Override
public DataFlowNode getDataFlowNode() {
if (this.dataFlowNode == null) {
if (this.parent != null) {
return parent.getDataFlowNode();
}
return null; // TODO wise?
}
return dataFlowNode;
}
@Override
public void setDataFlowNode(DataFlowNode dataFlowNode) {
this.dataFlowNode = dataFlowNode;
}
/**
* Returns the n-th parent or null if there are not <code>n</code> ancestors
*
* @param n
* how many ancestors to iterate over.
* @return the n-th parent or null.
* @throws IllegalArgumentException
* if <code>n</code> is not positive.
*/
@Override
public Node getNthParent(int n) {
if (n <= 0) {
throw new IllegalArgumentException();
}
Node result = this.jjtGetParent();
for (int i = 1; i < n; i++) {
if (result == null) {
return null;
}
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.
*/
@Override
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.
*/
@Override
public <T> List<T> getParentsOfType(Class<T> parentType) {
List<T> parents = new ArrayList<>();
Node parentNode = jjtGetParent();
while (parentNode != null) {
if (parentNode.getClass() == parentType) {
parents.add((T) parentNode);
}
parentNode = parentNode.jjtGetParent();
}
return parents;
}
/**
* {@inheritDoc}
*/
@Override
public <T> List<T> findDescendantsOfType(Class<T> targetType) {
List<T> list = new ArrayList<>();
findDescendantsOfType(this, targetType, list, true);
return list;
}
/**
* {@inheritDoc}
*/
@Override
public <T> void findDescendantsOfType(Class<T> targetType, List<T> results, boolean crossBoundaries) {
findDescendantsOfType(this, targetType, results, crossBoundaries);
}
private static <T> void findDescendantsOfType(Node node, Class<T> targetType, List<T> results,
boolean crossFindBoundaries) {
if (!crossFindBoundaries && node.isFindBoundary()) {
return;
}
int n = node.jjtGetNumChildren();
for (int i = 0; i < n; i++) {
Node child = node.jjtGetChild(i);
if (child.getClass() == targetType) {
results.add((T) child);
}
findDescendantsOfType(child, targetType, results, crossFindBoundaries);
}
}
/**
* {@inheritDoc}
*/
@Override
public <T> List<T> findChildrenOfType(Class<T> targetType) {
List<T> list = new ArrayList<>();
int n = jjtGetNumChildren();
for (int i = 0; i < n; i++) {
Node child = jjtGetChild(i);
if (child.getClass() == targetType) {
list.add((T) child);
}
}
return list;
}
@Override
public boolean isFindBoundary() {
return false;
}
@Override
public Document getAsDocument() {
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.getStringValue());
}
for (Iterator<Node> iter = docNav.getChildAxisIterator(this); iter.hasNext();) {
AbstractNode child = (AbstractNode) iter.next();
child.appendElement(element);
}
}
/**
* {@inheritDoc}
*/
@Override
public <T> T getFirstDescendantOfType(Class<T> descendantType) {
return getFirstDescendantOfType(descendantType, this);
}
/**
* {@inheritDoc}
*/
@Override
public <T> T getFirstChildOfType(Class<T> childType) {
int n = jjtGetNumChildren();
for (int i = 0; i < n; i++) {
Node child = jjtGetChild(i);
if (child.getClass() == childType) {
return (T) child;
}
}
return null;
}
private static <T> T getFirstDescendantOfType(Class<T> descendantType, Node node) {
int n = node.jjtGetNumChildren();
for (int i = 0; i < n; i++) {
Node n1 = node.jjtGetChild(i);
if (n1.getClass() == descendantType) {
return (T) n1;
}
T n2 = getFirstDescendantOfType(descendantType, n1);
if (n2 != null) {
return n2;
}
}
return null;
}
/**
* {@inheritDoc}
*/
@Override
public final <T> boolean hasDescendantOfType(Class<T> type) {
return getFirstDescendantOfType(type) != null;
}
/**
*
* @param types
* @return boolean
*/
public final boolean hasDecendantOfAnyType(Class<?>... types) {
for (Class<?> type : types) {
if (hasDescendantOfType(type)) {
return true;
}
}
return false;
}
/**
* {@inheritDoc}
*/
@Override
@SuppressWarnings("unchecked")
public List<Node> findChildNodesWithXPath(String xpathString) throws JaxenException {
return new BaseXPath(xpathString, new DocumentNavigator()).selectNodes(this);
}
/**
* {@inheritDoc}
*/
@Override
public boolean hasDescendantMatchingXPath(String xpathString) {
try {
return !findChildNodesWithXPath(xpathString).isEmpty();
} catch (JaxenException e) {
throw new RuntimeException("XPath expression " + xpathString + " failed: " + e.getLocalizedMessage(), e);
}
}
/**
* {@inheritDoc}
*/
@Override
public Object getUserData() {
return userData;
}
/**
* {@inheritDoc}
*/
@Override
public void setUserData(Object userData) {
this.userData = userData;
}
public GenericToken jjtGetFirstToken() {
return firstToken;
}
public void jjtSetFirstToken(GenericToken token) {
this.firstToken = token;
}
public GenericToken jjtGetLastToken() {
return lastToken;
}
public void jjtSetLastToken(GenericToken token) {
this.lastToken = token;
}
}