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