/**
* 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 com.python.pydev.analysis.scopeanalysis;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.python.pydev.core.FullRepIterable;
import org.python.pydev.core.IModule;
import org.python.pydev.core.IPythonNature;
import org.python.pydev.core.IToken;
import org.python.pydev.core.Tuple3;
import org.python.pydev.core.Tuple4;
import org.python.pydev.core.docutils.PySelection;
import org.python.pydev.core.docutils.StringUtils;
import org.python.pydev.core.log.Log;
import org.python.pydev.core.structure.FastStack;
import org.python.pydev.editor.codecompletion.revisited.modules.SourceToken;
import org.python.pydev.editor.codecompletion.revisited.visitors.AbstractVisitor;
import org.python.pydev.parser.jython.SimpleNode;
import org.python.pydev.parser.jython.ast.Assign;
import org.python.pydev.parser.jython.ast.Attribute;
import org.python.pydev.parser.jython.ast.Call;
import org.python.pydev.parser.jython.ast.Import;
import org.python.pydev.parser.jython.ast.ImportFrom;
import org.python.pydev.parser.jython.ast.Name;
import org.python.pydev.parser.jython.ast.NameTok;
import org.python.pydev.parser.jython.ast.aliasType;
import org.python.pydev.parser.visitors.NodeUtils;
import org.python.pydev.parser.visitors.scope.ASTEntry;
import com.aptana.shared_core.structure.Tuple;
import com.python.pydev.analysis.messages.AbstractMessage;
import com.python.pydev.analysis.visitors.Found;
import com.python.pydev.analysis.visitors.GenAndTok;
import com.python.pydev.analysis.visitors.ScopeItems;
/**
* This is almost the same as the ScopeAnalyzer, but it won't find imports that may be related
* nor imports that are generated from the module part of an ImportFrom
*/
public class ScopeAnalyzerVisitorWithoutImports extends AbstractScopeAnalyzerVisitor {
public final static String FOUND_ADDITIONAL_INFO_IN_AST_ENTRY = "FOUND_ADDITIONAL_INFO_IN_AST_ENTRY";
protected String completeNameToFind = "";
protected String nameToFind = "";
/**
* List of tuple with:
*
* - the token found
*
* - the delta to the column that the token we're looking for was found (delta from the currCol)
* negative values mean that it is undefined
*
* - the entry that is the parent of this found
*/
private List<Tuple3<Found, Integer, ASTEntry>> foundOccurrences = new ArrayList<Tuple3<Found, Integer, ASTEntry>>();
private FastStack<ASTEntry> parents; //initialized on demand
/**
* Keeps the variables that are really undefined (we keep them here if there's still a chance that
* what we're looking for is an undefined variable and all in the same scope should also be marked
* as that same undefined).
*/
private List<Found> undefinedFound = new ArrayList<Found>();
/**
* This one is not null only if it is the name we're looking for in the exact position (even if it does
* not have a definition).
*/
private Found hitAsUndefined = null;
private boolean finished = false;
private int currLine;
private int currCol;
/**
* Constructor when we have a PySelection object
* @throws BadLocationException
*/
public ScopeAnalyzerVisitorWithoutImports(IPythonNature nature, String moduleName, IModule current,
IProgressMonitor monitor, PySelection ps) throws BadLocationException {
this(nature, moduleName, current, ps.getDoc(), monitor, ps.getCurrToken().o1, ps.getAbsoluteCursorOffset(), ps
.getActivationTokenAndQual(true));
}
/**
* Base constructor (after data from the PySelection is gotten)
* @throws BadLocationException
*/
protected ScopeAnalyzerVisitorWithoutImports(IPythonNature nature, String moduleName, IModule current,
IDocument document, IProgressMonitor monitor, String pNameToFind, int absoluteCursorOffset,
String[] tokenAndQual) throws BadLocationException {
super(nature, moduleName, current, document, monitor);
if (document != null) {
IRegion region = document.getLineInformationOfOffset(absoluteCursorOffset);
currLine = document.getLineOfOffset(absoluteCursorOffset);
currCol = absoluteCursorOffset - region.getOffset();
}
nameToFind = pNameToFind;
completeNameToFind = tokenAndQual[0] + tokenAndQual[1];
}
@Override
protected void onLastScope(ScopeItems m) {
//not found
for (Found found : probablyNotDefined) {
ASTEntry parent = peekParent();
if (checkFound(found, parent) == null) {
//ok, it was actually not found, so, after marking it as an occurrence, we have to check all
//the others that have the same representation in its scope.
String rep = found.getSingle().generator.getRepresentation();
if (FullRepIterable.containsPart(rep, nameToFind)) {
undefinedFound.add(found);
}
} else {
hitAsUndefined = found;
}
}
}
@Override
public void onAddUnusedMessage(SimpleNode node, Found found) {
}
@Override
public void onAddReimportMessage(Found newFound) {
}
@Override
public void onAddUnresolvedImport(IToken token) {
}
@Override
protected void onAfterAddToNamesToIgnore(ScopeItems currScopeItems, Tuple<IToken, Found> tup) {
if (tup.o1 instanceof SourceToken) {
checkFound(tup.o2, peekParent());
}
}
@Override
protected Tuple<IToken, Found> findInNamesToIgnore(String rep, IToken token) {
com.aptana.shared_core.structure.Tuple<IToken, Found> found = scope.findInNamesToIgnore(rep);
if (found != null) {
found.o2.getSingle().references.add(token);
checkToken(found.o2, token, peekParent());
}
return found;
}
@Override
protected void onFoundUnresolvedImportPart(IToken token, String rep, Found foundAs) {
onAddUndefinedMessage(token, foundAs);
}
@Override
protected void onAddUndefinedVarInImportMessage(IToken token, Found foundAs) {
onAddUndefinedMessage(token, foundAs);
}
@Override
protected void onAddAssignmentToBuiltinMessage(IToken token, String representation) {
}
@Override
protected void onAddToProbablyNotDefined(IToken token, Found foundForProbablyNotDefined) {
super.onAddToProbablyNotDefined(token, foundForProbablyNotDefined);
onAddUndefinedMessage(token, foundForProbablyNotDefined);
}
@Override
protected void onNotDefinedFoundLater(Found foundInProbablyNotDefined, Found laterFound) {
super.onNotDefinedFoundLater(foundInProbablyNotDefined, laterFound);
if (hitAsUndefined == foundInProbablyNotDefined) {
//we have a 'late' match as a foundInProbablyNotDefined, so, remove the hit as undefined
hitAsUndefined = null;
}
//and add those generators -- task: Scope Analysis should be able to get all the class references
Tuple3<Found, Integer, ASTEntry> tup = new Tuple3<Found, Integer, ASTEntry>(laterFound, -1, peekParent());
laterFound.addGeneratorsFromFound(foundInProbablyNotDefined);
addFoundOccurrence(tup);
}
@Override
protected void onAddUndefinedMessage(IToken token, Found found) {
ASTEntry parent = peekParent();
if (checkFound(found, parent) == null) {
//ok, it was actually not found, so, after marking it as an occurrence, we have to check all
//the others that have the same representation in its scope.
if (token.getRepresentation().equals(nameToFind)) {
undefinedFound.add(found);
}
} else {
hitAsUndefined = found;
}
}
/**
* Will peek the parent if the node is not null (otherwise will return null)
*/
protected ASTEntry popParent(SimpleNode node) {
return parents.pop();
}
/**
* If the 'parents' stack is higher than 0, peek it (may return null)
*/
protected ASTEntry peekParent() {
ASTEntry parent = null;
if (parents.size() > 0) {
parent = parents.peek();
}
return parent;
}
/**
* When we start the scope, we have to put an entry in the parents.
*/
@Override
protected void onAfterStartScope(int newScopeType, SimpleNode node) {
if (parents == null) {
parents = new FastStack<ASTEntry>(10);
}
if (parents.size() == 0) {
parents.push(new ASTEntry(null, node));
} else {
parents.push(new ASTEntry(parents.peek(), node));
}
}
@Override
protected void onBeforeEndScope(SimpleNode node) {
}
@Override
protected void onAfterVisitAssign(Assign node) {
}
/**
* If it is still not finished we'll have to finish it (end the last scope).
*/
protected void checkFinished() {
if (!finished) {
finished = true;
endScope(null); //finish the last scope
}
}
@Override
protected void onAfterEndScope(SimpleNode node, ScopeItems m) {
ASTEntry parent = popParent(node);
if (hitAsUndefined == null) {
for (String rep : new FullRepIterable(this.completeNameToFind, true)) {
List<Found> foundItems = m.getAll(rep);
for (Found found : foundItems) {
if (checkFound(found, parent) != null) {
return;
}
}
}
} else { //(hitAsUndefined != null)
String foundRep = hitAsUndefined.getSingle().generator.getRepresentation();
if (foundRep.indexOf('.') == -1 || FullRepIterable.containsPart(foundRep, nameToFind)) {
//now, there's a catch here, if we found it as an attribute,
//we cannot get the locals
for (Found f : this.undefinedFound) {
if (f.getSingle().generator.getRepresentation().startsWith(foundRep)) {
if (foundOccurrences.size() == 1) {
Tuple3<Found, Integer, ASTEntry> hit = foundOccurrences.get(0);
Tuple3<Found, Integer, ASTEntry> foundOccurrence = new Tuple3<Found, Integer, ASTEntry>(f,
hit.o2, hit.o3);
addFoundOccurrence(foundOccurrence);
}
}
}
}
}
}
/**
* Checks to see if the given found is actually a match to the current position.
* @return the same Found passed on the parameter if it is a match (and null otherwise)
*/
protected Found checkFound(Found found, ASTEntry parent) {
if (found == null) {
return null;
}
List<GenAndTok> all = found.getAll();
try {
for (GenAndTok gen : all) {
for (IToken tok2 : gen.getAllTokens()) {
if (checkToken(found, tok2, parent)) {
return found; //ok, found it
}
}
}
} catch (Exception e) {
Log.log(e);
}
return null;
}
protected boolean checkToken(Found found, IToken generator, ASTEntry parent) {
int startLine = AbstractMessage.getStartLine(generator, this.document) - 1;
int endLine = AbstractMessage.getEndLine(generator, this.document, false) - 1;
String rep = generator.getRepresentation();
int startCol = AbstractMessage.getStartCol(generator, this.document, rep, true) - 1;
int endCol = AbstractMessage.getEndCol(generator, this.document, rep, false) - 1;
if (currLine >= startLine && currLine <= endLine && currCol >= startCol && currCol <= endCol) {
int colDelta = 0;
if (currLine == startLine || currLine == endLine) {
colDelta = currCol - startCol;
}
Tuple3<Found, Integer, ASTEntry> foundOccurrence = new Tuple3<Found, Integer, ASTEntry>(found, colDelta,
parent);
//ok, it's a valid occurrence, so, let's add it.
addFoundOccurrence(foundOccurrence);
return true;
}
return false;
}
/**
* Used to add an occurrence to the found occurrences.
* @param foundOccurrence
*/
private void addFoundOccurrence(Tuple3<Found, Integer, ASTEntry> foundOccurrence) {
foundOccurrences.add(foundOccurrence);
}
/**
* @return all the token occurrences
*/
public List<IToken> getTokenOccurrences() {
List<IToken> ret = new ArrayList<IToken>();
List<ASTEntry> entryOccurrences = getEntryOccurrences();
for (ASTEntry entry : entryOccurrences) {
ret.add(AbstractVisitor.makeToken(entry.node, moduleName));
}
return ret;
}
/**
* We get the occurrences as tokens for the name we're looking for. Note that the complete name (may be a dotted name)
* we're looking for may not be equal to the 'partial' name.
*
* This can happen when we're looking for some import such as os.path, and are looking just for the 'path' part.
* So, when this happens, the return is analyzed and only returns names as the one we're looking for (with
* the correct line and col positions).
*/
public List<ASTEntry> getEntryOccurrences() {
checkFinished();
Set<Tuple3<String, Integer, Integer>> s = new HashSet<Tuple3<String, Integer, Integer>>();
ArrayList<Tuple4<IToken, Integer, ASTEntry, Found>> complete = getCompleteTokenOccurrences();
ArrayList<ASTEntry> ret = new ArrayList<ASTEntry>();
for (Tuple4<IToken, Integer, ASTEntry, Found> tup : complete) {
IToken token = tup.o1;
if (!(token instanceof SourceToken)) { // we want only the source tokens for this module
continue;
}
//if it is different, we have to make partial names
SourceToken sourceToken = (SourceToken) tup.o1;
SimpleNode ast = (sourceToken).getAst();
String representation = null;
if (ast instanceof ImportFrom) {
ImportFrom f = (ImportFrom) ast;
//f.names may be empty if it is a wild import
for (aliasType t : f.names) {
NameTok importName = NodeUtils.getNameForAlias(t);
String importRep = NodeUtils.getFullRepresentationString(importName);
if (importRep.equals(nameToFind)) {
ast = importName;
representation = importRep;
break;
}
}
} else if (ast instanceof Import) {
representation = NodeUtils.getFullRepresentationString(ast);
Import f = (Import) ast;
NameTok importName = NodeUtils.getNameForRep(f.names, representation);
if (importName != null) {
ast = importName;
}
} else {
representation = NodeUtils.getFullRepresentationString(ast);
}
if (representation == null) {
//do nothing
//can happen on wild imports
} else if (nameToFind.equals(representation)) {
if (ast instanceof Attribute) {
//it can happen, as we won't go up to the part of the actual call (if there's one).
ast = NodeUtils.getAttributeParts((Attribute) ast).get(0);
ASTEntry entry = new ASTEntry(tup.o3, ast);
entry.setAdditionalInfo(FOUND_ADDITIONAL_INFO_IN_AST_ENTRY, tup.o4);
ret.add(entry);
} else {
ASTEntry entry = new ASTEntry(tup.o3, ast);
entry.setAdditionalInfo(FOUND_ADDITIONAL_INFO_IN_AST_ENTRY, tup.o4);
ret.add(entry);
}
} else if (FullRepIterable.containsPart(representation, nameToFind)) {
Name nameAst = new Name(nameToFind, Name.Store, false);
List<String> strings = StringUtils.dotSplit(representation);
int plus = 0;
for (String string : strings) {
if (string.equals(nameToFind) && (plus + nameToFind.length() >= tup.o2)) {
break;
}
plus += string.length() + 1; //len + dot
}
nameAst.beginColumn = AbstractMessage.getStartCol(token, document) + plus;
nameAst.beginLine = AbstractMessage.getStartLine(token, document);
Tuple3<String, Integer, Integer> t = new Tuple3<String, Integer, Integer>(nameToFind,
nameAst.beginColumn, nameAst.beginLine);
if (!s.contains(t)) {
s.add(t);
ASTEntry entry = new ASTEntry(tup.o3, nameAst);
entry.setAdditionalInfo(FOUND_ADDITIONAL_INFO_IN_AST_ENTRY, tup.o4);
ret.add(entry);
}
}
}
return ret;
}
/**
* @return all the occurrences found in a 'complete' way (dotted name).
* The ASTEtries are decorated with the Found here...
*/
@SuppressWarnings("unchecked")
protected ArrayList<Tuple4<IToken, Integer, ASTEntry, Found>> getCompleteTokenOccurrences() {
//that's because we don't want duplicates
Set<IToken> f = new HashSet<IToken>();
ArrayList<Tuple4<IToken, Integer, ASTEntry, Found>> ret = new ArrayList();
for (Tuple3<Found, Integer, ASTEntry> found : foundOccurrences) {
List<GenAndTok> all = found.o1.getAll();
for (GenAndTok tok : all) {
Tuple4<IToken, Integer, ASTEntry, Found> tup4 = new Tuple4(tok.generator, found.o2, found.o3, found.o1);
if (!f.contains(tok.generator)) {
f.add(tok.generator);
ret.add(tup4);
}
for (IToken t : tok.references) {
tup4 = new Tuple4(t, found.o2, found.o3, found.o1);
if (!f.contains(t)) {
f.add(t);
ret.add(tup4);
}
}
}
onGetCompleteTokenOccurrences(found, f, ret);
}
return ret;
}
/**
* To be overriden
* @param ret
*/
protected void onGetCompleteTokenOccurrences(Tuple3<Found, Integer, ASTEntry> found, Set<IToken> f,
ArrayList<Tuple4<IToken, Integer, ASTEntry, Found>> ret) {
}
}