08ea1f00f740d24f9a09f15998696da776cb6d3b
[idea/community.git] / java / java-impl / src / com / intellij / refactoring / changeSignature / JavaChangeSignatureUsageProcessor.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 com.intellij.refactoring.changeSignature;
17
18 import com.intellij.codeInsight.ExceptionUtil;
19 import com.intellij.lang.StdLanguages;
20 import com.intellij.openapi.diagnostic.Logger;
21 import com.intellij.openapi.util.Ref;
22 import com.intellij.openapi.util.text.StringUtil;
23 import com.intellij.psi.*;
24 import com.intellij.psi.codeStyle.CodeStyleManager;
25 import com.intellij.psi.codeStyle.JavaCodeStyleManager;
26 import com.intellij.psi.codeStyle.VariableKind;
27 import com.intellij.psi.scope.processor.VariablesProcessor;
28 import com.intellij.psi.scope.util.PsiScopesUtil;
29 import com.intellij.psi.util.*;
30 import com.intellij.refactoring.RefactoringBundle;
31 import com.intellij.refactoring.rename.RenameUtil;
32 import com.intellij.refactoring.util.*;
33 import com.intellij.refactoring.util.usageInfo.DefaultConstructorImplicitUsageInfo;
34 import com.intellij.refactoring.util.usageInfo.NoConstructorClassUsageInfo;
35 import com.intellij.usageView.UsageInfo;
36 import com.intellij.util.IncorrectOperationException;
37 import com.intellij.util.VisibilityUtil;
38 import com.intellij.util.containers.HashSet;
39 import com.intellij.util.containers.MultiMap;
40 import org.jetbrains.annotations.Nullable;
41
42 import java.util.*;
43
44 /**
45  * @author Maxim.Medvedev
46  */
47 public class JavaChangeSignatureUsageProcessor implements ChangeSignatureUsageProcessor {
48   private static final Logger LOG = Logger.getInstance("#com.intellij.refactoring.changeSignature.JavaChangeSignatureUsageProcessor");
49
50   private static boolean isJavaUsage(UsageInfo info) {
51     final PsiElement element = info.getElement();
52     if (element == null) return false;
53     return element.getLanguage() == StdLanguages.JAVA;
54   }
55
56   public UsageInfo[] findUsages(ChangeInfo info) {
57     if (info instanceof JavaChangeInfo) {
58       return new JavaChangeSignatureUsageSearcher((JavaChangeInfo)info).findUsages();
59     }
60     else {
61       return UsageInfo.EMPTY_ARRAY;
62     }
63   }
64
65   public MultiMap<PsiElement, String> findConflicts(ChangeInfo info, Ref<UsageInfo[]> refUsages) {
66     if (info instanceof JavaChangeInfo) {
67       return new ConflictSearcher((JavaChangeInfo)info).findConflicts(refUsages);
68     }
69     else {
70       return new MultiMap<PsiElement, String>();
71     }
72   }
73
74   public boolean processUsage(ChangeInfo changeInfo, UsageInfo usage, boolean beforeMethodChange, UsageInfo[] usages) {
75     if (!isJavaUsage(usage)) return false;
76     if (!(changeInfo instanceof JavaChangeInfo)) return false;
77
78
79     if (beforeMethodChange) {
80       if (usage instanceof CallerUsageInfo) {
81         final CallerUsageInfo callerUsageInfo = (CallerUsageInfo)usage;
82         processCallerMethod((JavaChangeInfo)changeInfo, callerUsageInfo.getMethod(), null, callerUsageInfo.isToInsertParameter(),
83                             callerUsageInfo.isToInsertException());
84         return true;
85       }
86       else if (usage instanceof OverriderUsageInfo) {
87         OverriderUsageInfo info = (OverriderUsageInfo)usage;
88         final PsiMethod method = info.getElement();
89         final PsiMethod baseMethod = info.getBaseMethod();
90         if (info.isOriginalOverrider()) {
91           processPrimaryMethod((JavaChangeInfo)changeInfo, method, baseMethod, false);
92         }
93         else {
94           processCallerMethod((JavaChangeInfo)changeInfo, method, baseMethod, info.isToInsertArgs(), info.isToCatchExceptions());
95         }
96         return true;
97       }
98
99     }
100     else {
101       PsiElement element = usage.getElement();
102       LOG.assertTrue(element != null);
103
104       if (usage instanceof DefaultConstructorImplicitUsageInfo) {
105         final DefaultConstructorImplicitUsageInfo defConstructorUsage = (DefaultConstructorImplicitUsageInfo)usage;
106         PsiMethod constructor = defConstructorUsage.getConstructor();
107         if (!constructor.isPhysical()) {
108           final boolean toPropagate =
109             changeInfo instanceof JavaChangeInfoImpl && ((JavaChangeInfoImpl)changeInfo).propagateParametersMethods.remove(constructor);
110           final PsiClass containingClass = defConstructorUsage.getContainingClass();
111           constructor = (PsiMethod)containingClass.add(constructor);
112           PsiUtil.setModifierProperty(constructor, VisibilityUtil.getVisibilityModifier(containingClass.getModifierList()), true);
113           if (toPropagate) {
114             ((JavaChangeInfoImpl)changeInfo).propagateParametersMethods.add(constructor);
115           }
116         }
117         addSuperCall((JavaChangeInfo)changeInfo, constructor, defConstructorUsage.getBaseConstructor(), usages);
118         return true;
119       }
120       else if (usage instanceof NoConstructorClassUsageInfo) {
121         addDefaultConstructor(((JavaChangeInfo)changeInfo), ((NoConstructorClassUsageInfo)usage).getPsiClass(), usages);
122         return true;
123       }
124       else if (usage instanceof MethodCallUsageInfo) {
125         final MethodCallUsageInfo methodCallInfo = (MethodCallUsageInfo)usage;
126         processMethodUsage(methodCallInfo.getElement(), (JavaChangeInfo)changeInfo, methodCallInfo.isToChangeArguments(),
127                            methodCallInfo.isToCatchExceptions(), methodCallInfo.getReferencedMethod(), methodCallInfo.getSubstitutor(), usages);
128         return true;
129       }
130       else if (usage instanceof ChangeSignatureParameterUsageInfo) {
131         String newName = ((ChangeSignatureParameterUsageInfo)usage).newParameterName;
132         String oldName = ((ChangeSignatureParameterUsageInfo)usage).oldParameterName;
133         processParameterUsage((PsiReferenceExpression)element, oldName, newName);
134         return true;
135       }
136       else if (usage instanceof CallReferenceUsageInfo) {
137         ((CallReferenceUsageInfo)usage).getReference().handleChangeSignature(changeInfo);
138         return true;
139       }
140       else if (element instanceof PsiEnumConstant) {
141         fixActualArgumentsList(((PsiEnumConstant)element).getArgumentList(), (JavaChangeInfo)changeInfo, true, PsiSubstitutor.EMPTY);
142         return true;
143       }
144       else if (!(usage instanceof OverriderUsageInfo)) {
145         PsiReference reference = usage instanceof MoveRenameUsageInfo ? usage.getReference() : element.getReference();
146         if (reference != null) {
147           PsiElement target = changeInfo.getMethod();
148           if (target != null) {
149             reference.bindToElement(target);
150           }
151         }
152       }
153     }
154     return false;
155   }
156
157   private static void processParameterUsage(PsiReferenceExpression ref, String oldName, String newName)
158     throws IncorrectOperationException {
159
160     PsiElement last = ref.getReferenceNameElement();
161     if (last instanceof PsiIdentifier && last.getText().equals(oldName)) {
162       PsiElementFactory factory = JavaPsiFacade.getInstance(ref.getProject()).getElementFactory();
163       PsiIdentifier newNameIdentifier = factory.createIdentifier(newName);
164       last.replace(newNameIdentifier);
165     }
166   }
167
168
169   private static void addDefaultConstructor(JavaChangeInfo changeInfo, PsiClass aClass, final UsageInfo[] usages)
170     throws IncorrectOperationException {
171     if (!(aClass instanceof PsiAnonymousClass)) {
172       PsiElementFactory factory = JavaPsiFacade.getElementFactory(aClass.getProject());
173       PsiMethod defaultConstructor = factory.createMethodFromText(aClass.getName() + "(){}", aClass);
174       defaultConstructor = (PsiMethod)CodeStyleManager.getInstance(aClass.getProject()).reformat(defaultConstructor);
175       defaultConstructor = (PsiMethod)aClass.add(defaultConstructor);
176       PsiUtil.setModifierProperty(defaultConstructor, VisibilityUtil.getVisibilityModifier(aClass.getModifierList()), true);
177       addSuperCall(changeInfo, defaultConstructor, null, usages);
178     }
179     else {
180       final PsiElement parent = aClass.getParent();
181       if (parent instanceof PsiNewExpression) {
182         final PsiExpressionList argumentList = ((PsiNewExpression)parent).getArgumentList();
183         final PsiClass baseClass = changeInfo.getMethod().getContainingClass();
184         final PsiSubstitutor substitutor = TypeConversionUtil.getSuperClassSubstitutor(baseClass, aClass, PsiSubstitutor.EMPTY);
185         fixActualArgumentsList(argumentList, changeInfo, true, substitutor);
186       }
187     }
188   }
189
190   private static void addSuperCall(JavaChangeInfo changeInfo, PsiMethod constructor, PsiMethod callee, final UsageInfo[] usages)
191     throws IncorrectOperationException {
192     final PsiElementFactory factory = JavaPsiFacade.getElementFactory(constructor.getProject());
193     PsiExpressionStatement superCall = (PsiExpressionStatement)factory.createStatementFromText("super();", constructor);
194     PsiCodeBlock body = constructor.getBody();
195     assert body != null;
196     PsiStatement[] statements = body.getStatements();
197     if (statements.length > 0) {
198       superCall = (PsiExpressionStatement)body.addBefore(superCall, statements[0]);
199     }
200     else {
201       superCall = (PsiExpressionStatement)body.add(superCall);
202     }
203     PsiMethodCallExpression callExpression = (PsiMethodCallExpression)superCall.getExpression();
204     final PsiClass aClass = constructor.getContainingClass();
205     final PsiClass baseClass = changeInfo.getMethod().getContainingClass();
206     final PsiSubstitutor substitutor = TypeConversionUtil.getSuperClassSubstitutor(baseClass, aClass, PsiSubstitutor.EMPTY);
207     processMethodUsage(callExpression.getMethodExpression(), changeInfo, true, false, callee, substitutor, usages);
208   }
209
210   private static void processMethodUsage(PsiElement ref,
211                                   JavaChangeInfo changeInfo,
212                                   boolean toChangeArguments,
213                                   boolean toCatchExceptions,
214                                   PsiMethod callee, PsiSubstitutor subsitutor, final UsageInfo[] usages) throws IncorrectOperationException {
215     if (changeInfo.isNameChanged()) {
216       if (ref instanceof PsiJavaCodeReferenceElement) {
217         PsiElement last = ((PsiJavaCodeReferenceElement)ref).getReferenceNameElement();
218         if (last instanceof PsiIdentifier && last.getText().equals(changeInfo.getOldName())) {
219           last.replace(changeInfo.getNewNameIdentifier());
220         }
221       }
222     }
223
224     final PsiMethod caller = RefactoringUtil.getEnclosingMethod(ref);
225     if (toChangeArguments) {
226       final PsiExpressionList list = RefactoringUtil.getArgumentListByMethodReference(ref);
227       boolean toInsertDefaultValue = !(changeInfo instanceof JavaChangeInfoImpl) || !((JavaChangeInfoImpl)changeInfo).propagateParametersMethods.contains(caller);
228       if (toInsertDefaultValue && ref instanceof PsiReferenceExpression) {
229         final PsiExpression qualifierExpression = ((PsiReferenceExpression)ref).getQualifierExpression();
230         if (qualifierExpression instanceof PsiSuperExpression && callerSignatureIsAboutToChangeToo(caller, usages)) {
231           toInsertDefaultValue = false;
232         }
233       }
234
235       fixActualArgumentsList(list, changeInfo, toInsertDefaultValue, subsitutor);
236     }
237
238     if (toCatchExceptions) {
239       if (!(ref instanceof PsiReferenceExpression &&
240             ((PsiReferenceExpression)ref).getQualifierExpression() instanceof PsiSuperExpression)) {
241         if (needToCatchExceptions(changeInfo, caller)) {
242           PsiClassType[] newExceptions =
243             callee != null ? getCalleeChangedExceptionInfo(callee) : getPrimaryChangedExceptionInfo(changeInfo);
244           fixExceptions(ref, newExceptions);
245         }
246       }
247     }
248   }
249
250   private static boolean callerSignatureIsAboutToChangeToo(final PsiMethod caller, final UsageInfo[] usages) {
251     for (UsageInfo usage : usages) {
252       if (usage instanceof MethodCallUsageInfo &&
253           MethodSignatureUtil.isSuperMethod(((MethodCallUsageInfo)usage).getReferencedMethod(), caller)) {
254         return true;
255       }
256     }
257     return false;
258   }
259
260   private static PsiClassType[] getCalleeChangedExceptionInfo(final PsiMethod callee) {
261     return callee.getThrowsList().getReferencedTypes(); //Callee method's throws list is already modified!
262   }
263
264   private static void fixExceptions(PsiElement ref, PsiClassType[] newExceptions) throws IncorrectOperationException {
265     //methods' throws lists are already modified, may use ExceptionUtil.collectUnhandledExceptions
266     newExceptions = filterCheckedExceptions(newExceptions);
267
268     PsiElement context = PsiTreeUtil.getParentOfType(ref, PsiTryStatement.class, PsiMethod.class);
269     if (context instanceof PsiTryStatement) {
270       PsiTryStatement tryStatement = (PsiTryStatement)context;
271       PsiCodeBlock tryBlock = tryStatement.getTryBlock();
272
273       //Remove unused catches
274       Collection<PsiClassType> classes = ExceptionUtil.collectUnhandledExceptions(tryBlock, tryBlock);
275       PsiParameter[] catchParameters = tryStatement.getCatchBlockParameters();
276       for (PsiParameter parameter : catchParameters) {
277         final PsiType caughtType = parameter.getType();
278
279         if (!(caughtType instanceof PsiClassType)) continue;
280         if (ExceptionUtil.isUncheckedExceptionOrSuperclass((PsiClassType)caughtType)) continue;
281
282         if (!isCatchParameterRedundant((PsiClassType)caughtType, classes)) continue;
283         parameter.getParent().delete(); //delete catch section
284       }
285
286       PsiClassType[] exceptionsToAdd = filterUnhandledExceptions(newExceptions, tryBlock);
287       addExceptions(exceptionsToAdd, tryStatement);
288
289       adjustPossibleEmptyTryStatement(tryStatement);
290     }
291     else {
292       newExceptions = filterUnhandledExceptions(newExceptions, ref);
293       if (newExceptions.length > 0) {
294         //Add new try statement
295         PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(ref.getProject());
296         PsiTryStatement tryStatement = (PsiTryStatement)elementFactory.createStatementFromText("try {} catch (Exception e) {}", null);
297         PsiStatement anchor = PsiTreeUtil.getParentOfType(ref, PsiStatement.class);
298         LOG.assertTrue(anchor != null);
299         tryStatement.getTryBlock().add(anchor);
300         tryStatement = (PsiTryStatement)anchor.getParent().addAfter(tryStatement, anchor);
301
302         addExceptions(newExceptions, tryStatement);
303         anchor.delete();
304         tryStatement.getCatchSections()[0].delete(); //Delete dummy catch section
305       }
306     }
307   }
308
309   private static PsiClassType[] filterCheckedExceptions(PsiClassType[] exceptions) {
310     List<PsiClassType> result = new ArrayList<PsiClassType>();
311     for (PsiClassType exceptionType : exceptions) {
312       if (!ExceptionUtil.isUncheckedException(exceptionType)) result.add(exceptionType);
313     }
314     return result.toArray(new PsiClassType[result.size()]);
315   }
316
317   private static void adjustPossibleEmptyTryStatement(PsiTryStatement tryStatement) throws IncorrectOperationException {
318     PsiCodeBlock tryBlock = tryStatement.getTryBlock();
319     if (tryBlock != null) {
320       if (tryStatement.getCatchSections().length == 0 &&
321           tryStatement.getFinallyBlock() == null) {
322         PsiElement firstBodyElement = tryBlock.getFirstBodyElement();
323         if (firstBodyElement != null) {
324           tryStatement.getParent().addRangeAfter(firstBodyElement, tryBlock.getLastBodyElement(), tryStatement);
325         }
326         tryStatement.delete();
327       }
328     }
329   }
330
331   private static void addExceptions(PsiClassType[] exceptionsToAdd, PsiTryStatement tryStatement) throws IncorrectOperationException {
332     for (PsiClassType type : exceptionsToAdd) {
333       final JavaCodeStyleManager styleManager = JavaCodeStyleManager.getInstance(tryStatement.getProject());
334       String name = styleManager.suggestVariableName(VariableKind.PARAMETER, null, null, type).names[0];
335       name = styleManager.suggestUniqueVariableName(name, tryStatement, false);
336
337       PsiCatchSection catchSection =
338         JavaPsiFacade.getInstance(tryStatement.getProject()).getElementFactory().createCatchSection(type, name, tryStatement);
339       tryStatement.add(catchSection);
340     }
341   }
342
343   private static PsiClassType[] filterUnhandledExceptions(PsiClassType[] exceptions, PsiElement place) {
344     List<PsiClassType> result = new ArrayList<PsiClassType>();
345     for (PsiClassType exception : exceptions) {
346       if (!ExceptionUtil.isHandled(exception, place)) result.add(exception);
347     }
348     return result.toArray(new PsiClassType[result.size()]);
349   }
350
351   private static boolean isCatchParameterRedundant(PsiClassType catchParamType, Collection<PsiClassType> thrownTypes) {
352     for (PsiType exceptionType : thrownTypes) {
353       if (exceptionType.isConvertibleFrom(catchParamType)) return false;
354     }
355     return true;
356   }
357
358   //This methods works equally well for primary usages as well as for propagated callers' usages
359   private static void fixActualArgumentsList(PsiExpressionList list,
360                                              JavaChangeInfo changeInfo,
361                                              boolean toInsertDefaultValue, PsiSubstitutor substitutor) throws IncorrectOperationException {
362     final PsiElementFactory factory = JavaPsiFacade.getInstance(list.getProject()).getElementFactory();
363     if (changeInfo.isParameterSetOrOrderChanged()) {
364       if (changeInfo instanceof JavaChangeInfoImpl && ((JavaChangeInfoImpl)changeInfo).isPropagationEnabled) {
365         final ParameterInfoImpl[] createdParmsInfo = ((JavaChangeInfoImpl)changeInfo).getCreatedParmsInfoWithoutVarargs();
366         for (ParameterInfoImpl info : createdParmsInfo) {
367           PsiExpression newArg;
368           if (toInsertDefaultValue) {
369             newArg = createDefaultValue(changeInfo, factory, info, list);
370           }
371           else {
372             newArg = factory.createExpressionFromText(info.getName(), list);
373           }
374           list.add(newArg);
375         }
376       }
377       else {
378         final PsiExpression[] args = list.getExpressions();
379         final int nonVarargCount = getNonVarargCount(changeInfo, args);
380         final int varargCount = args.length - nonVarargCount;
381         PsiExpression[] newVarargInitializers = null;
382
383         final int newArgsLength;
384         final int newNonVarargCount;
385         final JavaParameterInfo[] newParms = changeInfo.getNewParameters();
386         if (changeInfo.isArrayToVarargs()) {
387           newNonVarargCount = newParms.length - 1;
388           final JavaParameterInfo lastNewParm = newParms[newParms.length - 1];
389           final PsiExpression arrayToConvert = args[lastNewParm.getOldIndex()];
390           if (arrayToConvert instanceof PsiNewExpression) {
391             final PsiNewExpression expression = (PsiNewExpression)arrayToConvert;
392             final PsiArrayInitializerExpression arrayInitializer = expression.getArrayInitializer();
393             if (arrayInitializer != null) {
394               newVarargInitializers = arrayInitializer.getInitializers();
395             }
396           }
397           newArgsLength = newVarargInitializers == null ? newParms.length : newNonVarargCount + newVarargInitializers.length;
398         }
399         else if (changeInfo.isRetainsVarargs()) {
400           newNonVarargCount = newParms.length - 1;
401           newArgsLength = newNonVarargCount + varargCount;
402         }
403         else if (changeInfo.isObtainsVarags()) {
404           newNonVarargCount = newParms.length - 1;
405           newArgsLength = newNonVarargCount;
406         }
407         else {
408           newNonVarargCount = newParms.length;
409           newArgsLength = newParms.length;
410         }
411
412         String[] oldVarargs = null;
413         if (changeInfo.wasVararg() && !changeInfo.isRetainsVarargs()) {
414           oldVarargs = new String[varargCount];
415           for (int i = nonVarargCount; i < args.length; i++) {
416             oldVarargs[i - nonVarargCount] = args[i].getText();
417           }
418         }
419
420         final PsiExpression[] newArgs = new PsiExpression[newArgsLength];
421         for (int i = 0; i < newNonVarargCount; i++) {
422           if (newParms[i].getOldIndex() == nonVarargCount && oldVarargs != null) {
423             PsiType type = newParms[i].createType(changeInfo.getMethod(), list.getManager());
424             if (type instanceof PsiArrayType) {
425               type = substitutor.substitute(type);
426               type = TypeConversionUtil.erasure(type);
427               String typeText = type.getCanonicalText();
428               if (type instanceof PsiEllipsisType) {
429                 typeText = typeText.replace("...", "[]");
430               }
431               String text = "new " + typeText + "{" + StringUtil.join(oldVarargs, ",") + "}";
432               newArgs[i] = factory.createExpressionFromText(text, changeInfo.getMethod());
433               continue;
434             }
435           }
436           newArgs[i] = createActualArgument(changeInfo, list, newParms[i], toInsertDefaultValue, args);
437         }
438         if (changeInfo.isArrayToVarargs()) {
439           if (newVarargInitializers == null) {
440             newArgs[newNonVarargCount] =
441               createActualArgument(changeInfo, list, newParms[newNonVarargCount], toInsertDefaultValue, args);
442           }
443           else {
444             System.arraycopy(newVarargInitializers, 0, newArgs, newNonVarargCount, newVarargInitializers.length);
445           }
446         }
447         else {
448           final int newVarargCount = newArgsLength - newNonVarargCount;
449           LOG.assertTrue(newVarargCount == 0 || newVarargCount == varargCount);
450           System.arraycopy(args, nonVarargCount, newArgs, newNonVarargCount, newVarargCount);
451         }
452         ChangeSignatureUtil.synchronizeList(list, Arrays.asList(newArgs), ExpressionList.INSTANCE, changeInfo.toRemoveParm());
453       }
454     }
455   }
456
457   private static int getNonVarargCount(JavaChangeInfo changeInfo, PsiExpression[] args) {
458     if (!changeInfo.wasVararg()) return args.length;
459     return changeInfo.getOldParameterTypes().length - 1;
460   }
461
462
463   @Nullable
464   private static PsiExpression createActualArgument(JavaChangeInfo changeInfo,
465                                                     final PsiExpressionList list,
466                                                     final JavaParameterInfo info,
467                                                     final boolean toInsertDefaultValue,
468                                                     final PsiExpression[] args) throws IncorrectOperationException {
469     final PsiElementFactory factory = JavaPsiFacade.getInstance(list.getProject()).getElementFactory();
470     final int index = info.getOldIndex();
471     if (index >= 0) {
472       return args[index];
473     }
474     else {
475       if (toInsertDefaultValue) {
476         return createDefaultValue(changeInfo, factory, info, list);
477       }
478       else {
479         return factory.createExpressionFromText(info.getName(), list);
480       }
481     }
482   }
483
484   @Nullable
485   private static PsiExpression createDefaultValue(JavaChangeInfo changeInfo,
486                                                   final PsiElementFactory factory,
487                                                   final JavaParameterInfo info,
488                                                   final PsiExpressionList list)
489     throws IncorrectOperationException {
490     if (info.isUseAnySingleVariable()) {
491       final PsiResolveHelper resolveHelper = JavaPsiFacade.getInstance(list.getProject()).getResolveHelper();
492       final PsiType type = info.getTypeWrapper().getType(changeInfo.getMethod(), list.getManager());
493       final VariablesProcessor processor = new VariablesProcessor(false) {
494         protected boolean check(PsiVariable var, ResolveState state) {
495           if (var instanceof PsiField && !resolveHelper.isAccessible((PsiField)var, list, null)) return false;
496           final PsiType varType = state.get(PsiSubstitutor.KEY).substitute(var.getType());
497           return type.isAssignableFrom(varType);
498         }
499
500         public boolean execute(PsiElement pe, ResolveState state) {
501           super.execute(pe, state);
502           return size() < 2;
503         }
504       };
505       PsiScopesUtil.treeWalkUp(processor, list, null);
506       if (processor.size() == 1) {
507         final PsiVariable result = processor.getResult(0);
508         return factory.createExpressionFromText(result.getName(), list);
509       }
510     }
511     final PsiCallExpression callExpression = PsiTreeUtil.getParentOfType(list, PsiCallExpression.class);
512     return callExpression != null ? info.getValue(callExpression) : factory.createExpressionFromText(info.getDefaultValue(), list);
513   }
514
515
516   public boolean processPrimaryMethod(ChangeInfo changeInfo) {
517     if (!StdLanguages.JAVA.equals(changeInfo.getLanguage()) || !(changeInfo instanceof JavaChangeInfo)) return false;
518     final PsiElement element = changeInfo.getMethod();
519     LOG.assertTrue(element instanceof PsiMethod);
520     if (changeInfo.isGenerateDelegate()) {
521       generateDelegate((JavaChangeInfo)changeInfo);
522     }
523     processPrimaryMethod((JavaChangeInfo)changeInfo, (PsiMethod)element, null, true);
524     return true;
525   }
526
527   private static void generateDelegate(JavaChangeInfo changeInfo) throws IncorrectOperationException {
528     final PsiMethod delegate = (PsiMethod)changeInfo.getMethod().copy();
529     final PsiClass targetClass = changeInfo.getMethod().getContainingClass();
530     LOG.assertTrue(!targetClass.isInterface());
531     PsiElementFactory factory = JavaPsiFacade.getElementFactory(targetClass.getProject());
532     ChangeSignatureProcessor.makeEmptyBody(factory, delegate);
533     final PsiCallExpression callExpression = ChangeSignatureProcessor.addDelegatingCallTemplate(delegate, changeInfo.getNewName());
534     addDelegateArguments(changeInfo, factory, callExpression);
535     targetClass.addBefore(delegate, changeInfo.getMethod());
536   }
537
538
539   private static void addDelegateArguments(JavaChangeInfo changeInfo, PsiElementFactory factory, final PsiCallExpression callExpression) throws IncorrectOperationException {
540     final JavaParameterInfo[] newParms = changeInfo.getNewParameters();
541     final String[] oldParameterNames = changeInfo.getOldParameterNames();
542     for (int i = 0; i < newParms.length; i++) {
543       JavaParameterInfo newParm = newParms[i];
544       final PsiExpression actualArg;
545       if (newParm.getOldIndex() >= 0) {
546         actualArg = factory.createExpressionFromText(oldParameterNames[newParm.getOldIndex()], callExpression);
547       }
548       else {
549         actualArg = changeInfo.getValue(i, callExpression);
550       }
551       callExpression.getArgumentList().add(actualArg);
552     }
553   }
554
555   private static void processPrimaryMethod(JavaChangeInfo changeInfo, PsiMethod method,
556                                            PsiMethod baseMethod,
557                                            boolean isOriginal) throws IncorrectOperationException {
558     PsiElementFactory factory = JavaPsiFacade.getInstance(method.getProject()).getElementFactory();
559
560     if (changeInfo.isVisibilityChanged()) {
561       PsiModifierList modifierList = method.getModifierList();
562       final String highestVisibility = isOriginal
563                                        ? changeInfo.getNewVisibility()
564                                        : VisibilityUtil.getHighestVisibility(changeInfo.getNewVisibility(),
565                                                                              VisibilityUtil.getVisibilityModifier(modifierList));
566       VisibilityUtil.setVisibility(modifierList, highestVisibility);
567     }
568
569     if (changeInfo.isNameChanged()) {
570       String newName = baseMethod == null ? changeInfo.getNewName() :
571                        RefactoringUtil.suggestNewOverriderName(method.getName(), baseMethod.getName(), changeInfo.getNewName());
572
573       if (newName != null && !newName.equals(method.getName())) {
574         final PsiIdentifier nameId = method.getNameIdentifier();
575         assert nameId != null;
576         nameId.replace(JavaPsiFacade.getInstance(method.getProject()).getElementFactory().createIdentifier(newName));
577       }
578     }
579
580     final PsiSubstitutor substitutor = baseMethod == null ? PsiSubstitutor.EMPTY : calculateSubstitutor(method, baseMethod);
581
582     if (changeInfo.isReturnTypeChanged()) {
583       PsiType newTypeElement = changeInfo.getNewReturnType().getType(changeInfo.getMethod().getParameterList(), method.getManager());
584       final PsiType returnType = substitutor.substitute(newTypeElement);
585       // don't modify return type for non-Java overriders (EJB)
586       if (method.getName().equals(changeInfo.getNewName())) {
587         final PsiTypeElement typeElement = method.getReturnTypeElement();
588         if (typeElement != null) {
589           typeElement.replace(factory.createTypeElement(returnType));
590         }
591       }
592     }
593
594     PsiParameterList list = method.getParameterList();
595     PsiParameter[] parameters = list.getParameters();
596
597     final JavaParameterInfo[] parameterInfos = changeInfo.getNewParameters();
598     PsiParameter[] newParms = new PsiParameter[parameterInfos.length];
599     final String[] oldParameterNames = changeInfo.getOldParameterNames();
600     final String[] oldParameterTypes = changeInfo.getOldParameterTypes();
601     for (int i = 0; i < newParms.length; i++) {
602       JavaParameterInfo info = parameterInfos[i];
603       int index = info.getOldIndex();
604       if (index >= 0) {
605         PsiParameter parameter = parameters[index];
606         newParms[i] = parameter;
607
608         String oldName = oldParameterNames[index];
609         if (!oldName.equals(info.getName()) && oldName.equals(parameter.getName())) {
610           PsiIdentifier newIdentifier = factory.createIdentifier(info.getName());
611           parameter.getNameIdentifier().replace(newIdentifier);
612         }
613
614         String oldType = oldParameterTypes[index];
615         if (!oldType.equals(info.getTypeText())) {
616           parameter.normalizeDeclaration();
617           PsiType newType = substitutor.substitute(info.createType(changeInfo.getMethod().getParameterList(), method.getManager()));
618
619           parameter.getTypeElement().replace(factory.createTypeElement(newType));
620         }
621       }
622       else {
623         newParms[i] = createNewParameter(changeInfo, info, substitutor);
624       }
625     }
626
627
628     resolveParameterVsFieldsConflicts(newParms, method, list, changeInfo.toRemoveParm());
629     fixJavadocsForChangedMethod(method, changeInfo);
630     if (changeInfo.isExceptionSetOrOrderChanged()) {
631       final PsiClassType[] newExceptions = getPrimaryChangedExceptionInfo(changeInfo);
632       fixPrimaryThrowsLists(method, newExceptions);
633     }
634   }
635
636   private static PsiClassType[] getPrimaryChangedExceptionInfo(JavaChangeInfo changeInfo) throws IncorrectOperationException {
637     final ThrownExceptionInfo[] newExceptionInfos = changeInfo.getNewExceptions();
638     PsiClassType[] newExceptions = new PsiClassType[newExceptionInfos.length];
639     final PsiMethod method = changeInfo.getMethod();
640     for (int i = 0; i < newExceptions.length; i++) {
641       newExceptions[i] =
642         (PsiClassType)newExceptionInfos[i].createType(method, method.getManager()); //context really does not matter here
643     }
644     return newExceptions;
645   }
646
647
648   private static void processCallerMethod(JavaChangeInfo changeInfo, PsiMethod caller,
649                                           PsiMethod baseMethod,
650                                           boolean toInsertParams,
651                                           boolean toInsertThrows) throws IncorrectOperationException {
652     LOG.assertTrue(toInsertParams || toInsertThrows);
653     if (toInsertParams) {
654       List<PsiParameter> newParameters = new ArrayList<PsiParameter>();
655       newParameters.addAll(Arrays.asList(caller.getParameterList().getParameters()));
656       final JavaParameterInfo[] primaryNewParms = changeInfo.getNewParameters();
657       PsiSubstitutor substitutor = baseMethod == null ? PsiSubstitutor.EMPTY : calculateSubstitutor(caller, baseMethod);
658       for (JavaParameterInfo info : primaryNewParms) {
659         if (info.getOldIndex() < 0) newParameters.add(createNewParameter(changeInfo, info, substitutor));
660       }
661       PsiParameter[] arrayed = newParameters.toArray(new PsiParameter[newParameters.size()]);
662       boolean[] toRemoveParm = new boolean[arrayed.length];
663       Arrays.fill(toRemoveParm, false);
664       resolveParameterVsFieldsConflicts(arrayed, caller, caller.getParameterList(), toRemoveParm);
665     }
666
667     if (toInsertThrows) {
668       List<PsiJavaCodeReferenceElement> newThrowns = new ArrayList<PsiJavaCodeReferenceElement>();
669       final PsiReferenceList throwsList = caller.getThrowsList();
670       newThrowns.addAll(Arrays.asList(throwsList.getReferenceElements()));
671       final ThrownExceptionInfo[] primaryNewExns = changeInfo.getNewExceptions();
672       for (ThrownExceptionInfo thrownExceptionInfo : primaryNewExns) {
673         if (thrownExceptionInfo.getOldIndex() < 0) {
674           final PsiClassType type = (PsiClassType)thrownExceptionInfo.createType(caller, caller.getManager());
675           final PsiJavaCodeReferenceElement ref =
676             JavaPsiFacade.getInstance(caller.getProject()).getElementFactory().createReferenceElementByType(type);
677           newThrowns.add(ref);
678         }
679       }
680       PsiJavaCodeReferenceElement[] arrayed = newThrowns.toArray(new PsiJavaCodeReferenceElement[newThrowns.size()]);
681       boolean[] toRemoveParm = new boolean[arrayed.length];
682       Arrays.fill(toRemoveParm, false);
683       ChangeSignatureUtil.synchronizeList(throwsList, Arrays.asList(arrayed), ThrowsList.INSTANCE, toRemoveParm);
684     }
685   }
686
687   private static void fixPrimaryThrowsLists(PsiMethod method, PsiClassType[] newExceptions) throws IncorrectOperationException {
688     PsiElementFactory elementFactory = JavaPsiFacade.getInstance(method.getProject()).getElementFactory();
689     PsiJavaCodeReferenceElement[] refs = new PsiJavaCodeReferenceElement[newExceptions.length];
690     for (int i = 0; i < refs.length; i++) {
691       refs[i] = elementFactory.createReferenceElementByType(newExceptions[i]);
692     }
693     PsiReferenceList throwsList = elementFactory.createReferenceList(refs);
694
695     PsiReferenceList methodThrowsList = (PsiReferenceList)method.getThrowsList().replace(throwsList);
696     methodThrowsList = (PsiReferenceList)JavaCodeStyleManager.getInstance(method.getProject()).shortenClassReferences(methodThrowsList);
697     method.getManager().getCodeStyleManager().reformatRange(method, method.getParameterList().getTextRange().getEndOffset(),
698                                                             methodThrowsList.getTextRange().getEndOffset());
699   }
700
701   private static void fixJavadocsForChangedMethod(PsiMethod method, JavaChangeInfo changeInfo) throws IncorrectOperationException {
702     final PsiParameter[] parameters = method.getParameterList().getParameters();
703     final JavaParameterInfo[] newParms = changeInfo.getNewParameters();
704     LOG.assertTrue(parameters.length == newParms.length);
705     final Set<PsiParameter> newParameters = new HashSet<PsiParameter>();
706     final String[] oldParameterNames = changeInfo.getOldParameterNames();
707     for (int i = 0; i < newParms.length; i++) {
708       JavaParameterInfo newParm = newParms[i];
709       if (newParm.getOldIndex() < 0 ||
710           !newParm.getName().equals(oldParameterNames[newParm.getOldIndex()])) {
711         newParameters.add(parameters[i]);
712       }
713     }
714     RefactoringUtil.fixJavadocsForParams(method, newParameters);
715   }
716
717   private static PsiParameter createNewParameter(JavaChangeInfo changeInfo, JavaParameterInfo newParm,
718                                                  PsiSubstitutor substitutor) throws IncorrectOperationException {
719     final PsiParameterList list = changeInfo.getMethod().getParameterList();
720     final PsiElementFactory factory = JavaPsiFacade.getInstance(list.getProject()).getElementFactory();
721     final PsiType type = substitutor.substitute(newParm.createType(list, list.getManager()));
722     return factory.createParameter(newParm.getName(), type);
723   }
724
725   private static void resolveParameterVsFieldsConflicts(final PsiParameter[] newParms,
726                                                         final PsiMethod method,
727                                                         final PsiParameterList list,
728                                                         boolean[] toRemoveParm) throws IncorrectOperationException {
729     List<FieldConflictsResolver> conflictResolvers = new ArrayList<FieldConflictsResolver>();
730     for (PsiParameter parameter : newParms) {
731       conflictResolvers.add(new FieldConflictsResolver(parameter.getName(), method.getBody()));
732     }
733     ChangeSignatureUtil.synchronizeList(list, Arrays.asList(newParms), ParameterList.INSTANCE, toRemoveParm);
734     JavaCodeStyleManager.getInstance(list.getProject()).shortenClassReferences(list);
735     for (FieldConflictsResolver fieldConflictsResolver : conflictResolvers) {
736       fieldConflictsResolver.fix();
737     }
738   }
739
740   private static PsiSubstitutor calculateSubstitutor(PsiMethod derivedMethod, PsiMethod baseMethod) {
741     PsiSubstitutor substitutor;
742     if (derivedMethod.getManager().areElementsEquivalent(derivedMethod, baseMethod)) {
743       substitutor = PsiSubstitutor.EMPTY;
744     }
745     else {
746       final PsiClass baseClass = baseMethod.getContainingClass();
747       final PsiClass derivedClass = derivedMethod.getContainingClass();
748       if (baseClass != null && derivedClass != null && InheritanceUtil.isInheritorOrSelf(derivedClass, baseClass, true)) {
749         final PsiSubstitutor superClassSubstitutor =
750           TypeConversionUtil.getSuperClassSubstitutor(baseClass, derivedClass, PsiSubstitutor.EMPTY);
751         final MethodSignature superMethodSignature = baseMethod.getSignature(superClassSubstitutor);
752         final MethodSignature methodSignature = derivedMethod.getSignature(PsiSubstitutor.EMPTY);
753         final PsiSubstitutor superMethodSubstitutor =
754           MethodSignatureUtil.getSuperMethodSignatureSubstitutor(methodSignature, superMethodSignature);
755         substitutor = superMethodSubstitutor != null ? superMethodSubstitutor : superClassSubstitutor;
756       }
757       else {
758         substitutor = PsiSubstitutor.EMPTY;
759       }
760     }
761     return substitutor;
762   }
763
764   private static boolean needToCatchExceptions(JavaChangeInfo changeInfo, PsiMethod caller) {
765     return changeInfo.isExceptionSetOrOrderChanged() &&
766            !(changeInfo instanceof JavaChangeInfoImpl && ((JavaChangeInfoImpl)changeInfo).propagateExceptionsMethods.contains(caller));
767   }
768
769   private static class ParameterList implements ChangeSignatureUtil.ChildrenGenerator<PsiParameterList, PsiParameter> {
770     public static final ParameterList INSTANCE = new ParameterList();
771
772     public List<PsiParameter> getChildren(PsiParameterList psiParameterList) {
773       return Arrays.asList(psiParameterList.getParameters());
774     }
775   }
776
777   private static class ThrowsList implements ChangeSignatureUtil.ChildrenGenerator<PsiReferenceList, PsiJavaCodeReferenceElement> {
778     public static final ThrowsList INSTANCE = new ThrowsList();
779
780     public List<PsiJavaCodeReferenceElement> getChildren(PsiReferenceList throwsList) {
781       return Arrays.asList(throwsList.getReferenceElements());
782     }
783   }
784
785   private static class ConflictSearcher {
786     private final JavaChangeInfo myChangeInfo;
787
788     private ConflictSearcher(JavaChangeInfo changeInfo) {
789       this.myChangeInfo = changeInfo;
790     }
791
792     public MultiMap<PsiElement, String> findConflicts(Ref<UsageInfo[]> refUsages) {
793       MultiMap<PsiElement, String> conflictDescriptions = new MultiMap<PsiElement, String>();
794       addMethodConflicts(conflictDescriptions);
795       Set<UsageInfo> usagesSet = new HashSet<UsageInfo>(Arrays.asList(refUsages.get()));
796       RenameUtil.removeConflictUsages(usagesSet);
797       if (myChangeInfo.isVisibilityChanged()) {
798         try {
799           addInaccessibilityDescriptions(usagesSet, conflictDescriptions);
800         }
801         catch (IncorrectOperationException e) {
802           LOG.error(e);
803         }
804       }
805
806       return conflictDescriptions;
807     }
808
809     private boolean needToChangeCalls() {
810       return myChangeInfo.isNameChanged() || myChangeInfo.isParameterSetOrOrderChanged() || myChangeInfo.isExceptionSetOrOrderChanged();
811     }
812
813
814     private void addInaccessibilityDescriptions(Set<UsageInfo> usages, MultiMap<PsiElement, String> conflictDescriptions)
815       throws IncorrectOperationException {
816       PsiMethod method = myChangeInfo.getMethod();
817       PsiModifierList modifierList = (PsiModifierList)method.getModifierList().copy();
818       VisibilityUtil.setVisibility(modifierList, myChangeInfo.getNewVisibility());
819
820       for (Iterator<UsageInfo> iterator = usages.iterator(); iterator.hasNext();) {
821         UsageInfo usageInfo = iterator.next();
822         PsiElement element = usageInfo.getElement();
823         if (element != null && StdLanguages.JAVA.equals(element.getLanguage())) {
824           if (element instanceof PsiReferenceExpression) {
825             PsiClass accessObjectClass = null;
826             PsiExpression qualifier = ((PsiReferenceExpression)element).getQualifierExpression();
827             if (qualifier != null) {
828               accessObjectClass = (PsiClass)PsiUtil.getAccessObjectClass(qualifier).getElement();
829             }
830
831             if (!JavaPsiFacade.getInstance(element.getProject()).getResolveHelper()
832               .isAccessible(method, modifierList, element, accessObjectClass, null)) {
833               String message =
834                 RefactoringBundle.message("0.with.1.visibility.is.not.accesible.from.2",
835                                           RefactoringUIUtil.getDescription(method, true),
836                                           myChangeInfo.getNewVisibility(),
837                                           RefactoringUIUtil.getDescription(ConflictsUtil.getContainer(element), true));
838               conflictDescriptions.putValue(method, message);
839               if (!needToChangeCalls()) {
840                 iterator.remove();
841               }
842             }
843           }
844         }
845       }
846     }
847
848
849     private void addMethodConflicts(MultiMap<PsiElement, String> conflicts) {
850       String newMethodName = myChangeInfo.getNewName();
851       if (!(myChangeInfo instanceof JavaChangeInfo)) {
852         return;
853       }
854       try {
855         PsiMethod prototype;
856         final PsiMethod method = myChangeInfo.getMethod();
857         PsiManager manager = PsiManager.getInstance(method.getProject());
858         PsiElementFactory factory = JavaPsiFacade.getInstance(manager.getProject()).getElementFactory();
859         final CanonicalTypes.Type returnType = myChangeInfo.getNewReturnType();
860         if (returnType != null) {
861           prototype = factory.createMethod(newMethodName, returnType.getType(method, manager));
862         }
863         else {
864           prototype = factory.createConstructor();
865           prototype.setName(newMethodName);
866         }
867         JavaParameterInfo[] parameters = myChangeInfo.getNewParameters();
868
869
870         for (JavaParameterInfo info : parameters) {
871           PsiType parameterType = info.createType(method, manager);
872           if (parameterType == null) {
873             parameterType =
874               JavaPsiFacade.getElementFactory(method.getProject()).createTypeFromText(CommonClassNames.JAVA_LANG_OBJECT, method);
875           }
876           PsiParameter param = factory.createParameter(info.getName(), parameterType);
877           prototype.getParameterList().add(param);
878         }
879
880         ConflictsUtil.checkMethodConflicts(method.getContainingClass(), method, prototype, conflicts);
881       }
882       catch (IncorrectOperationException e) {
883         LOG.error(e);
884       }
885     }
886   }
887
888   private static class ExpressionList implements ChangeSignatureUtil.ChildrenGenerator<PsiExpressionList, PsiExpression> {
889     public static final ExpressionList INSTANCE = new ExpressionList();
890
891     public List<PsiExpression> getChildren(PsiExpressionList psiExpressionList) {
892       return Arrays.asList(psiExpressionList.getExpressions());
893     }
894   }
895
896 }