/**
* Copyright (c) 2005-2011 by Appcelerator, Inc. All Rights Reserved.
* Licensed under the terms of the Eclipse Public License (EPL).
* Please see the license.txt included with this distribution for details.
* Any modifications to this file must keep this entire header intact.
*/
package org.python.pydev.parser.visitors.scope;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.python.pydev.core.structure.FastStack;
import org.python.pydev.parser.jython.ISpecialStr;
import org.python.pydev.parser.jython.SimpleNode;
import org.python.pydev.parser.jython.ast.ClassDef;
import org.python.pydev.parser.jython.ast.FunctionDef;
import org.python.pydev.parser.jython.ast.Module;
import org.python.pydev.parser.jython.ast.VisitorBase;
import org.python.pydev.parser.jython.ast.commentType;
import org.python.pydev.parser.visitors.NodeUtils;
/**
* Iterator base. Keeps track of the current scope (method or class).
*
* This object (and subclasses) are 'disposable', meaning that they should only do 1 visit and after that
* only the methods to get the classes should be called.
*
* @author fabioz
*/
public abstract class EasyAstIteratorBase extends VisitorBase {
protected List<ASTEntry> nodes = new ArrayList<ASTEntry>();
protected final FastStack<SimpleNode> stack = new FastStack<SimpleNode>(20);
protected final FastStack<ASTEntry> parents = new FastStack<ASTEntry>(10);
protected SimpleNode lastVisited;
protected ASTEntry lastDefVisited; //ClassDef or FunctionDef
private int higherLine = -1;
/**
* @see org.python.pydev.parser.jython.ast.VisitorBase#unhandled_node(org.python.pydev.parser.jython.SimpleNode)
*/
protected Object unhandled_node(SimpleNode node) throws Exception {
this.lastVisited = node;
int l = NodeUtils.getLineEnd(this.lastVisited);
if (l > higherLine) {
higherLine = l;
}
if (node.specialsAfter != null) {
for (Object o : node.specialsAfter) {
if (o instanceof ISpecialStr) {
ISpecialStr str = (ISpecialStr) o;
if (str.getBeginLine() > higherLine) {
higherLine = str.getBeginLine();
}
}
}
}
//the lastDefVisited is only kept if it's really the last definition visited (if there's a node
//visited after the last def, it's not kept).
if (this.lastDefVisited != null) {
this.lastDefVisited = null;
}
return null;
}
/**
* @see org.python.pydev.parser.jython.ast.VisitorBase#traverse(org.python.pydev.parser.jython.SimpleNode)
*/
public void traverse(SimpleNode node) throws Exception {
if (node instanceof FunctionDef) {
traverse((FunctionDef) node); //the order we traverse it is different
} else {
node.traverse(this);
}
}
/**
* @param node
* @return
*/
protected ASTEntry before(SimpleNode node) {
ASTEntry entry;
entry = createEntry();
entry.node = node;
doAddNode(entry);
stack.push(node);
return entry;
}
/**
* @param entry the entry we're adding. The default implementation adds
* the node to the returned nodes (flattened list)
*/
protected void doAddNode(ASTEntry entry) {
nodes.add(entry);
}
/**
* @param entry
*/
protected void after(ASTEntry entry) {
stack.pop();
//only set the end line if it was still not set
if (entry.endLine == 0) {
int lineEnd = NodeUtils.getLineEnd(lastVisited);
if (lineEnd > higherLine) {
entry.endLine = lineEnd;
} else {
entry.endLine = higherLine;
}
//also make comments found after the node a part of its context.
List<Object> s = entry.node.specialsAfter;
if (s != null) {
for (Object o : s) {
if (o instanceof commentType) {
commentType comment = (commentType) o;
if (comment.beginLine > entry.endLine) {
entry.endLine = comment.beginLine;
}
}
}
}
}
this.lastDefVisited = entry;
}
@Override
public Object visitModule(Module node) throws Exception {
Object ret = super.visitModule(node);
//after visiting the module, let's put the comments to the scope of the last definition found
//if there were no other statements out of the class scope.
int size = this.nodes.size();
if (size > 0) {
int i = -1;
if (node.specialsAfter != null) {
for (Object o : node.specialsAfter) {
if (o instanceof commentType) {
commentType type = (commentType) o;
if (type.beginLine > i) {
i = type.beginLine;
}
}
}
}
if (i != -1 && this.lastDefVisited != null) {
if (lastDefVisited.endLine < i) {
lastDefVisited.endLine = i;
}
}
}
return ret;
}
/**
* @param node the node we're adding in an 'atomic' way
* @return the ast entry that was created in this 'atomic' add
*/
protected ASTEntry atomic(SimpleNode node) {
ASTEntry entry;
entry = createEntry();
entry.node = node;
entry.endLine = NodeUtils.getLineEnd(node);
doAddNode(entry);
return entry;
}
/**
* @return the created entry (with its parent set)
*/
protected ASTEntry createEntry() {
ASTEntry entry;
if (parents.size() > 0) {
entry = new ASTEntry(parents.peek());
} else {
entry = new ASTEntry(null);
}
return entry;
}
/**
* @see org.python.pydev.parser.jython.ast.VisitorBase#visitClassDef(org.python.pydev.parser.jython.ast.ClassDef)
*/
public Object visitClassDef(ClassDef node) throws Exception {
ASTEntry entry = before(node);
parents.push(entry);
traverse(node);
after(entry);
parents.pop();
return null;
}
protected boolean isInGlobal() {
Iterator<SimpleNode> iterator = stack.iterator();
while (iterator.hasNext()) {
SimpleNode node = (SimpleNode) iterator.next();
if (node instanceof ClassDef || node instanceof FunctionDef) {
return false;
}
}
return true;
}
/**
* @return whether we are in a class or method definition scope
*/
protected boolean isInClassMethodDecl() {
Iterator<SimpleNode> iterator = stack.iterator();
while (iterator.hasNext()) {
SimpleNode node = (SimpleNode) iterator.next();
if (node instanceof ClassDef) {
break;
}
}
while (iterator.hasNext()) {
SimpleNode node = (SimpleNode) iterator.next();
if (node instanceof FunctionDef) {
return true;
}
}
return false;
}
/**
* @return whether we are in a class definition scope
*/
protected boolean isInClassDecl() {
if (stack.size() == 0) {
return false;
}
SimpleNode last = (SimpleNode) stack.peek();
if (last instanceof ClassDef) {
return true;
}
return false;
}
/**
* @see org.python.pydev.parser.jython.ast.VisitorBase#visitFunctionDef(org.python.pydev.parser.jython.ast.FunctionDef)
*/
public Object visitFunctionDef(FunctionDef node) throws Exception {
ASTEntry entry = before(node);
parents.push(entry);
traverse(node);
parents.pop();
after(entry);
return null;
}
public void traverse(FunctionDef node) throws Exception {
if (node.decs != null) {
for (int i = 0; i < node.decs.length; i++) {
if (node.decs[i] != null)
node.decs[i].accept(this);
}
}
if (node.name != null)
node.name.accept(this);
if (node.args != null)
node.args.accept(this);
if (node.body != null) {
for (int i = 0; i < node.body.length; i++) {
if (node.body[i] != null)
node.body[i].accept(this);
}
}
}
/**
* @return and iterator that passes through all the nodes
*/
public Iterator<ASTEntry> getIterator() {
return nodes.iterator();
}
/**
* @return an iterator for all the classes definitions
*/
public Iterator<ASTEntry> getClassesIterator() {
return getIterator(ClassDef.class);
}
/**
* @return a list with all the class and method definitions
*/
public List<ASTEntry> getClassesAndMethodsList() {
return getAsList(new Class[] { ClassDef.class, FunctionDef.class });
}
/**
* @param iter this is the iterator we want to get as a list
* @return a list with the elements of the iterator
*/
protected List<ASTEntry> getIteratorAsList(Iterator<ASTEntry> iter) {
ArrayList<ASTEntry> list = new ArrayList<ASTEntry>();
while (iter.hasNext()) {
list.add(iter.next());
}
return list;
}
/**
* @return an iterator for class and method definitions
*/
public Iterator<ASTEntry> getClassesAndMethodsIterator() {
return getIterator(new Class[] { ClassDef.class, FunctionDef.class });
}
/**
* @return an iterator for method definitions
*/
public Iterator<ASTEntry> getMethodsIterator() {
return getIterator(new Class[] { FunctionDef.class });
}
/**
* @see EasyASTIteratorVisitor#getIterator(Class[])
*/
public Iterator<ASTEntry> getIterator(Class class_) {
return getIterator(new Class[] { class_ });
}
public List<ASTEntry> getAsList(Class... classes) {
List<ASTEntry> newList = new ArrayList<ASTEntry>();
for (Iterator<ASTEntry> iter = nodes.iterator(); iter.hasNext();) {
ASTEntry entry = (ASTEntry) iter.next();
if (isFromClass(entry.node, classes)) {
newList.add(entry);
}
}
return newList;
}
public List<ASTEntry> getAsList(Class class_) {
return getAsList(new Class[] { class_ });
}
/**
* @param classes the classes we are searching for
* @return an iterator with nodes found from the passed classes
*/
public Iterator<ASTEntry> getIterator(Class... classes) {
return getAsList(classes).iterator();
}
/**
* @return an iterator that will pass through Name and NameTok tokens
*/
public Iterator<ASTEntry> getNamesIterator() {
return new NameIterator(nodes);
}
public Iterator<ASTEntry> getOutline() {
return new OutlineIterator(nodes);
}
public List<ASTEntry> getAll() {
return nodes;
}
/**
* @return an iterator that will pass all the nodes that were added in this visitor
*/
public Iterator<ASTEntry> getAllIterator() {
return nodes.iterator();
}
/**
* @param node this is the node we are analyzing
* @param classes this are the classes we are looking for
* @return true if the node is from one of the passed classes (may be some subclass too)
*/
@SuppressWarnings("unchecked")
protected boolean isFromClass(SimpleNode node, Class[] classes) {
Class class1 = node.getClass();
for (int i = 0; i < classes.length; i++) {
if (class1.isAssignableFrom(classes[i])) {
return true;
}
}
return false;
}
}