0fb9a680b572c7fd651fac1df30b6ecd38aabf5f
[idea/community.git] / python / src / com / jetbrains / python / refactoring / makeFunctionTopLevel / PyBaseMakeFunctionTopLevelProcessor.java
1 /*
2  * Copyright 2000-2015 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.jetbrains.python.refactoring.makeFunctionTopLevel;
17
18 import com.intellij.codeInsight.controlflow.ControlFlow;
19 import com.intellij.codeInsight.controlflow.Instruction;
20 import com.intellij.openapi.application.ApplicationManager;
21 import com.intellij.openapi.util.text.StringUtil;
22 import com.intellij.psi.PsiElement;
23 import com.intellij.psi.PsiFile;
24 import com.intellij.psi.PsiNamedElement;
25 import com.intellij.psi.util.PsiTreeUtil;
26 import com.intellij.refactoring.BaseRefactoringProcessor;
27 import com.intellij.refactoring.ui.UsageViewDescriptorAdapter;
28 import com.intellij.usageView.UsageInfo;
29 import com.intellij.usageView.UsageViewDescriptor;
30 import com.intellij.util.ArrayUtil;
31 import com.intellij.util.containers.HashSet;
32 import com.jetbrains.python.PyNames;
33 import com.jetbrains.python.codeInsight.controlflow.ControlFlowCache;
34 import com.jetbrains.python.codeInsight.controlflow.ReadWriteInstruction;
35 import com.jetbrains.python.codeInsight.controlflow.ScopeOwner;
36 import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil;
37 import com.jetbrains.python.codeInsight.imports.AddImportHelper;
38 import com.jetbrains.python.psi.*;
39 import com.jetbrains.python.psi.impl.PyPsiUtils;
40 import com.jetbrains.python.psi.resolve.PyResolveContext;
41 import com.jetbrains.python.psi.types.TypeEvalContext;
42 import com.jetbrains.python.refactoring.PyRefactoringUtil;
43 import com.jetbrains.python.refactoring.classes.PyClassRefactoringUtil;
44 import org.jetbrains.annotations.NotNull;
45
46 import java.util.ArrayList;
47 import java.util.Collection;
48 import java.util.List;
49 import java.util.Set;
50
51 import static com.jetbrains.python.psi.PyUtil.as;
52
53 /**
54  * @author Mikhail Golubev
55  */
56 public abstract class PyBaseMakeFunctionTopLevelProcessor extends BaseRefactoringProcessor {
57   protected final PyFunction myFunction;
58   protected final PsiFile mySourceFile;
59   protected final PyResolveContext myResolveContext;
60   protected final PyElementGenerator myGenerator;
61   protected final String myDestinationPath;
62   protected final List<PsiElement> myExternalReads = new ArrayList<PsiElement>();
63
64   public PyBaseMakeFunctionTopLevelProcessor(@NotNull PyFunction targetFunction, @NotNull String destinationPath) {
65     super(targetFunction.getProject());
66     myFunction = targetFunction;
67     myDestinationPath = destinationPath;
68     final TypeEvalContext typeEvalContext = TypeEvalContext.userInitiated(myProject, targetFunction.getContainingFile());
69     myResolveContext = PyResolveContext.defaultContext().withTypeEvalContext(typeEvalContext);
70     myGenerator = PyElementGenerator.getInstance(myProject);
71     mySourceFile = myFunction.getContainingFile();
72   }
73
74   @NotNull
75   @Override
76   protected final UsageViewDescriptor createUsageViewDescriptor(@NotNull UsageInfo[] usages) {
77     return new UsageViewDescriptorAdapter() {
78       @NotNull
79       @Override
80       public PsiElement[] getElements() {
81         return new PsiElement[] {myFunction};
82       }
83
84       @Override
85       public String getProcessedElementsHeader() {
86         return getRefactoringName();
87       }
88     };
89   }
90
91   @NotNull
92   @Override
93   protected final UsageInfo[] findUsages() {
94     return ArrayUtil.toObjectArray(PyRefactoringUtil.findUsages(myFunction, false), UsageInfo.class);
95   }
96
97   @Override
98   protected final String getCommandName() {
99     return getRefactoringName();
100   }
101
102   @Override
103   protected final void performRefactoring(@NotNull UsageInfo[] usages) {
104     final List<String> newParameters = collectNewParameterNames();
105
106     assert ApplicationManager.getApplication().isWriteAccessAllowed();
107
108     // We should update usages before we generate and insert new function, because we have to update its usages inside 
109     // (e.g. recursive calls) it first 
110     updateUsages(newParameters, usages);
111     final PyFile newFile = PyUtil.getOrCreateFile(myDestinationPath, myProject);
112     final PyFunction newFunction = insertFunction(createNewFunction(newParameters), newFile);
113
114     myFunction.delete();
115
116     updateImports(newFunction, usages);
117   }
118
119   private void updateImports(@NotNull PyFunction newFunction, @NotNull UsageInfo[] usages) {
120     final Set<PsiFile> usageFiles = new HashSet<PsiFile>();
121     for (UsageInfo usage : usages) {
122       usageFiles.add(usage.getFile());
123     }
124     for (PsiFile file : usageFiles) {
125       if (file != newFunction.getContainingFile()) {
126         PyClassRefactoringUtil.insertImport(file, newFunction, null, true);
127       }
128     }
129     // References inside the body of function 
130     if (newFunction.getContainingFile() != mySourceFile) {
131       for (PsiElement read : myExternalReads) {
132         if (read instanceof PsiNamedElement && read.isValid()) {
133           PyClassRefactoringUtil.insertImport(newFunction, (PsiNamedElement)read, null, true);
134         }
135       }
136       PyClassRefactoringUtil.optimizeImports(mySourceFile);
137     }
138   }
139
140   @NotNull
141   protected abstract String getRefactoringName();
142
143   @NotNull
144   protected abstract List<String> collectNewParameterNames();
145
146   protected abstract void updateUsages(@NotNull Collection<String> newParamNames, @NotNull UsageInfo[] usages);
147   
148   @NotNull
149   protected abstract PyFunction createNewFunction(@NotNull Collection<String> newParamNames);
150
151   @NotNull
152   protected final PyParameterList addParameters(@NotNull PyParameterList paramList, @NotNull Collection<String> newParameters) {
153     if (!newParameters.isEmpty()) {
154       final String commaSeparatedNames = StringUtil.join(newParameters, ", ");
155       final StringBuilder paramListText = new StringBuilder(paramList.getText());
156       paramListText.insert(1, commaSeparatedNames + (paramList.getParameters().length > 0 ? ", " : ""));
157       final PyParameterList newElement = myGenerator.createParameterList(LanguageLevel.forElement(myFunction), paramListText.toString());
158       return (PyParameterList)paramList.replace(newElement);
159     }
160     return paramList;
161   }
162
163   @NotNull
164   protected PyArgumentList addArguments(@NotNull PyArgumentList argList, @NotNull Collection<String> newArguments) {
165     if (!newArguments.isEmpty()) {
166       final String commaSeparatedNames = StringUtil.join(newArguments, ", ");
167       final StringBuilder argListText = new StringBuilder(argList.getText());
168       argListText.insert(1, commaSeparatedNames + (argList.getArguments().length > 0 ? ", " : ""));
169       final PyArgumentList newElement = myGenerator.createArgumentList(LanguageLevel.forElement(argList), argListText.toString());
170       return (PyArgumentList)argList.replace(newElement);
171     }
172     return argList;
173   }
174
175   @NotNull
176   protected PyFunction insertFunction(@NotNull PyFunction newFunction, PyFile newFile) {
177     final PyFunction replacement;
178     if (mySourceFile == newFile) {
179       final PsiElement anchor;
180       anchor = PyPsiUtils.getParentRightBefore(myFunction, mySourceFile);
181       replacement = (PyFunction)mySourceFile.addAfter(newFunction, anchor);
182     }
183     else {
184       replacement = (PyFunction)newFile.addAfter(newFunction, AddImportHelper.getFileInsertPosition(newFile));
185     }
186     return replacement;
187   }
188
189   @NotNull
190   protected AnalysisResult analyseScope(@NotNull ScopeOwner owner) {
191     final ControlFlow controlFlow = ControlFlowCache.getControlFlow(owner);
192     final AnalysisResult result = new AnalysisResult();
193     for (Instruction instruction : controlFlow.getInstructions()) {
194       if (instruction instanceof ReadWriteInstruction) {
195         final ReadWriteInstruction readWriteInstruction = (ReadWriteInstruction)instruction;
196         final PsiElement element = readWriteInstruction.getElement();
197         if (element == null) {
198           continue;
199         }
200         if (readWriteInstruction.getAccess().isReadAccess()) {
201           for (PsiElement resolved : PyUtil.multiResolveTopPriority(element, myResolveContext)) {
202             if (resolved != null) {
203               if (isInitOrNewMethod(resolved)) {
204                 resolved = ((PyFunction)resolved).getContainingClass();
205               }
206               if (isFromEnclosingScope(resolved)) {
207                 result.readsFromEnclosingScope.add(element);
208               }
209               else if (!PsiTreeUtil.isAncestor(myFunction, resolved, false)) {
210                 myExternalReads.add(resolved);
211               }
212               if (resolved instanceof PyParameter && ((PyParameter)resolved).isSelf()) {
213                 if (PsiTreeUtil.getParentOfType(resolved, PyFunction.class) == myFunction) {
214                   result.readsOfSelfParameter.add(element);
215                 }
216                 else if (!PsiTreeUtil.isAncestor(myFunction, resolved, true)) {
217                   result.readsOfSelfParametersFromEnclosingScope.add(element);
218                 }
219               }
220             }
221           }
222         }
223         if (readWriteInstruction.getAccess().isWriteAccess() && element instanceof PyTargetExpression) {
224           for (PsiElement resolved : PyUtil.multiResolveTopPriority(element, myResolveContext)) {
225             if (resolved != null) {
226               if (element.getParent() instanceof PyNonlocalStatement && isFromEnclosingScope(resolved)) {
227                 result.nonlocalWritesToEnclosingScope.add((PyTargetExpression)element);
228               }
229               if (resolved instanceof PyParameter && ((PyParameter)resolved).isSelf() && 
230                   PsiTreeUtil.getParentOfType(resolved, PyFunction.class) == myFunction) {
231                 result.writesToSelfParameter.add((PyTargetExpression)element);
232               }
233             }
234           }
235         }
236       }
237     }
238     return result;
239   }
240
241   private static boolean isInitOrNewMethod(@NotNull PsiElement elem) {
242     final PyFunction func = as(elem, PyFunction.class);
243     return func != null && (PyNames.INIT.equals(func.getName()) || PyNames.NEW.equals(func.getName()));
244   }
245
246   private boolean isFromEnclosingScope(@NotNull PsiElement element) {
247     return element.getContainingFile() == mySourceFile &&
248            !PsiTreeUtil.isAncestor(myFunction, element, false) &&
249            !(ScopeUtil.getScopeOwner(element) instanceof PsiFile); 
250   }
251
252   protected static class AnalysisResult {
253     final List<PsiElement> readsFromEnclosingScope = new ArrayList<>();
254     final List<PyTargetExpression> nonlocalWritesToEnclosingScope = new ArrayList<>();
255     final List<PsiElement> readsOfSelfParametersFromEnclosingScope = new ArrayList<>();
256     final List<PsiElement> readsOfSelfParameter = new ArrayList<>();
257     // No one writes to "self", but handle this case too just to be sure
258     final List<PyTargetExpression> writesToSelfParameter = new ArrayList<>();
259   }
260 }