/**
* Copyright (c) 2005-2013 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.
*/
/*
* Created on Feb 2, 2005
*
* @author Fabio Zadrozny
*/
package org.python.pydev.editor.codecompletion.revisited;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import org.eclipse.core.runtime.Assert;
import org.python.pydev.core.ICompletionCache;
import org.python.pydev.core.ICompletionState;
import org.python.pydev.core.IDefinition;
import org.python.pydev.core.IModule;
import org.python.pydev.core.IPythonNature;
import org.python.pydev.core.IToken;
import org.python.pydev.core.structure.CompletionRecursionException;
import org.python.pydev.editor.codecompletion.PyCodeCompletionPreferencesPage;
import org.python.pydev.editor.codecompletion.revisited.visitors.Definition;
import org.python.pydev.shared_core.SharedCorePlugin;
import org.python.pydev.shared_core.structure.Tuple3;
/**
* @author Fabio Zadrozny
*/
public final class CompletionState implements ICompletionState {
private String activationToken;
private int line = -1;
private int col = -1;
private IPythonNature nature;
private String qualifier;
private int levelGetCompletionsUnpackingObject = 0;
private final Memo<String> memory = new Memo<String>();
private final Memo<Definition> definitionMemory = new Memo<Definition>();
private final Memo<IModule> wildImportMemory = new Memo<IModule>();
private final Memo<String> importedModsCalled = new Memo<String>();
private final Memo<String> findMemory = new Memo<String>();
private final Memo<String> resolveImportMemory = new Memo<String>();
private final Memo<String> findDefinitionMemory = new Memo<String>();
private final Memo<String> findLocalDefinedDefinitionMemory = new Memo<String>();
private Stack<Memo<IToken>> findResolveImportMemory = new Stack<Memo<IToken>>();
private final Memo<String> findModuleCompletionsMemory = new Memo<String>();
private final Memo<String> findSourceFromCompiledMemory = new Memo<String>(1); //max is 1 for this one!
private boolean builtinsGotten = false;
private boolean localImportsGotten = false;
private boolean isInCalltip = false;
private int lookingForInstance = LOOKING_FOR_INSTANCE_UNDEFINED;
private List<IToken> tokenImportedModules;
private ICompletionCache completionCache;
private String fullActivationToken;
private long initialMillis = 0;
private long maxMillisToComplete;
@Override
public ICompletionState getCopy() {
return new CompletionStateWrapper(this);
}
@Override
public ICompletionState getCopyForResolveImportWithActTok(String actTok) {
CompletionState state = (CompletionState) CompletionStateFactory.getEmptyCompletionState(actTok, this.nature,
this.completionCache);
state.nature = nature;
state.findResolveImportMemory = findResolveImportMemory;
return state;
}
/**
* this is a class that can act as a memo and check if something is defined more than 'n' times
*
* @author Fabio Zadrozny
*/
private static class Memo<E> {
private int max;
public Memo() {
this.max = MAX_NUMBER_OF_OCURRENCES;
}
public Memo(int max) {
this.max = max;
}
/**
* if more than this number of ocurrences is found, we are in a recursion
*/
private static final int MAX_NUMBER_OF_OCURRENCES = 5;
public Map<IModule, Map<E, Integer>> memo = new HashMap<IModule, Map<E, Integer>>();
public boolean isInRecursion(IModule caller, E def) {
Map<E, Integer> val;
boolean occuredMoreThanMax = false;
if (!memo.containsKey(caller)) {
//still does not exist, let's create the structure...
val = new HashMap<E, Integer>();
memo.put(caller, val);
} else {
val = memo.get(caller);
if (val.containsKey(def)) { //may be a recursion
Integer numberOfOccurences = val.get(def);
//should never be null...
if (numberOfOccurences > max) {
occuredMoreThanMax = true; //ok, we are recursing...
}
}
}
//let's raise the number of ocurrences anyway
Integer numberOfOccurences = val.get(def);
if (numberOfOccurences == null) {
val.put(def, 1); //this is the first ocurrence
} else {
val.put(def, numberOfOccurences + 1);
}
return occuredMoreThanMax;
}
}
/**
* @param line2 starting at 0
* @param col2 starting at 0
* @param token
* @param qual
* @param nature2
*/
public CompletionState(int line2, int col2, String token, IPythonNature nature2, String qualifier) {
this(line2, col2, token, nature2, qualifier, new CompletionCache());
}
/**
* @param line2 starting at 0
* @param col2 starting at 0
* @param token
* @param qual
* @param nature2
*/
public CompletionState(int line2, int col2, String token, IPythonNature nature2, String qualifier,
ICompletionCache completionCache) {
this.line = line2;
this.col = col2;
this.activationToken = token;
this.nature = nature2;
this.qualifier = qualifier;
Assert.isNotNull(completionCache);
this.completionCache = completionCache;
}
public CompletionState() {
}
/**
* @param module
* @param base
*/
@Override
public void checkWildImportInMemory(IModule caller, IModule wild) throws CompletionRecursionException {
if (this.wildImportMemory.isInRecursion(caller, wild)) {
throw new CompletionRecursionException(
"Possible recursion found -- probably programming error -- (caller: " + caller.getName()
+ ", import: " + wild.getName() + " ) - stopping analysis.");
}
}
/**
* @param module
* @param definition
*/
@Override
public void checkDefinitionMemory(IModule module, IDefinition definition) throws CompletionRecursionException {
if (this.definitionMemory.isInRecursion(module, (Definition) definition)) {
throw new CompletionRecursionException(
"Possible recursion found -- probably programming error -- (module: " + module.getName()
+ ", token: " + definition + ") - stopping analysis.");
}
}
/**
* @param module
*/
@Override
public void checkFindMemory(IModule module, String value) throws CompletionRecursionException {
if (this.findMemory.isInRecursion(module, value)) {
throw new CompletionRecursionException(
"Possible recursion found -- probably programming error -- (module: " + module.getName()
+ ", value: " + value + ") - stopping analysis.");
}
}
/**
* @param module
* @throws CompletionRecursionException
*/
@Override
public void checkResolveImportMemory(IModule module, String value) throws CompletionRecursionException {
if (this.resolveImportMemory.isInRecursion(module, value)) {
throw new CompletionRecursionException(
"Possible recursion found -- probably programming error -- (module: " + module.getName()
+ ", value: " + value + ") - stopping analysis.");
}
}
@Override
public void checkFindDefinitionMemory(IModule mod, String tok) throws CompletionRecursionException {
if (this.findDefinitionMemory.isInRecursion(mod, tok)) {
throw new CompletionRecursionException(
"Possible recursion found -- probably programming error -- (module: " + mod.getName()
+ ", value: " + tok + ") - stopping analysis.");
}
}
@Override
public void checkFindLocalDefinedDefinitionMemory(IModule mod, String tok) throws CompletionRecursionException {
if (this.findLocalDefinedDefinitionMemory.isInRecursion(mod, tok)) {
throw new CompletionRecursionException(
"Possible recursion found -- probably programming error -- (module: " + mod.getName()
+ ", value: " + tok + ") - stopping analysis.");
}
}
/**
* @param module
* @param base
*/
@Override
public void checkMemory(IModule module, String base) throws CompletionRecursionException {
if (this.memory.isInRecursion(module, base)) {
throw new CompletionRecursionException(
"Possible recursion found -- probably programming error -- (module: " + module.getName()
+ ", token: " + base + ") - stopping analysis.");
}
}
@Override
public void checkMaxTimeForCompletion() throws CompletionRecursionException {
if (this.initialMillis <= 0) {
this.initialMillis = System.currentTimeMillis();
if (SharedCorePlugin.inTestMode()) {
this.maxMillisToComplete = Long.MAX_VALUE; //2 * 1000; //In test mode the max is 2 seconds.
} else {
this.maxMillisToComplete = PyCodeCompletionPreferencesPage
.getMaximumNumberOfMillisToCompleteCodeCompletionRequest();
}
} else {
long diff = System.currentTimeMillis() - this.initialMillis;
if (diff > this.maxMillisToComplete) {
throw new CompletionRecursionException(
"Stopping analysis: completion took too much time to complete. Max set to: "
+ this.maxMillisToComplete + " millis. Current: " + diff + " millis. Note: this "
+ "value may be changed in the code-completion preferences.");
}
}
};
Set<Tuple3<Integer, Integer, IModule>> foundSameDefinitionMemory = new HashSet<Tuple3<Integer, Integer, IModule>>();
@Override
public boolean checkFoudSameDefinition(int line, int col, IModule mod) {
Tuple3<Integer, Integer, IModule> key = new Tuple3<Integer, Integer, IModule>(line, col, mod);
if (foundSameDefinitionMemory.contains(key)) {
return true;
}
foundSameDefinitionMemory.add(key);
return false;
}
@Override
public boolean canStillCheckFindSourceFromCompiled(IModule mod, String tok) {
if (!findSourceFromCompiledMemory.isInRecursion(mod, tok)) {
return true;
}
return false;
}
/**
* This check is a bit different from the others because of the context it will work in...
*
* This check is used when resolving things from imports, so, it may check for recursions found when in previous context, but
* if a recursion is found in the current context, that's ok (because it's simply trying to get the actual representation for a token)
*/
@Override
public void checkFindResolveImportMemory(IToken token) throws CompletionRecursionException {
Iterator<Memo<IToken>> it = findResolveImportMemory.iterator();
while (it.hasNext()) {
Memo<IToken> memo = it.next();
if (memo.isInRecursion(null, token)) {
// if(it.hasNext()){
throw new CompletionRecursionException(
"Possible recursion found -- probably programming error -- (token: " + token
+ ") - stopping analysis.");
// }
}
}
}
@Override
public void popFindResolveImportMemoryCtx() {
findResolveImportMemory.pop();
}
@Override
public void pushFindResolveImportMemoryCtx() {
findResolveImportMemory.push(new Memo<IToken>());
}
/**
* @param module
* @param base
*/
@Override
public void checkFindModuleCompletionsMemory(IModule mod, String tok) throws CompletionRecursionException {
if (this.findModuleCompletionsMemory.isInRecursion(mod, tok)) {
throw new CompletionRecursionException(
"Possible recursion found -- probably programming error -- (module: " + mod.getName()
+ ", token: " + tok + ") - stopping analysis.");
}
}
@Override
public String getActivationToken() {
return activationToken;
}
@Override
public IPythonNature getNature() {
return nature;
}
@Override
public void setActivationToken(String string) {
activationToken = string;
}
@Override
public String getFullActivationToken() {
return this.fullActivationToken;
}
@Override
public void setFullActivationToken(String act) {
this.fullActivationToken = act;
}
@Override
public void setBuiltinsGotten(boolean b) {
builtinsGotten = b;
}
/**
* @param i: starting at 0
*/
@Override
public void setCol(int i) {
col = i;
}
/**
* @param i: starting at 0
*/
@Override
public void setLine(int i) {
line = i;
}
@Override
public void setLocalImportsGotten(boolean b) {
localImportsGotten = b;
}
@Override
public boolean getLocalImportsGotten() {
return localImportsGotten;
}
@Override
public int getLine() {
return line;
}
@Override
public int getCol() {
return col;
}
@Override
public boolean getBuiltinsGotten() {
return builtinsGotten;
}
@Override
public void raiseNFindTokensOnImportedModsCalled(IModule mod, String tok) throws CompletionRecursionException {
if (this.importedModsCalled.isInRecursion(mod, tok)) {
throw new CompletionRecursionException("Possible recursion found (mod: " + mod.getName() + ", tok: " + tok
+ " ) - stopping analysis.");
}
}
@Override
public boolean getIsInCalltip() {
return isInCalltip;
}
@Override
public void setLookingFor(int b) {
this.setLookingFor(b, false);
}
@Override
public void setLookingFor(int b, boolean force) {
//the 1st is the one that counts (or it can be forced)
if (this.lookingForInstance == ICompletionState.LOOKING_FOR_INSTANCE_UNDEFINED || force) {
this.lookingForInstance = b;
}
}
@Override
public int getLookingFor() {
return this.lookingForInstance;
}
@Override
public ICompletionState getCopyWithActTok(String value) {
ICompletionState copy = getCopy();
copy.setActivationToken(value);
return copy;
}
@Override
public String getQualifier() {
return this.qualifier;
}
@Override
public void setIsInCalltip(boolean isInCalltip) {
this.isInCalltip = isInCalltip;
}
@Override
public void setTokenImportedModules(List<IToken> tokenImportedModules) {
if (tokenImportedModules != null) {
if (this.tokenImportedModules == null) {
this.tokenImportedModules = new ArrayList<IToken>(tokenImportedModules); //keep a copy of it
}
}
}
@Override
public List<IToken> getTokenImportedModules() {
return this.tokenImportedModules;
}
// ICompletionCache interface implementation -----------------------------------------------------------------------
@Override
public void add(Object key, Object n) {
this.completionCache.add(key, n);
}
@Override
public Object getObj(Object o) {
return this.completionCache.getObj(o);
}
@Override
public void remove(Object key) {
this.completionCache.remove(key);
}
@Override
public void removeStaleEntries() {
this.completionCache.removeStaleEntries();
}
@Override
public void clear() {
this.completionCache.clear();
}
private static class AlreadySerched {
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((actTok == null) ? 0 : actTok.hashCode());
result = prime * result + col;
result = prime * result + line;
result = prime * result + ((module == null) ? 0 : module.hashCode());
result = prime * result + ((value == null) ? 0 : value.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
AlreadySerched other = (AlreadySerched) obj;
if (actTok == null) {
if (other.actTok != null) {
return false;
}
} else if (!actTok.equals(other.actTok)) {
return false;
}
if (col != other.col) {
return false;
}
if (line != other.line) {
return false;
}
if (module == null) {
if (other.module != null) {
return false;
}
} else if (!module.equals(other.module)) {
return false;
}
if (value == null) {
if (other.value != null) {
return false;
}
} else if (!value.equals(other.value)) {
return false;
}
return true;
}
private final int line;
private final int col;
private final IModule module;
private final String actTok;
private final String value;
AlreadySerched(int line, int col, IModule module, String value, String actTok) {
this.line = line;
this.col = col;
this.module = module;
this.actTok = actTok;
this.value = value;
}
}
private Set<AlreadySerched> alreadySearchedInAssign = new HashSet<CompletionState.AlreadySerched>();
@Override
public boolean getAlreadySearchedInAssign(int line, int col, IModule module, String value, String actTok) {
AlreadySerched s = new AlreadySerched(line, col, module, value, actTok);
if (alreadySearchedInAssign.contains(s)) {
return true;
}
alreadySearchedInAssign.add(s);
return false;
}
int assign = 0;
@Override
public int pushAssign() {
assign += 1;
return assign;
}
@Override
public void popAssign() {
assign -= 1;
if (assign == 0) {
// When we get to level 0, clear anything searched previously
alreadySearchedInAssign.clear();
}
}
@Override
public void pushGetCompletionsUnpackingObject() throws CompletionRecursionException {
levelGetCompletionsUnpackingObject += 1;
if (levelGetCompletionsUnpackingObject > 15) {
throw new CompletionRecursionException(
"Error: recursion detected getting completions unpacking object. Activation token: "
+ this.getActivationToken());
}
}
@Override
public void popGetCompletionsUnpackingObject() {
levelGetCompletionsUnpackingObject -= 1;
}
}