2 * Copyright 2000-2009 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
18 * Created by IntelliJ IDEA.
22 * To change template for new class use
23 * Code Style | Class Templates options (Tools | IDE Options).
25 package com
.intellij
.refactoring
.introduceParameter
;
27 import com
.intellij
.codeInsight
.ChangeContextUtil
;
28 import com
.intellij
.openapi
.diagnostic
.Logger
;
29 import com
.intellij
.openapi
.project
.Project
;
30 import com
.intellij
.openapi
.util
.Pair
;
31 import com
.intellij
.openapi
.util
.Ref
;
32 import com
.intellij
.psi
.*;
33 import com
.intellij
.psi
.search
.GlobalSearchScope
;
34 import com
.intellij
.psi
.search
.searches
.MethodReferencesSearch
;
35 import com
.intellij
.psi
.search
.searches
.OverridingMethodsSearch
;
36 import com
.intellij
.psi
.util
.PsiTreeUtil
;
37 import com
.intellij
.refactoring
.BaseRefactoringProcessor
;
38 import com
.intellij
.refactoring
.RefactoringBundle
;
39 import com
.intellij
.refactoring
.changeSignature
.ChangeSignatureProcessor
;
40 import com
.intellij
.refactoring
.introduceVariable
.IntroduceVariableBase
;
41 import com
.intellij
.refactoring
.util
.*;
42 import com
.intellij
.refactoring
.util
.occurences
.ExpressionOccurenceManager
;
43 import com
.intellij
.refactoring
.util
.occurences
.LocalVariableOccurenceManager
;
44 import com
.intellij
.refactoring
.util
.occurences
.OccurenceManager
;
45 import com
.intellij
.refactoring
.util
.usageInfo
.DefaultConstructorImplicitUsageInfo
;
46 import com
.intellij
.refactoring
.util
.usageInfo
.NoConstructorClassUsageInfo
;
47 import com
.intellij
.usageView
.UsageInfo
;
48 import com
.intellij
.usageView
.UsageViewDescriptor
;
49 import com
.intellij
.usageView
.UsageViewUtil
;
50 import com
.intellij
.util
.IncorrectOperationException
;
51 import com
.intellij
.util
.containers
.HashSet
;
52 import com
.intellij
.util
.containers
.MultiMap
;
53 import gnu
.trove
.TIntArrayList
;
54 import gnu
.trove
.TIntProcedure
;
55 import org
.jetbrains
.annotations
.NotNull
;
56 import org
.jetbrains
.annotations
.Nullable
;
58 import java
.util
.ArrayList
;
61 public class IntroduceParameterProcessor
extends BaseRefactoringProcessor
implements IntroduceParameterData
{
62 private static final Logger LOG
= Logger
.getInstance("#com.intellij.refactoring.introduceParameter.IntroduceParameterProcessor");
64 private final PsiMethod myMethodToReplaceIn
;
65 private final PsiMethod myMethodToSearchFor
;
66 private PsiExpression myParameterInitializer
;
67 private final PsiExpression myExpressionToSearch
;
68 private final PsiLocalVariable myLocalVariable
;
69 private final boolean myRemoveLocalVariable
;
70 private final String myParameterName
;
71 private final boolean myReplaceAllOccurences
;
73 private int myReplaceFieldsWithGetters
;
74 private final boolean myDeclareFinal
;
75 private final boolean myGenerateDelegate
;
76 private PsiType myForcedType
;
77 private final TIntArrayList myParametersToRemove
;
78 private final PsiManager myManager
;
81 * if expressionToSearch is null, search for localVariable
83 public IntroduceParameterProcessor(@NotNull Project project
,
84 PsiMethod methodToReplaceIn
,
85 @NotNull PsiMethod methodToSearchFor
,
86 PsiExpression parameterInitializer
,
87 PsiExpression expressionToSearch
,
88 PsiLocalVariable localVariable
,
89 boolean removeLocalVariable
,
91 boolean replaceAllOccurences
,
92 int replaceFieldsWithGetters
,
94 boolean generateDelegate
,
96 @NotNull TIntArrayList parametersToRemove
) {
99 myMethodToReplaceIn
= methodToReplaceIn
;
100 myMethodToSearchFor
= methodToSearchFor
;
101 myParameterInitializer
= parameterInitializer
;
102 myExpressionToSearch
= expressionToSearch
;
104 myLocalVariable
= localVariable
;
105 myRemoveLocalVariable
= removeLocalVariable
;
106 myParameterName
= parameterName
;
107 myReplaceAllOccurences
= replaceAllOccurences
;
108 myReplaceFieldsWithGetters
= replaceFieldsWithGetters
;
109 myDeclareFinal
= declareFinal
;
110 myGenerateDelegate
= generateDelegate
;
111 myForcedType
= forcedType
;
112 myManager
= PsiManager
.getInstance(project
);
114 myParametersToRemove
= parametersToRemove
;
117 protected UsageViewDescriptor
createUsageViewDescriptor(UsageInfo
[] usages
) {
118 return new IntroduceParameterViewDescriptor(myMethodToSearchFor
);
121 public PsiType
getForcedType() {
125 public void setForcedType(PsiType forcedType
) {
126 myForcedType
= forcedType
;
129 public int getReplaceFieldsWithGetters() {
130 return myReplaceFieldsWithGetters
;
133 public void setReplaceFieldsWithGetters(int replaceFieldsWithGetters
) {
134 myReplaceFieldsWithGetters
= replaceFieldsWithGetters
;
138 protected UsageInfo
[] findUsages() {
139 ArrayList
<UsageInfo
> result
= new ArrayList
<UsageInfo
>();
141 PsiMethod
[] overridingMethods
=
142 OverridingMethodsSearch
.search(myMethodToSearchFor
, myMethodToSearchFor
.getUseScope(), true).toArray(PsiMethod
.EMPTY_ARRAY
);
143 for (PsiMethod overridingMethod
: overridingMethods
) {
144 result
.add(new UsageInfo(overridingMethod
));
146 if (!myGenerateDelegate
) {
147 PsiReference
[] refs
=
148 MethodReferencesSearch
.search(myMethodToSearchFor
, GlobalSearchScope
.projectScope(myProject
), true).toArray(PsiReference
.EMPTY_ARRAY
);
151 for (PsiReference ref1
: refs
) {
152 PsiElement ref
= ref1
.getElement();
153 if (ref
instanceof PsiMethod
&& ((PsiMethod
)ref
).isConstructor()) {
154 DefaultConstructorImplicitUsageInfo implicitUsageInfo
=
155 new DefaultConstructorImplicitUsageInfo((PsiMethod
)ref
, myMethodToSearchFor
);
156 result
.add(implicitUsageInfo
);
158 else if (ref
instanceof PsiClass
) {
159 result
.add(new NoConstructorClassUsageInfo((PsiClass
)ref
));
161 else if (!insideMethodToBeReplaced(ref
)) {
162 result
.add(new ExternalUsageInfo(ref
));
165 result
.add(new ChangedMethodCallInfo(ref
));
170 if (myReplaceAllOccurences
) {
171 final OccurenceManager occurenceManager
;
172 if (myLocalVariable
== null) {
173 occurenceManager
= new ExpressionOccurenceManager(myExpressionToSearch
, myMethodToReplaceIn
, null);
176 occurenceManager
= new LocalVariableOccurenceManager(myLocalVariable
, null);
178 PsiElement
[] exprs
= occurenceManager
.getOccurences();
179 for (PsiElement expr
: exprs
) {
180 result
.add(new InternalUsageInfo(expr
));
184 if (myExpressionToSearch
!= null) {
185 result
.add(new InternalUsageInfo(myExpressionToSearch
));
189 final UsageInfo
[] usageInfos
= result
.toArray(new UsageInfo
[result
.size()]);
190 return UsageViewUtil
.removeDuplicatedUsages(usageInfos
);
193 private static class ReferencedElementsCollector
extends JavaRecursiveElementWalkingVisitor
{
194 private final Set
<PsiElement
> myResult
= new HashSet
<PsiElement
>();
196 @Override public void visitReferenceExpression(PsiReferenceExpression expression
) {
197 visitReferenceElement(expression
);
200 @Override public void visitReferenceElement(PsiJavaCodeReferenceElement reference
) {
201 super.visitReferenceElement(reference
);
202 final PsiElement element
= reference
.resolve();
203 if (element
!= null) {
204 myResult
.add(element
);
209 protected boolean preprocessUsages(Ref
<UsageInfo
[]> refUsages
) {
210 UsageInfo
[] usagesIn
= refUsages
.get();
211 MultiMap
<PsiElement
, String
> conflicts
= new MultiMap
<PsiElement
, String
>();
213 AnySameNameVariables anySameNameVariables
= new AnySameNameVariables();
214 myMethodToReplaceIn
.accept(anySameNameVariables
);
215 final Pair
<PsiElement
, String
> conflictPair
= anySameNameVariables
.getConflict();
216 if (conflictPair
!= null) {
217 conflicts
.putValue(conflictPair
.first
, conflictPair
.second
);
220 if (!myGenerateDelegate
) {
221 detectAccessibilityConflicts(usagesIn
, conflicts
);
224 if (myParameterInitializer
!= null && !myMethodToReplaceIn
.hasModifierProperty(PsiModifier
.PRIVATE
)) {
225 final AnySupers anySupers
= new AnySupers();
226 myParameterInitializer
.accept(anySupers
);
227 if (anySupers
.isResult()) {
228 for (UsageInfo usageInfo
: usagesIn
) {
229 if (!(usageInfo
.getElement() instanceof PsiMethod
) && !(usageInfo
instanceof InternalUsageInfo
)) {
230 if (!PsiTreeUtil
.isAncestor(myMethodToReplaceIn
.getContainingClass(), usageInfo
.getElement(), false)) {
231 conflicts
.putValue(myParameterInitializer
, RefactoringBundle
.message("parameter.initializer.contains.0.but.not.all.calls.to.method.are.in.its.class",
232 CommonRefactoringUtil
.htmlEmphasize(PsiKeyword
.SUPER
)));
240 for (IntroduceParameterMethodUsagesProcessor processor
: IntroduceParameterMethodUsagesProcessor
.EP_NAME
.getExtensions()) {
241 processor
.findConflicts(this, refUsages
.get(), conflicts
);
244 return showConflicts(conflicts
);
247 private void detectAccessibilityConflicts(final UsageInfo
[] usageArray
, MultiMap
<PsiElement
, String
> conflicts
) {
248 if (myParameterInitializer
!= null) {
249 final ReferencedElementsCollector collector
= new ReferencedElementsCollector();
250 myParameterInitializer
.accept(collector
);
251 final Set
<PsiElement
> result
= collector
.myResult
;
252 if (!result
.isEmpty()) {
253 for (final UsageInfo usageInfo
: usageArray
) {
254 if (usageInfo
instanceof ExternalUsageInfo
&& isMethodUsage(usageInfo
)) {
255 final PsiElement place
= usageInfo
.getElement();
256 for (final PsiElement element
: result
) {
257 if (element
instanceof PsiMember
&&
258 !JavaPsiFacade
.getInstance(myProject
).getResolveHelper().isAccessible((PsiMember
)element
, place
, null)) {
260 RefactoringBundle
.message(
261 "0.is.not.accesible.from.1.value.for.introduced.parameter.in.that.method.call.will.be.incorrect",
262 RefactoringUIUtil
.getDescription(element
, true),
263 RefactoringUIUtil
.getDescription(ConflictsUtil
.getContainer(place
), true));
264 conflicts
.putValue(element
, message
);
273 private static boolean isMethodUsage(UsageInfo usageInfo
) {
274 for (IntroduceParameterMethodUsagesProcessor processor
: IntroduceParameterMethodUsagesProcessor
.EP_NAME
.getExtensions()) {
275 if (processor
.isMethodUsage(usageInfo
)) return true;
280 public static class AnySupers
extends JavaRecursiveElementWalkingVisitor
{
281 private boolean myResult
= false;
282 @Override public void visitSuperExpression(PsiSuperExpression expression
) {
286 public boolean isResult() {
290 @Override public void visitReferenceExpression(PsiReferenceExpression expression
) {
291 visitElement(expression
);
295 public class AnySameNameVariables
extends JavaRecursiveElementWalkingVisitor
{
296 private Pair
<PsiElement
, String
> conflict
= null;
298 public Pair
<PsiElement
, String
> getConflict() {
302 @Override public void visitVariable(PsiVariable variable
) {
303 if (variable
== myLocalVariable
) return;
304 if (myParameterName
.equals(variable
.getName())) {
305 String descr
= RefactoringBundle
.message("there.is.already.a.0.it.will.conflict.with.an.introduced.parameter",
306 RefactoringUIUtil
.getDescription(variable
, true));
308 conflict
= Pair
.<PsiElement
, String
>create(variable
, CommonRefactoringUtil
.capitalize(descr
));
312 @Override public void visitReferenceExpression(PsiReferenceExpression expression
) {
315 @Override public void visitElement(PsiElement element
) {
316 if(conflict
!= null) return;
317 super.visitElement(element
);
321 private boolean insideMethodToBeReplaced(PsiElement methodUsage
) {
322 PsiElement parent
= methodUsage
.getParent();
323 while(parent
!= null) {
324 if (parent
.equals(myMethodToReplaceIn
)) {
327 parent
= parent
.getParent();
332 protected void refreshElements(PsiElement
[] elements
) {
335 protected void performRefactoring(UsageInfo
[] usages
) {
337 PsiElementFactory factory
= JavaPsiFacade
.getInstance(myManager
.getProject()).getElementFactory();
338 PsiType initializerType
= getInitializerType(myForcedType
, myParameterInitializer
, myLocalVariable
);
339 setForcedType(initializerType
);
341 // Converting myParameterInitializer
342 if (myParameterInitializer
== null) {
343 LOG
.assertTrue(myLocalVariable
!= null);
344 myParameterInitializer
= factory
.createExpressionFromText(myLocalVariable
.getName(), myLocalVariable
);
347 myParameterInitializer
= RefactoringUtil
.convertInitializerToNormalExpression(myParameterInitializer
, initializerType
);
350 // Changing external occurences (the tricky part)
352 for (UsageInfo usage
: usages
) {
353 if (!(usage
instanceof InternalUsageInfo
)) {
354 if (usage
instanceof DefaultConstructorImplicitUsageInfo
) {
355 addSuperCall(usage
, usages
);
357 else if (usage
instanceof NoConstructorClassUsageInfo
) {
358 addDefaultConstructor(usage
, usages
);
361 PsiElement element
= usage
.getElement();
362 if (element
instanceof PsiMethod
) {
363 if (!myManager
.areElementsEquivalent(element
, myMethodToReplaceIn
)) {
364 changeMethodSignatureAndResolveFieldConflicts(usage
, usages
);
367 else if (!myGenerateDelegate
) {
368 changeExternalUsage(usage
, usages
);
374 if (myGenerateDelegate
) {
378 // Changing signature of initial method
379 // (signature of myMethodToReplaceIn will be either changed now or have already been changed)
380 LOG
.assertTrue(initializerType
.isValid());
381 final FieldConflictsResolver fieldConflictsResolver
= new FieldConflictsResolver(myParameterName
, myMethodToReplaceIn
.getBody());
382 changeMethodSignatureAndResolveFieldConflicts(new UsageInfo(myMethodToReplaceIn
), usages
);
383 if (myMethodToSearchFor
!= myMethodToReplaceIn
) {
384 changeMethodSignatureAndResolveFieldConflicts(new UsageInfo(myMethodToSearchFor
), usages
);
386 ChangeContextUtil
.clearContextInfo(myParameterInitializer
);
388 // Replacing expression occurences
389 for (UsageInfo usage
: usages
) {
390 if (usage
instanceof ChangedMethodCallInfo
) {
391 PsiElement element
= usage
.getElement();
393 processChangedMethodCall(element
);
395 else if (usage
instanceof InternalUsageInfo
) {
396 PsiElement element
= usage
.getElement();
397 if (element
instanceof PsiExpression
) {
398 element
= RefactoringUtil
.outermostParenthesizedExpression((PsiExpression
)element
);
400 if (element
.getParent() instanceof PsiExpressionStatement
) {
401 element
.getParent().delete();
404 PsiExpression newExpr
= factory
.createExpressionFromText(myParameterName
, element
);
405 IntroduceVariableBase
.replace((PsiExpression
)element
, newExpr
, myProject
);
410 if(myLocalVariable
!= null && myRemoveLocalVariable
) {
411 myLocalVariable
.normalizeDeclaration();
412 myLocalVariable
.getParent().delete();
414 fieldConflictsResolver
.fix();
416 catch (IncorrectOperationException ex
) {
421 private void generateDelegate() throws IncorrectOperationException
{
422 final PsiMethod delegate
= (PsiMethod
)myMethodToReplaceIn
.copy();
423 final PsiElementFactory elementFactory
= JavaPsiFacade
.getInstance(myManager
.getProject()).getElementFactory();
424 ChangeSignatureProcessor
.makeEmptyBody(elementFactory
, delegate
);
425 final PsiCallExpression callExpression
= ChangeSignatureProcessor
.addDelegatingCallTemplate(delegate
, delegate
.getName());
426 final PsiExpressionList argumentList
= callExpression
.getArgumentList();
427 assert argumentList
!= null;
428 final PsiParameter
[] psiParameters
= myMethodToReplaceIn
.getParameterList().getParameters();
430 final PsiParameter anchorParameter
= getAnchorParameter(myMethodToReplaceIn
);
431 if (psiParameters
.length
== 0) {
432 argumentList
.add(myParameterInitializer
);
435 for (int i
= 0; i
< psiParameters
.length
; i
++) {
436 PsiParameter psiParameter
= psiParameters
[i
];
437 if (!myParametersToRemove
.contains(i
)) {
438 final PsiExpression expression
= elementFactory
.createExpressionFromText(psiParameter
.getName(), delegate
);
439 argumentList
.add(expression
);
441 if (psiParameter
== anchorParameter
) {
442 argumentList
.add(myParameterInitializer
);
447 myMethodToReplaceIn
.getContainingClass().addBefore(delegate
, myMethodToReplaceIn
);
450 private void addDefaultConstructor(UsageInfo usage
, UsageInfo
[] usages
) throws IncorrectOperationException
{
451 for (IntroduceParameterMethodUsagesProcessor processor
: IntroduceParameterMethodUsagesProcessor
.EP_NAME
.getExtensions()) {
452 if (!processor
.processAddDefaultConstructor(this, usage
, usages
)) break;
456 private void addSuperCall(UsageInfo usage
, UsageInfo
[] usages
) throws IncorrectOperationException
{
457 for (IntroduceParameterMethodUsagesProcessor processor
: IntroduceParameterMethodUsagesProcessor
.EP_NAME
.getExtensions()) {
458 if (!processor
.processAddSuperCall(this, usage
, usages
)) break;
462 static PsiType
getInitializerType(PsiType forcedType
, PsiExpression parameterInitializer
, PsiLocalVariable localVariable
) {
463 final PsiType initializerType
;
464 if (forcedType
== null) {
465 if (parameterInitializer
== null) {
466 if (localVariable
!= null) {
467 initializerType
= localVariable
.getType();
469 LOG
.assertTrue(false);
470 initializerType
= null;
473 if (localVariable
== null) {
474 initializerType
= RefactoringUtil
.getTypeByExpressionWithExpectedType(parameterInitializer
);
476 initializerType
= localVariable
.getType();
480 initializerType
= forcedType
;
482 return initializerType
;
485 private void processChangedMethodCall(PsiElement element
) throws IncorrectOperationException
{
486 if (element
.getParent() instanceof PsiMethodCallExpression
) {
487 PsiMethodCallExpression methodCall
= (PsiMethodCallExpression
)element
.getParent();
489 PsiElementFactory factory
= JavaPsiFacade
.getInstance(methodCall
.getProject()).getElementFactory();
490 PsiExpression expression
= factory
.createExpressionFromText(myParameterName
, null);
491 final PsiExpressionList argList
= methodCall
.getArgumentList();
492 final PsiExpression
[] exprs
= argList
.getExpressions();
494 if (exprs
.length
> 0) {
495 argList
.addAfter(expression
, exprs
[exprs
.length
- 1]);
498 argList
.add(expression
);
501 removeParametersFromCall(argList
);
504 LOG
.error(element
.getParent().toString());
508 private void removeParametersFromCall(final PsiExpressionList argList
) {
509 final PsiExpression
[] exprs
= argList
.getExpressions();
510 myParametersToRemove
.forEachDescending(new TIntProcedure() {
511 public boolean execute(final int paramNum
) {
513 exprs
[paramNum
].delete();
515 catch (IncorrectOperationException e
) {
523 private void changeExternalUsage(UsageInfo usage
, UsageInfo
[] usages
) throws IncorrectOperationException
{
524 for (IntroduceParameterMethodUsagesProcessor processor
: IntroduceParameterMethodUsagesProcessor
.EP_NAME
.getExtensions()) {
525 if (!processor
.processChangeMethodUsage(this, usage
, usages
)) break;
529 protected String
getCommandName() {
530 return RefactoringBundle
.message("introduce.parameter.command", UsageViewUtil
.getDescriptiveName(myMethodToReplaceIn
));
533 private void changeMethodSignatureAndResolveFieldConflicts(UsageInfo usage
, UsageInfo
[] usages
) throws IncorrectOperationException
{
534 for (IntroduceParameterMethodUsagesProcessor processor
: IntroduceParameterMethodUsagesProcessor
.EP_NAME
.getExtensions()) {
535 if (!processor
.processChangeMethodSignature(this, usage
, usages
)) break;
540 private static PsiParameter
getAnchorParameter(PsiMethod methodToReplaceIn
) {
541 PsiParameterList parameterList
= methodToReplaceIn
.getParameterList();
542 final PsiParameter anchorParameter
;
543 final PsiParameter
[] parameters
= parameterList
.getParameters();
544 final int length
= parameters
.length
;
545 if (!methodToReplaceIn
.isVarArgs()) {
546 anchorParameter
= length
> 0 ? parameters
[length
-1] : null;
549 LOG
.assertTrue(length
> 0);
550 LOG
.assertTrue(parameters
[length
-1].isVarArgs());
551 anchorParameter
= length
> 1 ? parameters
[length
-2] : null;
553 return anchorParameter
;
556 public PsiMethod
getMethodToReplaceIn() {
557 return myMethodToReplaceIn
;
561 public PsiMethod
getMethodToSearchFor() {
562 return myMethodToSearchFor
;
565 public PsiExpression
getParameterInitializer() {
566 return myParameterInitializer
;
569 public PsiExpression
getExpressionToSearch() {
570 return myExpressionToSearch
;
573 public PsiLocalVariable
getLocalVariable() {
574 return myLocalVariable
;
577 public boolean isRemoveLocalVariable() {
578 return myRemoveLocalVariable
;
582 public String
getParameterName() {
583 return myParameterName
;
586 public boolean isReplaceAllOccurences() {
587 return myReplaceAllOccurences
;
590 public boolean isDeclareFinal() {
591 return myDeclareFinal
;
594 public boolean isGenerateDelegate() {
595 return myGenerateDelegate
;
599 public TIntArrayList
getParametersToRemove() {
600 return myParametersToRemove
;
603 public PsiManager
getManager() {
608 public Project
getProject() {