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