d5463514f004ff0bb22b4433ec13f8238090afad
[idea/community.git] / python / src / com / jetbrains / python / refactoring / move / 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.move.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.Condition;
22 import com.intellij.openapi.util.text.StringUtil;
23 import com.intellij.psi.PsiElement;
24 import com.intellij.psi.PsiFile;
25 import com.intellij.psi.PsiNamedElement;
26 import com.intellij.psi.util.PsiTreeUtil;
27 import com.intellij.refactoring.BaseRefactoringProcessor;
28 import com.intellij.refactoring.ui.UsageViewDescriptorAdapter;
29 import com.intellij.usageView.UsageInfo;
30 import com.intellij.usageView.UsageViewDescriptor;
31 import com.intellij.util.ArrayUtil;
32 import com.intellij.util.IncorrectOperationException;
33 import com.intellij.util.containers.ContainerUtil;
34 import com.intellij.util.containers.HashSet;
35 import com.jetbrains.python.PyBundle;
36 import com.jetbrains.python.PyNames;
37 import com.jetbrains.python.codeInsight.controlflow.ControlFlowCache;
38 import com.jetbrains.python.codeInsight.controlflow.ReadWriteInstruction;
39 import com.jetbrains.python.codeInsight.controlflow.ScopeOwner;
40 import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil;
41 import com.jetbrains.python.codeInsight.imports.AddImportHelper;
42 import com.jetbrains.python.psi.*;
43 import com.jetbrains.python.psi.impl.PyPsiUtils;
44 import com.jetbrains.python.psi.resolve.PyResolveContext;
45 import com.jetbrains.python.psi.types.TypeEvalContext;
46 import com.jetbrains.python.refactoring.PyRefactoringUtil;
47 import com.jetbrains.python.refactoring.classes.PyClassRefactoringUtil;
48 import com.jetbrains.python.refactoring.move.PyMoveRefactoringUtil;
49 import org.jetbrains.annotations.NotNull;
50
51 import java.util.ArrayList;
52 import java.util.Collection;
53 import java.util.List;
54 import java.util.Set;
55
56 import static com.jetbrains.python.psi.PyUtil.as;
57
58 /**
59  * @author Mikhail Golubev
60  */
61 public abstract class PyBaseMakeFunctionTopLevelProcessor extends BaseRefactoringProcessor {
62   protected final PyFunction myFunction;
63   protected final PsiFile mySourceFile;
64   protected final PyResolveContext myResolveContext;
65   protected final PyElementGenerator myGenerator;
66   protected final String myDestinationPath;
67   protected final List<PsiElement> myExternalReads = new ArrayList<PsiElement>();
68
69   public PyBaseMakeFunctionTopLevelProcessor(@NotNull PyFunction targetFunction, @NotNull String destinationPath) {
70     super(targetFunction.getProject());
71     myFunction = targetFunction;
72     myDestinationPath = destinationPath;
73     final TypeEvalContext typeEvalContext = TypeEvalContext.userInitiated(myProject, targetFunction.getContainingFile());
74     myResolveContext = PyResolveContext.defaultContext().withTypeEvalContext(typeEvalContext);
75     myGenerator = PyElementGenerator.getInstance(myProject);
76     mySourceFile = myFunction.getContainingFile();
77   }
78
79   @NotNull
80   @Override
81   protected final UsageViewDescriptor createUsageViewDescriptor(@NotNull UsageInfo[] usages) {
82     return new UsageViewDescriptorAdapter() {
83       @NotNull
84       @Override
85       public PsiElement[] getElements() {
86         return new PsiElement[] {myFunction};
87       }
88
89       @Override
90       public String getProcessedElementsHeader() {
91         return getRefactoringName();
92       }
93     };
94   }
95
96   @NotNull
97   @Override
98   protected final UsageInfo[] findUsages() {
99     return ArrayUtil.toObjectArray(PyRefactoringUtil.findUsages(myFunction, false), UsageInfo.class);
100   }
101
102   @Override
103   protected final String getCommandName() {
104     return getRefactoringName();
105   }
106
107   @Override
108   protected final void performRefactoring(@NotNull UsageInfo[] usages) {
109     final List<String> newParameters = collectNewParameterNames();
110
111     assert ApplicationManager.getApplication().isWriteAccessAllowed();
112
113     final PyFile targetFile = PyUtil.getOrCreateFile(myDestinationPath, myProject);
114     if (targetFile.findTopLevelFunction(myFunction.getName()) != null) {
115       throw new IncorrectOperationException(
116         PyBundle.message("refactoring.move.error.destination.file.contains.function.$0", myFunction.getName()));
117     }
118     if (importsRequired(usages, targetFile)) {
119       PyMoveRefactoringUtil.checkValidImportableFile(targetFile, targetFile.getVirtualFile());
120     }
121     
122     // We should update usages before we generate and insert new function, because we have to update its usages inside 
123     // (e.g. recursive calls) it first 
124     updateUsages(newParameters, usages);
125     final PyFunction newFunction = insertFunction(createNewFunction(newParameters), targetFile);
126
127     myFunction.delete();
128
129     updateImports(newFunction, usages);
130   }
131
132   private boolean importsRequired(@NotNull UsageInfo[] usages, final PyFile targetFile) {
133     return ContainerUtil.exists(usages, new Condition<UsageInfo>() {
134       @Override
135       public boolean value(UsageInfo info) {
136         final PsiElement element = info.getElement();
137         if (element == null) {
138           return false;
139         }
140         return !belongsToFunction(element) && info.getFile() != targetFile;
141       }
142     });
143   }
144
145   private boolean belongsToFunction(PsiElement element) {
146     return PsiTreeUtil.isAncestor(myFunction, element, false);
147   }
148
149
150   private void updateImports(@NotNull PyFunction newFunction, @NotNull UsageInfo[] usages) {
151     final Set<PsiFile> usageFiles = new HashSet<PsiFile>();
152     for (UsageInfo usage : usages) {
153       usageFiles.add(usage.getFile());
154     }
155     for (PsiFile file : usageFiles) {
156       if (file != newFunction.getContainingFile()) {
157         PyClassRefactoringUtil.insertImport(file, newFunction, null, true);
158       }
159     }
160     // References inside the body of function 
161     if (newFunction.getContainingFile() != mySourceFile) {
162       for (PsiElement read : myExternalReads) {
163         if (read instanceof PsiNamedElement && read.isValid()) {
164           PyClassRefactoringUtil.insertImport(newFunction, (PsiNamedElement)read, null, true);
165         }
166       }
167       PyClassRefactoringUtil.optimizeImports(mySourceFile);
168     }
169   }
170
171   @NotNull
172   protected abstract String getRefactoringName();
173
174   @NotNull
175   protected abstract List<String> collectNewParameterNames();
176
177   protected abstract void updateUsages(@NotNull Collection<String> newParamNames, @NotNull UsageInfo[] usages);
178   
179   @NotNull
180   protected abstract PyFunction createNewFunction(@NotNull Collection<String> newParamNames);
181
182   @NotNull
183   protected final PyParameterList addParameters(@NotNull PyParameterList paramList, @NotNull Collection<String> newParameters) {
184     if (!newParameters.isEmpty()) {
185       final String commaSeparatedNames = StringUtil.join(newParameters, ", ");
186       final StringBuilder paramListText = new StringBuilder(paramList.getText());
187       paramListText.insert(1, commaSeparatedNames + (paramList.getParameters().length > 0 ? ", " : ""));
188       final PyParameterList newElement = myGenerator.createParameterList(LanguageLevel.forElement(myFunction), paramListText.toString());
189       return (PyParameterList)paramList.replace(newElement);
190     }
191     return paramList;
192   }
193
194   @NotNull
195   protected PyArgumentList addArguments(@NotNull PyArgumentList argList, @NotNull Collection<String> newArguments) {
196     if (!newArguments.isEmpty()) {
197       final String commaSeparatedNames = StringUtil.join(newArguments, ", ");
198       final StringBuilder argListText = new StringBuilder(argList.getText());
199       argListText.insert(1, commaSeparatedNames + (argList.getArguments().length > 0 ? ", " : ""));
200       final PyArgumentList newElement = myGenerator.createArgumentList(LanguageLevel.forElement(argList), argListText.toString());
201       return (PyArgumentList)argList.replace(newElement);
202     }
203     return argList;
204   }
205
206   @NotNull
207   protected PyFunction insertFunction(@NotNull PyFunction newFunction, PyFile newFile) {
208     final PyFunction replacement;
209     if (mySourceFile == newFile) {
210       final PsiElement anchor;
211       anchor = PyPsiUtils.getParentRightBefore(myFunction, mySourceFile);
212       replacement = (PyFunction)mySourceFile.addAfter(newFunction, anchor);
213     }
214     else {
215       replacement = (PyFunction)newFile.addAfter(newFunction, AddImportHelper.getFileInsertPosition(newFile));
216     }
217     return replacement;
218   }
219
220   @NotNull
221   protected AnalysisResult analyseScope(@NotNull ScopeOwner owner) {
222     final ControlFlow controlFlow = ControlFlowCache.getControlFlow(owner);
223     final AnalysisResult result = new AnalysisResult();
224     for (Instruction instruction : controlFlow.getInstructions()) {
225       if (instruction instanceof ReadWriteInstruction) {
226         final ReadWriteInstruction readWriteInstruction = (ReadWriteInstruction)instruction;
227         final PsiElement element = readWriteInstruction.getElement();
228         if (element == null) {
229           continue;
230         }
231         if (readWriteInstruction.getAccess().isReadAccess()) {
232           for (PsiElement resolved : PyUtil.multiResolveTopPriority(element, myResolveContext)) {
233             if (resolved != null) {
234               if (isInitOrNewMethod(resolved)) {
235                 resolved = ((PyFunction)resolved).getContainingClass();
236               }
237               if (isFromEnclosingScope(resolved)) {
238                 result.readsFromEnclosingScope.add(element);
239               }
240               else if (!belongsToFunction(resolved)) {
241                 myExternalReads.add(resolved);
242               }
243               if (resolved instanceof PyParameter && ((PyParameter)resolved).isSelf()) {
244                 if (PsiTreeUtil.getParentOfType(resolved, PyFunction.class) == myFunction) {
245                   result.readsOfSelfParameter.add(element);
246                 }
247                 else if (!PsiTreeUtil.isAncestor(myFunction, resolved, true)) {
248                   result.readsOfSelfParametersFromEnclosingScope.add(element);
249                 }
250               }
251             }
252           }
253         }
254         if (readWriteInstruction.getAccess().isWriteAccess() && element instanceof PyTargetExpression) {
255           for (PsiElement resolved : PyUtil.multiResolveTopPriority(element, myResolveContext)) {
256             if (resolved != null) {
257               if (element.getParent() instanceof PyNonlocalStatement && isFromEnclosingScope(resolved)) {
258                 result.nonlocalWritesToEnclosingScope.add((PyTargetExpression)element);
259               }
260               if (resolved instanceof PyParameter && ((PyParameter)resolved).isSelf() && 
261                   PsiTreeUtil.getParentOfType(resolved, PyFunction.class) == myFunction) {
262                 result.writesToSelfParameter.add((PyTargetExpression)element);
263               }
264             }
265           }
266         }
267       }
268     }
269     return result;
270   }
271
272   private static boolean isInitOrNewMethod(@NotNull PsiElement elem) {
273     final PyFunction func = as(elem, PyFunction.class);
274     return func != null && (PyNames.INIT.equals(func.getName()) || PyNames.NEW.equals(func.getName()));
275   }
276
277   private boolean isFromEnclosingScope(@NotNull PsiElement element) {
278     return PyUtil.inSameFile(element, myFunction) &&
279            !belongsToFunction(element) &&
280            !(ScopeUtil.getScopeOwner(element) instanceof PsiFile); 
281   }
282
283   protected static class AnalysisResult {
284     final List<PsiElement> readsFromEnclosingScope = new ArrayList<>();
285     final List<PyTargetExpression> nonlocalWritesToEnclosingScope = new ArrayList<>();
286     final List<PsiElement> readsOfSelfParametersFromEnclosingScope = new ArrayList<>();
287     final List<PsiElement> readsOfSelfParameter = new ArrayList<>();
288     // No one writes to "self", but handle this case too just to be sure
289     final List<PyTargetExpression> writesToSelfParameter = new ArrayList<>();
290   }
291 }