/** * 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.refactoring.wizards.rename; import java.util.ArrayList; import java.util.List; import org.eclipse.core.runtime.Assert; import org.eclipse.ltk.core.refactoring.RefactoringStatus; import org.python.pydev.core.IDefinition; import org.python.pydev.core.IPythonNature; import org.python.pydev.editor.codecompletion.revisited.CompletionCache; import org.python.pydev.editor.codecompletion.revisited.CompletionStateFactory; import org.python.pydev.editor.codecompletion.revisited.modules.SourceModule; import org.python.pydev.editor.codecompletion.revisited.visitors.Definition; import org.python.pydev.editor.codecompletion.revisited.visitors.KeywordParameterDefinition; import org.python.pydev.editor.refactoring.RefactoringRequest; import org.python.pydev.parser.jython.SimpleNode; import org.python.pydev.parser.jython.ast.Call; import org.python.pydev.parser.jython.ast.FunctionDef; import org.python.pydev.parser.jython.ast.Name; import org.python.pydev.parser.jython.ast.NameTok; import org.python.pydev.parser.visitors.NodeUtils; import org.python.pydev.parser.visitors.scope.ASTEntry; import org.python.pydev.plugin.nature.PythonNature; import com.aptana.shared_core.structure.Tuple; import com.python.pydev.analysis.scopeanalysis.ScopeAnalysis; import com.python.pydev.refactoring.wizards.rename.visitors.FindCallVisitor; /** * The rename parameter is based on the rename function, because it will basically: * 1- get the function definition * 2- get all the references * * 3- change the parameter in the function definition (as well as any references to the * parameter inside the function * 4- change the parameter in all function calls * * * This process will only be available if we can find the function definition * (otherwise, we'd have a standard rename any local here) * * @author fabioz */ public class PyRenameParameterProcess extends PyRenameFunctionProcess { private String functionName; /** * Used if we're in a call and cannot find the definition for the method of that call when we've a KeywordParameterDefinition. * If that's not the case, it's null. */ private ASTEntry singleEntry; public PyRenameParameterProcess(KeywordParameterDefinition definition, IPythonNature nature) { Assert.isNotNull(definition.scope, "The scope for a rename parameter must always be provided."); String tok = NodeUtils.getFullRepresentationString(definition.call); int line = definition.call.beginLine; int col = definition.call.beginColumn; IDefinition[] definitions; try { definitions = definition.module.findDefinition(CompletionStateFactory.getEmptyCompletionState(tok, nature, line - 1, col - 1, new CompletionCache()), line, col, nature); } catch (Exception e) { throw new RuntimeException(e); } if (definitions != null && definitions.length > 0) { init((Definition) definitions[0]); } else { singleEntry = new ASTEntry(null); singleEntry.node = definition.ast; } } @Override protected void findReferencesToRenameOnLocalScope(RefactoringRequest request, RefactoringStatus status) { if (singleEntry == null) { super.findReferencesToRenameOnLocalScope(request, status); } else { docOccurrences.add(singleEntry); } } @Override protected void findReferencesToRenameOnWorkspace(RefactoringRequest request, RefactoringStatus status) { if (singleEntry == null) { super.findReferencesToRenameOnWorkspace(request, status); } else { docOccurrences.add(singleEntry); } } public PyRenameParameterProcess(Definition definition) { //empty, because we'll actually supply a different definition for the superclass (the method //definition, and not the parameter, which we receive here). init(definition); } private void init(Definition definition) { Assert.isNotNull(definition.scope, "The scope for a rename parameter must always be provided."); FunctionDef node; if (definition.ast instanceof FunctionDef) { node = (FunctionDef) definition.ast; } else { node = (FunctionDef) definition.scope.getScopeStack().peek(); } super.definition = new Definition(node.beginLine, node.beginColumn, ((NameTok) node.name).id, node, definition.scope, definition.module); this.functionName = ((NameTok) node.name).id; } /** * These are the methods that we need to override to change the function occurrences for parameter occurrences */ protected List<ASTEntry> getEntryOccurrencesInSameModule(RefactoringStatus status, String initialName, SimpleNode root) { List<ASTEntry> occurrences = super.getEntryOccurrencesInSameModule(status, this.functionName, root); return getParameterOccurences(occurrences, root); } protected List<ASTEntry> getOccurrencesInOtherModule(RefactoringStatus status, String initialName, SourceModule module, PythonNature nature) { List<ASTEntry> occurrences = super.getOccurrencesInOtherModule(status, this.functionName, module, nature); return getParameterOccurences(occurrences, module.getAst()); } /** * This method changes function occurrences for parameter occurrences */ private List<ASTEntry> getParameterOccurences(List<ASTEntry> occurrences, SimpleNode root) { List<ASTEntry> ret = new ArrayList<ASTEntry>(); List<Tuple<Integer, Integer>> acceptedCommentRanges = new ArrayList<Tuple<Integer, Integer>>(); for (ASTEntry entry : occurrences) { if (entry.node instanceof Name) { Name name = (Name) entry.node; if (name.ctx == Name.Artificial) { continue; } } if (entry.parent != null && entry.parent.node instanceof FunctionDef && entry.node instanceof NameTok && ((NameTok) entry.node).ctx == NameTok.FunctionName) { //process a function definition (get the parameters with the given name and //references inside that function) processFunctionDef(ret, entry); ASTEntry parent = entry.parent; acceptedCommentRanges.add(new Tuple<Integer, Integer>(parent.node.beginLine, parent.endLine)); } else if (entry.node instanceof Name) { processFoundName(root, ret, (Name) entry.node); } else if (entry.node instanceof NameTok) { processFoundNameTok(root, ret, (NameTok) entry.node); } } if (ret.size() > 0) { //only add comments and strings if there's at least some other occurrence List<ASTEntry> commentOccurrences = ScopeAnalysis.getCommentOccurrences(request.initialName, root); for (ASTEntry commentOccurrence : commentOccurrences) { for (Tuple<Integer, Integer> range : acceptedCommentRanges) { if (commentOccurrence.node.beginLine >= range.o1 && commentOccurrence.node.beginLine <= range.o2) { ret.add(commentOccurrence); } } } } return ret; } private void processFunctionDef(List<ASTEntry> ret, ASTEntry entry) { //this is the actual function definition, so, let's take a look at its arguments... FunctionDef node = (FunctionDef) entry.parent.node; List<ASTEntry> found = ScopeAnalysis.getLocalOccurrences(request.initialName, node); ret.addAll(ScopeAnalysis.getStringOccurrences(request.initialName, node)); ret.addAll(found); } private void processFoundNameTok(SimpleNode root, List<ASTEntry> ret, NameTok name) { if (name.ctx == NameTok.Attrib) { Call call = FindCallVisitor.findCall(name, root); processCall(ret, call); } } private void processFoundName(SimpleNode root, List<ASTEntry> ret, Name name) { if (name.ctx == Name.Load) { Call call = FindCallVisitor.findCall(name, root); processCall(ret, call); } } private void processCall(List<ASTEntry> ret, Call call) { if (call == null) { return; } List<ASTEntry> found = ScopeAnalysis.getLocalOccurrences(request.initialName, call); for (ASTEntry entry2 : found) { if (entry2.node instanceof NameTok) { NameTok name2 = (NameTok) entry2.node; if (name2.ctx == NameTok.KeywordName) { ret.add(entry2); } } } } }