/**
* 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 Apr 9, 2006
*/
package com.python.pydev.refactoring.wizards.rename;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.ltk.core.refactoring.RefactoringStatus;
import org.python.pydev.core.IPythonNature;
import org.python.pydev.core.ModulesKey;
import org.python.pydev.core.docutils.PySelection;
import org.python.pydev.core.docutils.PySelection.ActivationTokenAndQual;
import org.python.pydev.core.log.Log;
import org.python.pydev.editor.codecompletion.revisited.modules.SourceModule;
import org.python.pydev.editor.codecompletion.revisited.visitors.Definition;
import org.python.pydev.editor.refactoring.RefactoringRequest;
import org.python.pydev.parser.jython.SimpleNode;
import org.python.pydev.parser.jython.ast.Attribute;
import org.python.pydev.parser.jython.ast.Call;
import org.python.pydev.parser.jython.ast.ClassDef;
import org.python.pydev.parser.jython.ast.FunctionDef;
import org.python.pydev.parser.jython.ast.exprType;
import org.python.pydev.parser.visitors.scope.ASTEntry;
import org.python.pydev.shared_core.structure.Tuple;
import com.python.pydev.analysis.scopeanalysis.AstEntryScopeAnalysisConstants;
import com.python.pydev.analysis.scopeanalysis.ScopeAnalyzerVisitor;
import com.python.pydev.refactoring.refactorer.RefactorerFindReferences;
import com.python.pydev.refactoring.wizards.IRefactorRenameProcess;
/**
* This class presents the basic functionality for doing a rename.
*
* @author Fabio
*/
public abstract class AbstractRenameRefactorProcess implements IRefactorRenameProcess {
/**
* The request for the refactor
*/
protected RefactoringRequest request;
/**
* For a rename, we always need a definition
*/
protected Definition definition;
/**
* This is the list that contains only the occurrences for the current document,
* passed by the request.
*/
protected HashSet<ASTEntry> docOccurrences = new HashSet<ASTEntry>();
/**
* This map contains:
* key: tuple with module name and the IFile representing that module
* value: list of ast entries to be replaced in a given file
*/
protected Map<Tuple<String, File>, HashSet<ASTEntry>> fileOccurrences = new HashMap<Tuple<String, File>, HashSet<ASTEntry>>();
@Override
public void clear() {
fileOccurrences.clear();
docOccurrences.clear();
}
/**
* May be used by subclasses
*/
public AbstractRenameRefactorProcess() {
}
/**
* @param definition the definition on where this rename should be applied (we will find the references based
* on this definition).
*/
public AbstractRenameRefactorProcess(Definition definition) {
this.definition = definition;
}
/**
* Adds the occurences to be renamed given the request. If the rename is a local rename, and there is no need
* of handling multiple files, this should be the preferred way of adding the occurrences.
*
* @param request will be used to fill the module name and the document
* @param oc the occurrences to add
*/
protected void addOccurrences(RefactoringRequest request, List<ASTEntry> oc) {
docOccurrences.addAll(oc);
}
/**
* Adds the occurrences found to some module.
*
* @param oc the occurrences found
* @param file the file where the occurrences were found
* @param modName the name of the module that is bounded to the given file.
*/
protected void addOccurrences(List<ASTEntry> oc, File file, String modName) {
Tuple<String, File> key = new Tuple<String, File>(modName, file);
Set<ASTEntry> existent = fileOccurrences.get(key);
if (existent == null) {
fileOccurrences.put(key, new HashSet<ASTEntry>(oc));
} else {
existent.addAll(oc);
}
}
public static int getOffset(IDocument doc, ASTEntry entry) {
int beginLine;
int beginCol;
SimpleNode node = entry.node;
if (node instanceof ClassDef) {
ClassDef def = (ClassDef) node;
node = def.name;
}
if (node instanceof FunctionDef) {
FunctionDef def = (FunctionDef) node;
node = def.name;
}
if (node instanceof Attribute) {
exprType value = ((Attribute) node).value;
if (value instanceof Call) {
Call c = (Call) value;
node = c.func;
}
}
beginLine = node.beginLine;
beginCol = node.beginColumn;
int offset = PySelection.getAbsoluteCursorOffset(doc, beginLine - 1, beginCol - 1);
return offset;
}
/**
* This method is used to sort the occurrences given the place where they were found
*/
public static List<ASTEntry> sortOccurrences(List<ASTEntry> occurrences) {
occurrences = new ArrayList<ASTEntry>(occurrences);
Collections.sort(occurrences, new Comparator<ASTEntry>() {
@Override
public int compare(ASTEntry o1, ASTEntry o2) {
int o1Found = (Integer) o1
.getAdditionalInfo(AstEntryScopeAnalysisConstants.AST_ENTRY_FOUND_LOCATION, 0);
int o2Found = (Integer) o2
.getAdditionalInfo(AstEntryScopeAnalysisConstants.AST_ENTRY_FOUND_LOCATION, 0);
if (o1Found == o2Found) {
return 0;
}
if (o1Found < o2Found) {
return -1;
} else {
return 1;
}
}
});
return occurrences;
}
/**
* This function is used to redirect where the initial should should target
* (in the local or workspace scope).
*/
@Override
public void findReferencesToRename(RefactoringRequest request, RefactoringStatus status) {
this.request = request;
if ((Boolean) request.getAdditionalInfo(RefactoringRequest.FIND_REFERENCES_ONLY_IN_LOCAL_SCOPE,
false)) {
findReferencesToRenameOnLocalScope(request, status);
} else {
findReferencesToRenameOnWorkspace(request, status);
}
if (!occurrencesValid(status)) {
return;
}
}
/**
* This function should be overridden to find the occurrences in the local scope
* (and check if they are correct).
*
* @param status object where the status can be set (to add errors/warnings)
* @param request the request used for this check
*/
protected abstract void findReferencesToRenameOnLocalScope(RefactoringRequest request, RefactoringStatus status);
/**
* This function should be overridden to find the occurrences in the workspace scope
* (and check if they are correct).
*
* @param status object where the status can be set (to add errors/warnings)
* @param request the request used for this check
*/
protected abstract void findReferencesToRenameOnWorkspace(RefactoringRequest request, RefactoringStatus status);
/**
* Checks if the occurrences gotten are valid or not.
*
* @param status the errors will be added to the passed status.
* @return true if all is ok and false otherwise
*/
protected boolean occurrencesValid(RefactoringStatus status) {
if (docOccurrences.size() == 0 && !(request.isModuleRenameRefactoringRequest())) {
status.addFatalError("No occurrences found for:" + request.initialName);
return false;
}
return true;
}
/**
* Implemented from the super interface. Should return the occurrences from the current document
*
* @see com.python.pydev.refactoring.wizards.IRefactorRenameProcess#getOccurrences()
*/
@Override
public HashSet<ASTEntry> getOccurrences() {
return docOccurrences;
}
/**
* Implemented from the super interface. Should return the occurrences found in other documents
* (but should not return the ones found in the current document)
*
* @see com.python.pydev.refactoring.wizards.IRefactorRenameProcess#getOccurrencesInOtherFiles()
*/
@Override
public Map<Tuple<String, File>, HashSet<ASTEntry>> getOccurrencesInOtherFiles() {
return this.fileOccurrences;
}
/**
* Searches for a list of entries that are found within a scope.
*
* It is always based on a single scope and bases itself on a refactoring request.
*/
protected List<ASTEntry> getOccurrencesWithScopeAnalyzer(RefactoringRequest request, SourceModule module) {
List<ASTEntry> entryOccurrences = new ArrayList<ASTEntry>();
try {
ScopeAnalyzerVisitor visitor;
if (!request.ps.getCurrToken().o1.equals(request.initialName)) {
//i.e.: it seems it wasn't started from the editor, so, we need to search using the
//initial name and not the current selection
PySelection ps = request.ps;
visitor = new ScopeAnalyzerVisitor(request.nature, module.getName(), module,
ps.getDoc(),
new NullProgressMonitor(),
request.initialName,
-1,
ActivationTokenAndQual.splitActAndQualifier(request.initialName));
} else {
visitor = new ScopeAnalyzerVisitor(request.nature, module.getName(), module,
new NullProgressMonitor(), request.ps);
}
module.getAst().accept(visitor);
entryOccurrences = visitor.getEntryOccurrences();
} catch (BadLocationException e) {
//don't log
} catch (Exception e) {
Log.log(e);
}
return entryOccurrences;
}
/**
* This functions tries to find the modules that may have matches for a given request.
*
* Note that it may return files that don't actually contain what we're looking for.
*
* @param request the request for a rename.
* @return a list with the files that may contain matches for the refactoring.
*/
protected List<Tuple<List<ModulesKey>, IPythonNature>> findFilesWithPossibleReferences(
RefactoringRequest request) throws OperationCanceledException {
return new RefactorerFindReferences().findPossibleReferences(request);
}
}