View Javadoc
1   /**
2    * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3    */
4   package net.sourceforge.pmd.lang.ast;
5   
6   import java.util.ArrayList;
7   import java.util.Iterator;
8   import java.util.List;
9   
10  import javax.xml.parsers.DocumentBuilder;
11  import javax.xml.parsers.DocumentBuilderFactory;
12  import javax.xml.parsers.ParserConfigurationException;
13  
14  import net.sourceforge.pmd.lang.ast.xpath.Attribute;
15  import net.sourceforge.pmd.lang.ast.xpath.DocumentNavigator;
16  import net.sourceforge.pmd.lang.dfa.DataFlowNode;
17  import net.sourceforge.pmd.lang.ast.GenericToken;
18  
19  import org.jaxen.BaseXPath;
20  import org.jaxen.JaxenException;
21  import org.w3c.dom.Document;
22  import org.w3c.dom.Element;
23  
24  public abstract class AbstractNode implements Node {
25  
26      protected Node parent;
27      protected Node[] children;
28      protected int childIndex;
29      protected int id;
30  
31      private String image;
32      protected int beginLine = -1;
33      protected int endLine;
34      protected int beginColumn = -1;
35      protected int endColumn;
36      private DataFlowNode dataFlowNode;
37      private Object userData;
38  
39      public AbstractNode(int id) {
40      	this.id = id;
41      }
42  
43      public AbstractNode(int id, int theBeginLine, int theEndLine, int theBeginColumn, int theEndColumn) {
44      	this(id);
45      	
46      	beginLine = theBeginLine;
47      	endLine = theEndLine;
48      	beginColumn = theBeginColumn;
49      	endColumn = theEndColumn;
50      }
51      
52      public boolean isSingleLine() {
53      	return beginLine == endLine;
54      }
55      
56      public void jjtOpen() {
57  	// to be overridden by subclasses
58      }
59  
60      public void jjtClose() {
61  	// to be overridden by subclasses
62      }
63  
64      public void jjtSetParent(Node parent) {
65      	this.parent = parent;
66      }
67  
68      public Node jjtGetParent() {
69      	return parent;
70      }
71  
72      public void jjtAddChild(Node child, int index) {
73  		if (children == null) {
74  		    children = new Node[index + 1];
75  		} else if (index >= children.length) {
76  		    Node[] newChildren = new Node[index + 1];
77  		    System.arraycopy(children, 0, newChildren, 0, children.length);
78  		    children = newChildren;
79  		}
80  		children[index] = child;
81  		child.jjtSetChildIndex(index);
82      }
83      public void jjtSetChildIndex(int index) {
84          childIndex = index;
85      }
86      public int jjtGetChildIndex() {
87          return childIndex;
88      }
89  
90      public Node jjtGetChild(int index) {
91      	return children[index];
92      }
93  
94      public int jjtGetNumChildren() {
95      	return children == null ? 0 : children.length;
96      }
97  
98      public int jjtGetId() {
99      	return id;
100     }
101 
102     /**
103      * Subclasses should implement this method to return a name usable with
104      * XPathRule for evaluating Element Names.
105      */
106     @Override
107     public abstract String toString();
108 
109     public String getImage() {
110     	return image;
111     }
112 
113     public void setImage(String image) {
114 	this.image = image;
115     }
116 
117     public boolean hasImageEqualTo(String image) {
118 	return this.getImage() != null && this.getImage().equals(image);
119     }
120 
121     public int getBeginLine() {
122 	return beginLine;
123     }
124 
125     public void testingOnly__setBeginLine(int i) {
126 	this.beginLine = i;
127     }
128 
129     public int getBeginColumn() {
130 	if (beginColumn != -1) {
131 	    return beginColumn;
132 	} else {
133 	    if (children != null && children.length > 0) {
134 		return children[0].getBeginColumn();
135 	    } else {
136 		throw new RuntimeException("Unable to determine beginning line of Node.");
137 	    }
138 	}
139     }
140 
141     public void testingOnly__setBeginColumn(int i) {
142 	this.beginColumn = i;
143     }
144 
145     public int getEndLine() {
146 	return endLine;
147     }
148 
149     public void testingOnly__setEndLine(int i) {
150 	this.endLine = i;
151     }
152 
153     public int getEndColumn() {
154 	return endColumn;
155     }
156 
157     public void testingOnly__setEndColumn(int i) {
158 	this.endColumn = i;
159     }
160 
161     public DataFlowNode getDataFlowNode() {
162 	if (this.dataFlowNode == null) {
163 	    if (this.parent != null) {
164 		return parent.getDataFlowNode();
165 	    }
166 	    return null; //TODO wise?
167 	}
168 	return dataFlowNode;
169     }
170 
171     public void setDataFlowNode(DataFlowNode dataFlowNode) {
172 	this.dataFlowNode = dataFlowNode;
173     }
174 
175     /**
176      * Returns the n-th parent or null if there are not <code>n</code> ancestors
177      *
178      * @param n how many ancestors to iterate over.
179      * @return the n-th parent or null.
180      * @throws IllegalArgumentException if <code>n</code> is not positive.
181      */
182     public Node getNthParent(int n) {
183         if (n <= 0) {
184             throw new IllegalArgumentException();
185         }
186         Node result = this.jjtGetParent();
187         for (int i = 1; i < n; i++) {
188             if (result == null) {
189                 return null;
190             }
191             result = result.jjtGetParent();
192         }
193         return result;
194     }
195 
196     /**
197      * Traverses up the tree to find the first parent instance of type parentType
198      *
199      * @param parentType class which you want to find.
200      * @return Node of type parentType.  Returns null if none found.
201      */
202     public <T> T getFirstParentOfType(Class<T> parentType) {
203 	Node parentNode = jjtGetParent();
204 	while (parentNode != null && parentNode.getClass() != parentType) {
205 	    parentNode = parentNode.jjtGetParent();
206 	}
207 	return (T) parentNode;
208     }
209 
210     /**
211      * Traverses up the tree to find all of the parent instances of type parentType
212      *
213      * @param parentType classes which you want to find.
214      * @return List of parentType instances found.
215      */
216     public <T> List<T> getParentsOfType(Class<T> parentType) {
217 	List<T> parents = new ArrayList<>();
218 	Node parentNode = jjtGetParent();
219 	while (parentNode != null) {
220 	    if (parentNode.getClass() == parentType) {
221 		parents.add((T) parentNode);
222 	    }
223 	    parentNode = parentNode.jjtGetParent();
224 	}
225 	return parents;
226     }
227 
228     /**
229      * {@inheritDoc}
230      */
231     public <T> List<T> findDescendantsOfType(Class<T> targetType) {
232 	List<T> list = new ArrayList<>();
233 	findDescendantsOfType(this, targetType, list, true);
234 	return list;
235     }
236 
237     /**
238      * {@inheritDoc}
239      */
240     public <T> void findDescendantsOfType(Class<T> targetType, List<T> results, boolean crossBoundaries) {
241 	findDescendantsOfType(this, targetType, results, crossBoundaries);
242     }
243 
244     private static <T> void findDescendantsOfType(Node node, Class<T> targetType, List<T> results,
245 	    boolean crossFindBoundaries) {
246 
247 	if (!crossFindBoundaries && node.isFindBoundary()) {
248 	    return;
249 	}
250 
251 	int n = node.jjtGetNumChildren();
252 	for (int i = 0; i < n; i++) {
253 	    Node child = node.jjtGetChild(i);
254 	    if (child.getClass() == targetType) {
255 		results.add((T) child);
256 	    }
257 
258 	    findDescendantsOfType(child, targetType, results, crossFindBoundaries);
259 	}
260     }
261 
262     /**
263      * {@inheritDoc}
264      */
265     public <T> List<T> findChildrenOfType(Class<T> targetType) {
266 	List<T> list = new ArrayList<>();
267 	int n = jjtGetNumChildren();
268 	for (int i = 0; i < n; i++) {
269 	    Node child = jjtGetChild(i);
270 	    if (child.getClass() == targetType) {
271 		list.add((T) child);
272 	    }
273 	}
274 	return list;
275     }
276 
277     public boolean isFindBoundary() {
278 	return false;
279     }
280 
281     public Document getAsDocument() {
282 	try {
283 	    DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
284 	    DocumentBuilder db = dbf.newDocumentBuilder();
285 	    Document document = db.newDocument();
286 	    appendElement(document);
287 	    return document;
288 	} catch (ParserConfigurationException pce) {
289 	    throw new RuntimeException(pce);
290 	}
291     }
292 
293     protected void appendElement(org.w3c.dom.Node parentNode) {
294 	DocumentNavigator docNav = new DocumentNavigator();
295 	Document ownerDocument = parentNode.getOwnerDocument();
296 	if (ownerDocument == null) {
297 	    //If the parentNode is a Document itself, it's ownerDocument is null
298 	    ownerDocument = (Document) parentNode;
299 	}
300 	String elementName = docNav.getElementName(this);
301 	Element element = ownerDocument.createElement(elementName);
302 	parentNode.appendChild(element);
303 	for (Iterator<Attribute> iter = docNav.getAttributeAxisIterator(this); iter.hasNext();) {
304 	    Attribute attr = iter.next();
305 	    element.setAttribute(attr.getName(), attr.getStringValue());
306 	}
307 	for (Iterator<Node> iter = docNav.getChildAxisIterator(this); iter.hasNext();) {
308 	    AbstractNode child = (AbstractNode) iter.next();
309 	    child.appendElement(element);
310 	}
311     }
312 
313     /**
314      * {@inheritDoc}
315      */
316     public <T> T getFirstDescendantOfType(Class<T> descendantType) {
317 	return getFirstDescendantOfType(descendantType, this);
318     }
319 
320     /**
321      * {@inheritDoc}
322      */
323     public <T> T getFirstChildOfType(Class<T> childType) {
324 	int n = jjtGetNumChildren();
325 	for (int i = 0; i < n; i++) {
326 	    Node child = jjtGetChild(i);
327 	    if (child.getClass() == childType) {
328 		return (T) child;
329 	    }
330 	}
331 	return null;
332     }
333 
334     private static <T> T getFirstDescendantOfType(Class<T> descendantType, Node node) {
335 	int n = node.jjtGetNumChildren();
336 	for (int i = 0; i < n; i++) {
337 	    Node n1 = node.jjtGetChild(i);
338 	    if (n1.getClass() == descendantType) {
339 		return (T) n1;
340 	    }
341 	    T n2 = getFirstDescendantOfType(descendantType, n1);
342 	    if (n2 != null) {
343 		return n2;
344 	    }
345 	}
346 	return null;
347     }
348 
349     /**
350      * {@inheritDoc}
351      */
352     public final <T> boolean hasDescendantOfType(Class<T> type) {
353 	return getFirstDescendantOfType(type) != null;
354     }
355 
356     /**
357      * 
358      * @param types
359      * @return boolean
360      */
361     public final boolean hasDecendantOfAnyType(Class<?>... types) {
362     	for (Class<?> type : types) {
363     		if (hasDescendantOfType(type)) {
364     		    return true;
365     		}
366     	}
367     	return false;
368     }
369     
370     /**
371      * {@inheritDoc}
372      */
373     @SuppressWarnings("unchecked")
374     public List<Node> findChildNodesWithXPath(String xpathString) throws JaxenException {
375         return new BaseXPath(xpathString, new DocumentNavigator()).selectNodes(this);
376     }
377 
378     /**
379      * {@inheritDoc}
380      */
381     public boolean hasDescendantMatchingXPath(String xpathString) {
382         try {
383             return !findChildNodesWithXPath(xpathString).isEmpty();
384         } catch (JaxenException e) {
385             throw new RuntimeException("XPath expression " + xpathString + " failed: " + e.getLocalizedMessage(), e);
386         }
387     }
388 
389     /**
390      * {@inheritDoc}
391      */
392     public Object getUserData() {
393         return userData;
394     }
395 
396     /**
397      * {@inheritDoc}
398      */
399     public void setUserData(Object userData) {
400         this.userData = userData;
401     }
402     
403     protected GenericToken firstToken, lastToken;
404 
405 	public GenericToken jjtGetFirstToken() {
406 		return firstToken;
407 	}
408 
409 	public void jjtSetFirstToken(GenericToken token) {
410 		this.firstToken = token;
411 	}
412 
413 	public GenericToken jjtGetLastToken() {
414 		return lastToken;
415 	}
416 
417 	public void jjtSetLastToken(GenericToken token) {
418 		this.lastToken = token;
419 	}
420 }