/** * 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; } }