01a4d03ea1f679301841b5632ffd05bdf9a423cc
[idea/community.git] / plugins / groovy / src / org / jetbrains / plugins / groovy / refactoring / changeSignature / GrChageSignatureUsageSearcher.java
1 /*
2  * Copyright 2000-2010 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 org.jetbrains.plugins.groovy.refactoring.changeSignature;
17
18 import com.intellij.openapi.diagnostic.Logger;
19 import com.intellij.psi.*;
20 import com.intellij.psi.search.GlobalSearchScope;
21 import com.intellij.psi.search.searches.MethodReferencesSearch;
22 import com.intellij.psi.search.searches.OverridingMethodsSearch;
23 import com.intellij.psi.search.searches.ReferencesSearch;
24 import com.intellij.refactoring.RefactoringBundle;
25 import com.intellij.refactoring.changeSignature.*;
26 import com.intellij.refactoring.rename.UnresolvableCollisionUsageInfo;
27 import com.intellij.refactoring.util.MoveRenameUsageInfo;
28 import com.intellij.refactoring.util.RefactoringUIUtil;
29 import com.intellij.refactoring.util.RefactoringUtil;
30 import com.intellij.refactoring.util.usageInfo.DefaultConstructorImplicitUsageInfo;
31 import com.intellij.refactoring.util.usageInfo.NoConstructorClassUsageInfo;
32 import com.intellij.usageView.UsageInfo;
33 import com.intellij.usageView.UsageViewUtil;
34 import com.intellij.util.containers.HashSet;
35 import org.jetbrains.plugins.groovy.GroovyFileType;
36 import org.jetbrains.plugins.groovy.lang.groovydoc.psi.api.GrDocTagValueToken;
37 import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrArgumentList;
38 import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrOpenBlock;
39 import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrAnonymousClassDefinition;
40 import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrEnumConstant;
41 import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod;
42 import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil;
43
44 import java.util.ArrayList;
45 import java.util.Arrays;
46 import java.util.Set;
47
48 /**
49  * @author Maxim.Medvedev
50  */
51 class GrChageSignatureUsageSearcher {
52   private final JavaChangeInfo myChangeInfo;
53   private static final Logger LOG =
54     Logger.getInstance("org.jetbrains.plugins.groovy.refactoring.changeSignature.GrChageSignatureUsageSearcher");
55
56   GrChageSignatureUsageSearcher(JavaChangeInfo changeInfo) {
57     this.myChangeInfo = changeInfo;
58   }
59
60   public UsageInfo[] findUsages() {
61     ArrayList<UsageInfo> result = new ArrayList<UsageInfo>();
62     final PsiElement element = myChangeInfo.getMethod();
63     if (element instanceof PsiMethod) {
64       final PsiMethod method = (PsiMethod)element;
65
66       findSimpleUsages(method, result);
67
68       final UsageInfo[] usageInfos = result.toArray(new UsageInfo[result.size()]);
69       return UsageViewUtil.removeDuplicatedUsages(usageInfos);
70     }
71     return UsageInfo.EMPTY_ARRAY;
72   }
73
74
75   private void findSimpleUsages(final PsiMethod method, final ArrayList<UsageInfo> result) {
76     PsiMethod[] overridingMethods = findSimpleUsagesWithoutParameters(method, result, true, true, true);
77     //findUsagesInCallers(result); todo
78
79     //Parameter name changes are not propagated
80     findParametersUsage(method, result, overridingMethods);
81   }
82
83   /* todo
84   private void findUsagesInCallers(final ArrayList<UsageInfo> usages) {
85     if (myChangeInfo instanceof JavaChangeInfoImpl) {
86       JavaChangeInfoImpl changeInfo = (JavaChangeInfoImpl)myChangeInfo;
87
88       for (PsiMethod caller : changeInfo.propagateParametersMethods) {
89         usages.add(new CallerUsageInfo(caller, true, changeInfo.propagateExceptionsMethods.contains(caller)));
90       }
91       for (PsiMethod caller : changeInfo.propagateExceptionsMethods) {
92         usages.add(new CallerUsageInfo(caller, changeInfo.propagateParametersMethods.contains(caller), true));
93       }
94       Set<PsiMethod> merged = new HashSet<PsiMethod>();
95       merged.addAll(changeInfo.propagateParametersMethods);
96       merged.addAll(changeInfo.propagateExceptionsMethods);
97       for (final PsiMethod method : merged) {
98         findSimpleUsagesWithoutParameters(method, usages, changeInfo.propagateParametersMethods.contains(method),
99                                           changeInfo.propagateExceptionsMethods.contains(method), false);
100       }
101     }
102   }
103   */
104
105   private void detectLocalsCollisionsInMethod(final GrMethod method, final ArrayList<UsageInfo> result, boolean isOriginal) {
106     if (!GroovyFileType.GROOVY_LANGUAGE.equals(method.getLanguage())) return;
107
108     final PsiParameter[] parameters = method.getParameterList().getParameters();
109     final Set<PsiParameter> deletedOrRenamedParameters = new HashSet<PsiParameter>();
110     if (isOriginal) {
111       deletedOrRenamedParameters.addAll(Arrays.asList(parameters));
112       for (ParameterInfo parameterInfo : myChangeInfo.getNewParameters()) {
113         if (parameterInfo.getOldIndex() >= 0) {
114           final PsiParameter parameter = parameters[parameterInfo.getOldIndex()];
115           if (parameterInfo.getName().equals(parameter.getName())) {
116             deletedOrRenamedParameters.remove(parameter);
117           }
118         }
119       }
120     }
121
122     for (ParameterInfo parameterInfo : myChangeInfo.getNewParameters()) {
123       final int oldParameterIndex = parameterInfo.getOldIndex();
124       final String newName = parameterInfo.getName();
125       if (oldParameterIndex >= 0) {
126         if (isOriginal) {   //Name changes take place only in primary method
127           final PsiParameter parameter = parameters[oldParameterIndex];
128           if (!newName.equals(parameter.getName())) {
129             final GrUnresolvableLocalCollisionDetector.CollidingVariableVisitor collidingVariableVisitor =
130               new GrUnresolvableLocalCollisionDetector.CollidingVariableVisitor() {
131                 public void visitCollidingVariable(final PsiVariable collidingVariable) {
132                   if (!deletedOrRenamedParameters.contains(collidingVariable)) {
133                     result.add(new RenamedParameterCollidesWithLocalUsageInfo(parameter, collidingVariable, method));
134                   }
135                 }
136               };
137             GrUnresolvableLocalCollisionDetector.visitLocalsCollisions(parameter, newName, method.getBlock(), collidingVariableVisitor);
138           }
139         }
140       }
141       else {
142         final GrUnresolvableLocalCollisionDetector.CollidingVariableVisitor variableVisitor =
143           new GrUnresolvableLocalCollisionDetector.CollidingVariableVisitor() {
144             public void visitCollidingVariable(PsiVariable collidingVariable) {
145               if (!deletedOrRenamedParameters.contains(collidingVariable)) {
146                 result.add(new NewParameterCollidesWithLocalUsageInfo(collidingVariable, collidingVariable, method));
147               }
148             }
149           };
150         final GrOpenBlock block = method.getBlock();
151         if (block != null) {
152           GrUnresolvableLocalCollisionDetector.visitLocalsCollisions(method, newName, block, variableVisitor);
153         }
154       }
155     }
156   }
157
158   private void findParametersUsage(final PsiMethod method, ArrayList<UsageInfo> result, PsiMethod[] overriders) {
159     if (!GroovyFileType.GROOVY_LANGUAGE.equals(method.getLanguage())) return;
160
161     PsiParameter[] parameters = method.getParameterList().getParameters();
162     for (ParameterInfo info : myChangeInfo.getNewParameters()) {
163       if (info.getOldIndex() >= 0) {
164         PsiParameter parameter = parameters[info.getOldIndex()];
165         if (!info.getName().equals(parameter.getName())) {
166           addParameterUsages(parameter, result, info);
167
168           for (PsiMethod overrider : overriders) {
169             if (!GroovyFileType.GROOVY_LANGUAGE.equals(overrider.getLanguage())) continue;
170             PsiParameter parameter1 = overrider.getParameterList().getParameters()[info.getOldIndex()];
171             if (parameter.getName().equals(parameter1.getName())) {
172               addParameterUsages(parameter1, result, info);
173             }
174           }
175         }
176       }
177     }
178   }
179
180   private PsiMethod[] findSimpleUsagesWithoutParameters(final PsiMethod method,
181                                                         final ArrayList<UsageInfo> result,
182                                                         boolean isToModifyArgs,
183                                                         boolean isToThrowExceptions,
184                                                         boolean isOriginal) {
185
186     GlobalSearchScope projectScope = GlobalSearchScope.projectScope(method.getProject());
187     PsiMethod[] overridingMethods = OverridingMethodsSearch.search(method, method.getUseScope(), true).toArray(PsiMethod.EMPTY_ARRAY);
188
189     for (PsiMethod overridingMethod : overridingMethods) {
190       if (GroovyFileType.GROOVY_LANGUAGE.equals(overridingMethod.getLanguage())) {
191         result.add(new OverriderUsageInfo(overridingMethod, method, isOriginal, isToModifyArgs, isToThrowExceptions));
192       }
193     }
194
195     boolean needToChangeCalls =
196       !myChangeInfo.isGenerateDelegate() && (myChangeInfo.isNameChanged() ||
197                                              myChangeInfo.isParameterSetOrOrderChanged() ||
198                                              myChangeInfo.isExceptionSetOrOrderChanged() ||
199                                              myChangeInfo.isVisibilityChanged()/*for checking inaccessible*/);
200     if (needToChangeCalls) {
201       PsiReference[] refs = MethodReferencesSearch.search(method, projectScope, true).toArray(PsiReference.EMPTY_ARRAY);
202       for (PsiReference ref : refs) {
203         PsiElement element = ref.getElement();
204
205         if (!GroovyFileType.GROOVY_LANGUAGE.equals(element.getLanguage())) continue;
206
207         boolean isToCatchExceptions = isToThrowExceptions && needToCatchExceptions(RefactoringUtil.getEnclosingMethod(element));
208         if (PsiUtil.isMethodUsage(element)) {
209           final GrArgumentList argList = PsiUtil.getArgumentsList(element);
210           //enum constant may not have argList
211           if (argList == null) {
212             if (element instanceof GrEnumConstant) {
213               result.add(new GrMethodCallUsageInfo(element, isToModifyArgs, isToCatchExceptions));
214             }
215           }
216           else {
217             result.add(new GrMethodCallUsageInfo(element, isToModifyArgs,isToCatchExceptions));
218           }
219         }
220         else if (element instanceof GrDocTagValueToken) {
221           result.add(new UsageInfo(ref.getElement()));
222         }
223         else if (element instanceof GrMethod && ((GrMethod)element).isConstructor()) {
224           DefaultConstructorImplicitUsageInfo implicitUsageInfo =
225             new DefaultConstructorImplicitUsageInfo((GrMethod)element, ((GrMethod)element).getContainingClass(), method);
226           result.add(implicitUsageInfo);
227         }
228         else if (element instanceof PsiClass) {
229           LOG.assertTrue(method.isConstructor());
230           final PsiClass psiClass = (PsiClass)element;
231           if (psiClass instanceof GrAnonymousClassDefinition) {
232             result.add(new GrMethodCallUsageInfo(element, isToModifyArgs, isToCatchExceptions));
233             continue;
234           }
235           /*if (!(myChangeInfo instanceof JavaChangeInfoImpl)) continue; todo propagate methods
236           if (shouldPropagateToNonPhysicalMethod(method, result, psiClass,
237                                                  ((JavaChangeInfoImpl)myChangeInfo).propagateParametersMethods)) {
238             continue;
239           }
240           if (shouldPropagateToNonPhysicalMethod(method, result, psiClass,
241                                                  ((JavaChangeInfoImpl)myChangeInfo).propagateExceptionsMethods)) {
242             continue;
243           }*/
244           result.add(new NoConstructorClassUsageInfo(psiClass));
245         }
246         else if (ref instanceof PsiCallReference) {
247           result.add(new CallReferenceUsageInfo((PsiCallReference)ref));
248         }
249         else {
250           result.add(new MoveRenameUsageInfo(element, ref, method));
251         }
252       }
253     }
254     else if (myChangeInfo.isParameterTypesChanged()) {
255       PsiReference[] refs = MethodReferencesSearch.search(method, projectScope, true).toArray(PsiReference.EMPTY_ARRAY);
256       for (PsiReference reference : refs) {
257         final PsiElement element = reference.getElement();
258         if (element instanceof GrDocTagValueToken) {
259           result.add(new UsageInfo(reference));
260         }
261       }
262     }
263
264     // Conflicts
265     if (method instanceof GrMethod) {
266       detectLocalsCollisionsInMethod((GrMethod)method, result, isOriginal);
267     }
268     for (final PsiMethod overridingMethod : overridingMethods) {
269       if (overridingMethod instanceof GrMethod) {
270         detectLocalsCollisionsInMethod((GrMethod)overridingMethod, result, isOriginal);
271       }
272     }
273
274     return overridingMethods;
275   }
276
277   private static void addParameterUsages(PsiParameter parameter, ArrayList<UsageInfo> results, ParameterInfo info) {
278     PsiManager manager = parameter.getManager();
279     GlobalSearchScope projectScope = GlobalSearchScope.projectScope(manager.getProject());
280     for (PsiReference psiReference : ReferencesSearch.search(parameter, projectScope, false)) {
281       PsiElement parmRef = psiReference.getElement();
282       UsageInfo usageInfo = new ChangeSignatureParameterUsageInfo(parmRef, parameter.getName(), info.getName());
283       results.add(usageInfo);
284     }
285     if (info.getName() != parameter.getName()) {
286       
287     }
288   }
289
290   private boolean needToCatchExceptions(PsiMethod caller) {
291     /*if (myChangeInfo instanceof JavaChangeInfoImpl) { //todo propagate methods
292       return myChangeInfo.isExceptionSetOrOrderChanged() &&
293              !((JavaChangeInfoImpl)myChangeInfo).propagateExceptionsMethods.contains(caller);
294     }
295     else {*/
296     return myChangeInfo.isExceptionSetOrOrderChanged();
297   }
298
299   private static class RenamedParameterCollidesWithLocalUsageInfo extends UnresolvableCollisionUsageInfo {
300     private final PsiElement myCollidingElement;
301     private final PsiMethod myMethod;
302
303     public RenamedParameterCollidesWithLocalUsageInfo(PsiParameter parameter, PsiElement collidingElement, PsiMethod method) {
304       super(parameter, collidingElement);
305       myCollidingElement = collidingElement;
306       myMethod = method;
307     }
308
309     public String getDescription() {
310       return RefactoringBundle.message("there.is.already.a.0.in.the.1.it.will.conflict.with.the.renamed.parameter",
311                                        RefactoringUIUtil.getDescription(myCollidingElement, true),
312                                        RefactoringUIUtil.getDescription(myMethod, true));
313     }
314   }
315
316 }