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.
16 package com
.intellij
.refactoring
.move
.moveInstanceMethod
;
18 import com
.intellij
.codeInsight
.ChangeContextUtil
;
19 import com
.intellij
.openapi
.diagnostic
.Logger
;
20 import com
.intellij
.openapi
.project
.Project
;
21 import com
.intellij
.openapi
.util
.Ref
;
22 import com
.intellij
.psi
.*;
23 import com
.intellij
.psi
.javadoc
.PsiDocTagValue
;
24 import com
.intellij
.psi
.search
.GlobalSearchScope
;
25 import com
.intellij
.psi
.search
.searches
.ClassInheritorsSearch
;
26 import com
.intellij
.psi
.search
.searches
.ReferencesSearch
;
27 import com
.intellij
.psi
.util
.PsiTreeUtil
;
28 import com
.intellij
.psi
.util
.PsiUtil
;
29 import com
.intellij
.refactoring
.BaseRefactoringProcessor
;
30 import com
.intellij
.refactoring
.RefactoringBundle
;
31 import com
.intellij
.refactoring
.move
.MoveInstanceMembersUtil
;
32 import com
.intellij
.refactoring
.util
.*;
33 import com
.intellij
.usageView
.UsageInfo
;
34 import com
.intellij
.usageView
.UsageViewDescriptor
;
35 import com
.intellij
.util
.IncorrectOperationException
;
36 import com
.intellij
.util
.VisibilityUtil
;
37 import com
.intellij
.util
.containers
.HashSet
;
38 import com
.intellij
.util
.containers
.MultiMap
;
39 import org
.jetbrains
.annotations
.NonNls
;
40 import org
.jetbrains
.annotations
.NotNull
;
47 public class MoveInstanceMethodProcessor
extends BaseRefactoringProcessor
{
48 private static final Logger LOG
= Logger
.getInstance("#com.intellij.refactoring.move.moveInstanceMethod.MoveInstanceMethodProcessor");
50 public PsiMethod
getMethod() {
54 public PsiVariable
getTargetVariable() {
55 return myTargetVariable
;
58 private PsiMethod myMethod
;
59 private PsiVariable myTargetVariable
;
60 private PsiClass myTargetClass
;
61 private final String myNewVisibility
;
62 private final Map
<PsiClass
, String
> myOldClassParameterNames
;
64 public MoveInstanceMethodProcessor(final Project project
,
65 final PsiMethod method
,
66 final PsiVariable targetVariable
,
67 final String newVisibility
,
68 final Map
<PsiClass
, String
> oldClassParameterNames
) {
71 myTargetVariable
= targetVariable
;
72 myOldClassParameterNames
= oldClassParameterNames
;
73 LOG
.assertTrue(myTargetVariable
instanceof PsiParameter
|| myTargetVariable
instanceof PsiField
);
74 LOG
.assertTrue(myTargetVariable
.getType() instanceof PsiClassType
);
75 final PsiType type
= myTargetVariable
.getType();
76 LOG
.assertTrue(type
instanceof PsiClassType
);
77 myTargetClass
= ((PsiClassType
) type
).resolve();
78 myNewVisibility
= newVisibility
;
81 protected UsageViewDescriptor
createUsageViewDescriptor(UsageInfo
[] usages
) {
82 return new MoveInstanceMethodViewDescriptor(myMethod
, myTargetVariable
, myTargetClass
);
85 protected boolean preprocessUsages(Ref
<UsageInfo
[]> refUsages
) {
86 final UsageInfo
[] usages
= refUsages
.get();
87 MultiMap
<PsiElement
, String
> conflicts
= new MultiMap
<PsiElement
, String
>();
88 final Set
<PsiMember
> members
= new HashSet
<PsiMember
>();
89 members
.add(myMethod
);
90 if (myTargetVariable
instanceof PsiField
) members
.add((PsiMember
)myTargetVariable
);
91 if (!myTargetClass
.isInterface()) {
92 RefactoringConflictsUtil
.analyzeAccessibilityConflicts(members
, myTargetClass
, conflicts
, myNewVisibility
);
95 for (final UsageInfo usage
: usages
) {
96 if (usage
instanceof InheritorUsageInfo
) {
97 RefactoringConflictsUtil
.analyzeAccessibilityConflicts(
98 members
, ((InheritorUsageInfo
)usage
).getInheritor(), conflicts
, myNewVisibility
);
103 if (myTargetVariable
instanceof PsiParameter
) {
104 PsiParameter parameter
= (PsiParameter
)myTargetVariable
;
105 for (final UsageInfo usageInfo
: usages
) {
106 if (usageInfo
instanceof MethodCallUsageInfo
) {
107 final PsiMethodCallExpression methodCall
= ((MethodCallUsageInfo
)usageInfo
).getMethodCallExpression();
108 final PsiExpression
[] expressions
= methodCall
.getArgumentList().getExpressions();
109 final int index
= myMethod
.getParameterList().getParameterIndex(parameter
);
110 if (index
< expressions
.length
) {
111 PsiExpression instanceValue
= expressions
[index
];
112 instanceValue
= RefactoringUtil
.unparenthesizeExpression(instanceValue
);
113 if (instanceValue
instanceof PsiLiteralExpression
&& ((PsiLiteralExpression
)instanceValue
).getValue() == null) {
114 String message
= RefactoringBundle
.message("0.contains.call.with.null.argument.for.parameter.1",
115 RefactoringUIUtil
.getDescription(ConflictsUtil
.getContainer(methodCall
), true),
116 CommonRefactoringUtil
.htmlEmphasize(parameter
.getName()));
117 conflicts
.putValue(instanceValue
, message
);
125 ConflictsUtil
.checkMethodConflicts(myTargetClass
, myMethod
, getPatternMethod(), conflicts
);
127 catch (IncorrectOperationException e
) {}
129 return showConflicts(conflicts
);
133 protected UsageInfo
[] findUsages() {
134 final PsiManager manager
= myMethod
.getManager();
135 final GlobalSearchScope searchScope
= GlobalSearchScope
.allScope(manager
.getProject());
136 final List
<UsageInfo
> usages
= new ArrayList
<UsageInfo
>();
137 for (PsiReference ref
: ReferencesSearch
.search(myMethod
, searchScope
, false)) {
138 final PsiElement element
= ref
.getElement();
139 if (element
instanceof PsiReferenceExpression
) {
140 boolean isInternal
= PsiTreeUtil
.isAncestor(myMethod
, element
, true);
141 usages
.add(new MethodCallUsageInfo((PsiReferenceExpression
)element
, isInternal
));
143 else if (element
instanceof PsiDocTagValue
) {
144 usages
.add(new JavadocUsageInfo(((PsiDocTagValue
)element
)));
147 throw new UnknownReferenceTypeException(element
.getLanguage());
151 if (myTargetClass
.isInterface()) {
152 addInheritorUsages(myTargetClass
, searchScope
, usages
);
155 final PsiCodeBlock body
= myMethod
.getBody();
157 body
.accept(new JavaRecursiveElementWalkingVisitor() {
158 @Override public void visitNewExpression(PsiNewExpression expression
) {
159 if (MoveInstanceMembersUtil
.getClassReferencedByThis(expression
) != null) {
160 usages
.add(new InternalUsageInfo(expression
));
162 super.visitNewExpression(expression
);
165 @Override public void visitReferenceExpression(PsiReferenceExpression expression
) {
166 if (MoveInstanceMembersUtil
.getClassReferencedByThis(expression
) != null) {
167 usages
.add(new InternalUsageInfo(expression
));
168 } else if (!expression
.isQualified()) {
169 final PsiElement resolved
= expression
.resolve();
170 if (myTargetVariable
.equals(resolved
)) {
171 usages
.add(new InternalUsageInfo(expression
));
175 super.visitReferenceExpression(expression
);
180 return usages
.toArray(new UsageInfo
[usages
.size()]);
183 private static void addInheritorUsages(PsiClass aClass
, final GlobalSearchScope searchScope
, final List
<UsageInfo
> usages
) {
184 for (PsiClass inheritor
: ClassInheritorsSearch
.search(aClass
, searchScope
, false).findAll()) {
185 if (!inheritor
.isInterface()) {
186 usages
.add(new InheritorUsageInfo(inheritor
));
189 addInheritorUsages(inheritor
, searchScope
, usages
);
194 protected void refreshElements(PsiElement
[] elements
) {
195 LOG
.assertTrue(elements
.length
== 3);
196 myMethod
= (PsiMethod
) elements
[0];
197 myTargetVariable
= (PsiVariable
) elements
[1];
198 myTargetClass
= (PsiClass
) elements
[2];
201 protected String
getCommandName() {
202 return RefactoringBundle
.message("move.instance.method.command");
205 public PsiClass
getTargetClass() {
206 return myTargetClass
;
209 protected void performRefactoring(UsageInfo
[] usages
) {
210 if (!CommonRefactoringUtil
.checkReadOnlyStatus(myProject
, myTargetClass
)) return;
212 PsiMethod patternMethod
= createMethodToAdd();
213 final List
<PsiReference
> docRefs
= new ArrayList
<PsiReference
>();
214 for (UsageInfo usage
: usages
) {
215 if (usage
instanceof InheritorUsageInfo
) {
216 final PsiClass inheritor
= ((InheritorUsageInfo
)usage
).getInheritor();
217 addMethodToClass(inheritor
, patternMethod
);
219 else if (usage
instanceof MethodCallUsageInfo
&& !((MethodCallUsageInfo
)usage
).isInternal()) {
220 correctMethodCall(((MethodCallUsageInfo
)usage
).getMethodCallExpression(), false);
222 else if (usage
instanceof JavadocUsageInfo
) {
223 docRefs
.add(usage
.getElement().getReference());
228 if (myTargetClass
.isInterface()) patternMethod
.getBody().delete();
230 final PsiMethod method
= addMethodToClass(myTargetClass
, patternMethod
);
232 for (PsiReference reference
: docRefs
) {
233 reference
.bindToElement(method
);
236 catch (IncorrectOperationException e
) {
241 private void correctMethodCall(final PsiMethodCallExpression expression
, final boolean isInternalCall
) {
243 final PsiManager manager
= myMethod
.getManager();
244 PsiReferenceExpression methodExpression
= expression
.getMethodExpression();
245 if (!methodExpression
.isReferenceTo(myMethod
)) return;
246 final PsiExpression oldQualifier
= methodExpression
.getQualifierExpression();
247 PsiExpression newQualifier
= null;
249 if (myTargetVariable
instanceof PsiParameter
) {
250 final int index
= myMethod
.getParameterList().getParameterIndex((PsiParameter
)myTargetVariable
);
251 final PsiExpression
[] arguments
= expression
.getArgumentList().getExpressions();
252 if (index
< arguments
.length
) {
253 newQualifier
= (PsiExpression
)arguments
[index
].copy();
254 arguments
[index
].delete();
258 VisibilityUtil
.escalateVisibility((PsiField
)myTargetVariable
, expression
);
259 newQualifier
= JavaPsiFacade
.getInstance(manager
.getProject()).getElementFactory().createExpressionFromText(myTargetVariable
.getName(), null);
262 PsiExpression newArgument
= null;
264 final PsiClass classReferencedByThis
= MoveInstanceMembersUtil
.getClassReferencedByThis(methodExpression
);
265 if (classReferencedByThis
!= null) {
266 @NonNls String thisArgumentText
= null;
267 if (manager
.areElementsEquivalent(myMethod
.getContainingClass(), classReferencedByThis
)) {
268 if (myOldClassParameterNames
.containsKey(myMethod
.getContainingClass())) {
269 thisArgumentText
= "this";
273 thisArgumentText
= classReferencedByThis
.getName() + ".this";
276 if (thisArgumentText
!= null) {
277 newArgument
= JavaPsiFacade
.getInstance(manager
.getProject()).getElementFactory().createExpressionFromText(thisArgumentText
, null);
280 if (!isInternalCall
&& oldQualifier
!= null) {
281 final PsiType type
= oldQualifier
.getType();
282 if (type
instanceof PsiClassType
) {
283 final PsiClass resolved
= ((PsiClassType
)type
).resolve();
284 if (resolved
!= null && getParameterNameToCreate(resolved
) != null) {
285 newArgument
= replaceRefsToTargetVariable(oldQualifier
); //replace is needed in case old qualifier is e.g. the same as field as target variable
292 if (newArgument
!= null) {
293 expression
.getArgumentList().add(newArgument
);
296 if (newQualifier
!= null) {
297 if (newQualifier
instanceof PsiThisExpression
&& ((PsiThisExpression
)newQualifier
).getQualifier() == null) {
298 //Remove now redundant 'this' qualifier
299 if (oldQualifier
!= null) oldQualifier
.delete();
302 final PsiReferenceExpression refExpr
= (PsiReferenceExpression
)JavaPsiFacade
.getInstance(manager
.getProject()).getElementFactory()
303 .createExpressionFromText("q." + myMethod
.getName(), null);
304 refExpr
.getQualifierExpression().replace(newQualifier
);
305 methodExpression
.replace(refExpr
);
309 catch (IncorrectOperationException e
) {
314 private PsiExpression
replaceRefsToTargetVariable(final PsiExpression expression
) {
315 final PsiManager manager
= expression
.getManager();
316 if (expression
instanceof PsiReferenceExpression
&&
317 ((PsiReferenceExpression
)expression
).isReferenceTo(myTargetVariable
)) {
318 return createThisExpr(manager
);
321 expression
.accept(new JavaRecursiveElementVisitor() {
322 @Override public void visitReferenceExpression(PsiReferenceExpression expression
) {
323 super.visitReferenceExpression(expression
);
324 if (expression
.isReferenceTo(myTargetVariable
)) {
326 expression
.replace(createThisExpr(manager
));
328 catch (IncorrectOperationException e
) {
338 private static PsiExpression
createThisExpr(final PsiManager manager
) {
340 return JavaPsiFacade
.getInstance(manager
.getProject()).getElementFactory().createExpressionFromText("this", null);
342 catch (IncorrectOperationException e
) {
348 private static PsiMethod
addMethodToClass(final PsiClass aClass
, final PsiMethod patternMethod
) {
350 final PsiMethod method
= (PsiMethod
)aClass
.add(patternMethod
);
351 ChangeContextUtil
.decodeContextInfo(method
, null, null);
354 catch (IncorrectOperationException e
) {
361 private PsiMethod
createMethodToAdd () {
362 ChangeContextUtil
.encodeContextInfo(myMethod
, true);
364 final PsiManager manager
= myMethod
.getManager();
365 final PsiElementFactory factory
= JavaPsiFacade
.getInstance(manager
.getProject()).getElementFactory();
367 //correct internal references
368 final PsiCodeBlock body
= myMethod
.getBody();
370 body
.accept(new JavaRecursiveElementVisitor() {
371 @Override public void visitThisExpression(PsiThisExpression expression
) {
372 final PsiClass classReferencedByThis
= MoveInstanceMembersUtil
.getClassReferencedByThis(expression
);
373 if (classReferencedByThis
!= null) {
374 final PsiElementFactory factory
= JavaPsiFacade
.getInstance(myProject
).getElementFactory();
375 String paramName
= getParameterNameToCreate(classReferencedByThis
);
377 final PsiExpression refExpression
= factory
.createExpressionFromText(paramName
, null);
378 expression
.replace(refExpression
);
380 catch (IncorrectOperationException e
) {
386 @Override public void visitReferenceExpression(PsiReferenceExpression expression
) {
388 final PsiExpression qualifier
= expression
.getQualifierExpression();
389 if (qualifier
instanceof PsiReferenceExpression
&& ((PsiReferenceExpression
)qualifier
).isReferenceTo(myTargetVariable
)) {
390 //Target is a field, replace target.m -> m
394 final PsiElement resolved
= expression
.resolve();
395 if (myTargetVariable
.equals(resolved
)) {
396 PsiThisExpression thisExpression
= (PsiThisExpression
)factory
.createExpressionFromText("this", null);
397 expression
.replace(thisExpression
);
400 else if (myMethod
.equals(resolved
)) {
403 PsiClass classReferencedByThis
= MoveInstanceMembersUtil
.getClassReferencedByThis(expression
);
404 if (classReferencedByThis
!= null) {
405 final String paramName
= getParameterNameToCreate(classReferencedByThis
);
406 PsiReferenceExpression newQualifier
= (PsiReferenceExpression
)factory
.createExpressionFromText(paramName
, null);
407 expression
.setQualifierExpression(newQualifier
);
411 super.visitReferenceExpression(expression
);
413 catch (IncorrectOperationException e
) {
418 @Override public void visitNewExpression(PsiNewExpression expression
) {
420 final PsiExpression qualifier
= expression
.getQualifier();
421 if (qualifier
instanceof PsiReferenceExpression
&& ((PsiReferenceExpression
)qualifier
).isReferenceTo(myTargetVariable
)) {
422 //Target is a field, replace target.new A() -> new A()
425 final PsiClass classReferencedByThis
= MoveInstanceMembersUtil
.getClassReferencedByThis(expression
);
426 if (classReferencedByThis
!= null) {
427 if (qualifier
!= null) qualifier
.delete();
428 final String paramName
= getParameterNameToCreate(classReferencedByThis
);
429 final PsiExpression newExpression
= factory
.createExpressionFromText(paramName
+ "." + expression
.getText(), null);
430 expression
= (PsiNewExpression
)expression
.replace(newExpression
);
433 super.visitNewExpression(expression
);
435 catch (IncorrectOperationException e
) {
440 @Override public void visitMethodCallExpression(PsiMethodCallExpression expression
) {
441 super.visitMethodCallExpression(expression
);
442 correctMethodCall(expression
, true);
447 final PsiMethod methodCopy
= getPatternMethod();
449 final List
<PsiParameter
> newParameters
= Arrays
.asList(methodCopy
.getParameterList().getParameters());
450 RefactoringUtil
.fixJavadocsForParams(methodCopy
, new HashSet
<PsiParameter
>(newParameters
));
453 catch (IncorrectOperationException e
) {
459 private PsiMethod
getPatternMethod() throws IncorrectOperationException
{
460 final PsiMethod methodCopy
= (PsiMethod
)myMethod
.copy();
461 String name
= myTargetClass
.isInterface() ? PsiModifier
.PUBLIC
: myNewVisibility
;
463 PsiUtil
.setModifierProperty(methodCopy
, name
, true);
465 if (myTargetVariable
instanceof PsiParameter
) {
466 final int index
= myMethod
.getParameterList().getParameterIndex((PsiParameter
)myTargetVariable
);
467 methodCopy
.getParameterList().getParameters()[index
].delete();
470 addParameters(JavaPsiFacade
.getInstance(myProject
).getElementFactory(), methodCopy
, myTargetClass
.isInterface());
474 private void addParameters(final PsiElementFactory factory
, final PsiMethod methodCopy
, final boolean isInterface
) throws IncorrectOperationException
{
475 final Set
<Map
.Entry
<PsiClass
, String
>> entries
= myOldClassParameterNames
.entrySet();
476 for (final Map
.Entry
<PsiClass
, String
> entry
: entries
) {
477 final PsiClassType type
= factory
.createType(entry
.getKey());
478 final PsiParameter parameter
= factory
.createParameter(entry
.getValue(), type
);
480 PsiUtil
.setModifierProperty(parameter
, PsiModifier
.FINAL
, false);
482 methodCopy
.getParameterList().add(parameter
);
486 private String
getParameterNameToCreate(@NotNull PsiClass aClass
) {
487 return myOldClassParameterNames
.get(aClass
);