3 * Copyright 2000-2009 JetBrains s.r.o.
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
17 package com
.intellij
.refactoring
.encapsulateFields
;
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
.openapi
.util
.text
.StringUtil
;
23 import com
.intellij
.psi
.*;
24 import com
.intellij
.psi
.codeStyle
.CodeStyleManager
;
25 import com
.intellij
.psi
.search
.GlobalSearchScope
;
26 import com
.intellij
.psi
.search
.searches
.ReferencesSearch
;
27 import com
.intellij
.psi
.tree
.IElementType
;
28 import com
.intellij
.psi
.util
.PsiFormatUtil
;
29 import com
.intellij
.psi
.util
.PsiUtil
;
30 import com
.intellij
.refactoring
.BaseRefactoringProcessor
;
31 import com
.intellij
.refactoring
.HelpID
;
32 import com
.intellij
.refactoring
.RefactoringBundle
;
33 import com
.intellij
.refactoring
.ui
.ConflictsDialog
;
34 import com
.intellij
.refactoring
.util
.CommonRefactoringUtil
;
35 import com
.intellij
.refactoring
.util
.RefactoringUtil
;
36 import com
.intellij
.usageView
.UsageInfo
;
37 import com
.intellij
.usageView
.UsageViewDescriptor
;
38 import com
.intellij
.usageView
.UsageViewUtil
;
39 import com
.intellij
.util
.IncorrectOperationException
;
40 import com
.intellij
.util
.VisibilityUtil
;
41 import com
.intellij
.util
.containers
.HashMap
;
42 import com
.intellij
.util
.containers
.MultiMap
;
43 import org
.jetbrains
.annotations
.NonNls
;
44 import org
.jetbrains
.annotations
.NotNull
;
45 import org
.jetbrains
.annotations
.Nullable
;
47 import java
.util
.ArrayList
;
48 import java
.util
.List
;
51 public class EncapsulateFieldsProcessor
extends BaseRefactoringProcessor
{
52 private static final Logger LOG
= Logger
.getInstance("#com.intellij.refactoring.encapsulateFields.EncapsulateFieldsProcessor");
54 private PsiClass myClass
;
55 private final EncapsulateFieldsDialog myDialog
;
56 private PsiField
[] myFields
;
58 private HashMap
<String
,PsiMethod
> myNameToGetter
;
59 private HashMap
<String
,PsiMethod
> myNameToSetter
;
61 public EncapsulateFieldsProcessor(Project project
, EncapsulateFieldsDialog dialog
) {
66 protected UsageViewDescriptor
createUsageViewDescriptor(UsageInfo
[] usages
) {
67 PsiField
[] fields
= new PsiField
[myFields
.length
];
68 System
.arraycopy(myFields
, 0, fields
, 0, myFields
.length
);
69 return new EncapsulateFieldsViewDescriptor(fields
);
72 protected String
getCommandName() {
73 return RefactoringBundle
.message("encapsulate.fields.command.name", UsageViewUtil
.getDescriptiveName(myClass
));
77 myFields
= myDialog
.getSelectedFields();
78 if (myFields
.length
== 0){
79 String message
= RefactoringBundle
.message("encapsulate.fields.no.fields.selected");
80 CommonRefactoringUtil
.showErrorMessage(EncapsulateFieldsHandler
.REFACTORING_NAME
, message
, HelpID
.ENCAPSULATE_FIELDS
, myProject
);
83 myClass
= myFields
[0].getContainingClass();
88 protected boolean preprocessUsages(Ref
<UsageInfo
[]> refUsages
) {
89 MultiMap
<PsiElement
, String
> conflicts
= new MultiMap
<PsiElement
, String
>();
91 if (myDialog
!= null) {
92 checkExistingMethods(myDialog
.getGetterPrototypes(), conflicts
, true);
93 checkExistingMethods(myDialog
.getSetterPrototypes(), conflicts
, false);
95 if(conflicts
.size() > 0) {
96 ConflictsDialog dialog
= new ConflictsDialog(myProject
, conflicts
);
99 if (dialog
.isShowConflicts()) prepareSuccessful();
109 private void checkExistingMethods(PsiMethod
[] prototypes
, MultiMap
<PsiElement
, String
> conflicts
, boolean isGetter
) {
110 if(prototypes
== null) return;
111 for (PsiMethod prototype
: prototypes
) {
112 final PsiType prototypeReturnType
= prototype
.getReturnType();
113 PsiMethod existing
= myClass
.findMethodBySignature(prototype
, true);
114 if (existing
!= null) {
115 final PsiType returnType
= existing
.getReturnType();
116 if (!RefactoringUtil
.equivalentTypes(prototypeReturnType
, returnType
, myClass
.getManager())) {
117 final String descr
= PsiFormatUtil
.formatMethod(existing
,
118 PsiSubstitutor
.EMPTY
,
119 PsiFormatUtil
.SHOW_NAME
| PsiFormatUtil
.SHOW_PARAMETERS
| PsiFormatUtil
.SHOW_TYPE
,
120 PsiFormatUtil
.SHOW_TYPE
122 String message
= isGetter ?
123 RefactoringBundle
.message("encapsulate.fields.getter.exists", CommonRefactoringUtil
.htmlEmphasize(descr
),
124 CommonRefactoringUtil
.htmlEmphasize(prototype
.getName())) :
125 RefactoringBundle
.message("encapsulate.fields.setter.exists", CommonRefactoringUtil
.htmlEmphasize(descr
),
126 CommonRefactoringUtil
.htmlEmphasize(prototype
.getName()));
127 conflicts
.putValue(existing
, message
);
133 @NotNull protected UsageInfo
[] findUsages() {
134 boolean findGet
= myDialog
.isToEncapsulateGet();
135 boolean findSet
= myDialog
.isToEncapsulateSet();
136 PsiModifierList newModifierList
= null;
137 final JavaPsiFacade facade
= JavaPsiFacade
.getInstance(myProject
);
138 if (!myDialog
.isToUseAccessorsWhenAccessible()){
139 PsiElementFactory factory
= facade
.getElementFactory();
141 PsiField field
= factory
.createField("a", PsiType
.INT
);
142 setNewFieldVisibility(field
);
143 newModifierList
= field
.getModifierList();
145 catch(IncorrectOperationException e
){
149 PsiMethod
[] getterPrototypes
= myDialog
.getGetterPrototypes();
150 PsiMethod
[] setterPrototypes
= myDialog
.getSetterPrototypes();
151 ArrayList
<UsageInfo
> array
= new ArrayList
<UsageInfo
>();
152 PsiField
[] fields
= myFields
;
153 for(int i
= 0; i
< fields
.length
; i
++){
154 PsiField field
= fields
[i
];
155 for (final PsiReference reference
: ReferencesSearch
.search(field
, GlobalSearchScope
.projectScope(myProject
), true)) {
156 if (!(reference
instanceof PsiReferenceExpression
)) continue;
157 PsiReferenceExpression ref
= (PsiReferenceExpression
)reference
;
158 // [Jeka] to avoid recursion in the field's accessors
159 if (findGet
&& isUsedInExistingAccessor(getterPrototypes
[i
], ref
)) continue;
160 if (findSet
&& isUsedInExistingAccessor(setterPrototypes
[i
], ref
)) continue;
162 if (!PsiUtil
.isAccessedForWriting(ref
)) continue;
164 if (!findSet
|| field
.hasModifierProperty(PsiModifier
.FINAL
)) {
165 if (!PsiUtil
.isAccessedForReading(ref
)) continue;
167 if (!myDialog
.isToUseAccessorsWhenAccessible()) {
168 PsiClass accessObjectClass
= null;
169 PsiExpression qualifier
= ref
.getQualifierExpression();
170 if (qualifier
!= null) {
171 accessObjectClass
= (PsiClass
)PsiUtil
.getAccessObjectClass(qualifier
).getElement();
173 if (facade
.getResolveHelper()
174 .isAccessible(field
, newModifierList
, ref
, accessObjectClass
, null)) {
178 UsageInfo usageInfo
= new MyUsageInfo(ref
, i
);
179 array
.add(usageInfo
);
182 MyUsageInfo
[] usageInfos
= array
.toArray(new MyUsageInfo
[array
.size()]);
183 return UsageViewUtil
.removeDuplicatedUsages(usageInfos
);
186 protected void refreshElements(PsiElement
[] elements
) {
187 LOG
.assertTrue(elements
.length
== myFields
.length
);
189 for (int idx
= 0; idx
< elements
.length
; idx
++) {
190 PsiElement element
= elements
[idx
];
192 LOG
.assertTrue(element
instanceof PsiField
);
194 myFields
[idx
] = (PsiField
)element
;
197 myClass
= myFields
[0].getContainingClass();
200 protected void performRefactoring(UsageInfo
[] usages
) {
201 // change visibility of fields
202 if (myDialog
.getFieldsVisibility() != null){
204 for (PsiField field
: myFields
) {
205 setNewFieldVisibility(field
);
209 // generate accessors
210 myNameToGetter
= new com
.intellij
.util
.containers
.HashMap
<String
, PsiMethod
>();
211 myNameToSetter
= new com
.intellij
.util
.containers
.HashMap
<String
, PsiMethod
>();
212 for(int i
= 0; i
< myFields
.length
; i
++){
213 PsiField field
= myFields
[i
];
214 if (myDialog
.isToEncapsulateGet()){
215 PsiMethod
[] prototypes
= myDialog
.getGetterPrototypes();
216 addOrChangeAccessor(prototypes
[i
], myNameToGetter
);
218 if (myDialog
.isToEncapsulateSet() && !field
.hasModifierProperty(PsiModifier
.FINAL
)){
219 PsiMethod
[] prototypes
= myDialog
.getSetterPrototypes();
220 addOrChangeAccessor(prototypes
[i
], myNameToSetter
);
224 Map
<PsiFile
, List
<MyUsageInfo
>> usagesInFiles
= new HashMap
<PsiFile
, List
<MyUsageInfo
>>();
225 for (UsageInfo usage
: usages
) {
226 PsiElement element
= usage
.getElement();
227 if (element
== null) continue;
228 final PsiFile file
= element
.getContainingFile();
229 List
<MyUsageInfo
> usagesInFile
= usagesInFiles
.get(file
);
230 if (usagesInFile
== null) {
231 usagesInFile
= new ArrayList
<MyUsageInfo
>();
232 usagesInFiles
.put(file
, usagesInFile
);
234 usagesInFile
.add(((MyUsageInfo
)usage
));
237 for (List
<MyUsageInfo
> usageInfos
: usagesInFiles
.values()) {
238 //this is to avoid elements to become invalid as a result of processUsage
239 RefactoringUtil
.sortDepthFirstRightLeftOrder(usages
);
241 for (MyUsageInfo info
: usageInfos
) {
247 private void setNewFieldVisibility(PsiField field
) {
249 if (myDialog
.getFieldsVisibility() != null){
250 field
.normalizeDeclaration();
251 PsiUtil
.setModifierProperty(field
, myDialog
.getFieldsVisibility(), true);
254 catch(IncorrectOperationException e
){
259 private void addOrChangeAccessor(PsiMethod prototype
, HashMap
<String
,PsiMethod
> nameToAncestor
) {
260 PsiMethod existing
= myClass
.findMethodBySignature(prototype
, false);
261 PsiMethod result
= existing
;
263 if (existing
== null){
264 PsiUtil
.setModifierProperty(prototype
, myDialog
.getAccessorsVisibility(), true);
265 result
= (PsiMethod
) myClass
.add(prototype
);
268 //TODO : change visibility
270 nameToAncestor
.put(prototype
.getName(), result
);
272 catch(IncorrectOperationException e
){
277 private boolean isUsedInExistingAccessor(PsiMethod prototype
, PsiElement element
) {
278 PsiMethod existingAccessor
= myClass
.findMethodBySignature(prototype
, false);
279 if (existingAccessor
!= null) {
280 PsiElement parent
= element
;
281 while (parent
!= null) {
282 if (existingAccessor
.equals(parent
)) return true;
283 parent
= parent
.getParent();
289 private void processUsage(MyUsageInfo usage
) {
290 PsiField field
= myFields
[usage
.fieldIndex
];
291 boolean processGet
= myDialog
.isToEncapsulateGet();
292 boolean processSet
= myDialog
.isToEncapsulateSet() && !field
.hasModifierProperty(PsiModifier
.FINAL
);
293 if (!processGet
&& !processSet
) return;
294 PsiElementFactory factory
= JavaPsiFacade
.getInstance(myProject
).getElementFactory();
297 final PsiReferenceExpression expr
= (PsiReferenceExpression
)usage
.getElement();
298 if (expr
== null) return;
299 final PsiElement parent
= expr
.getParent();
300 if (parent
instanceof PsiAssignmentExpression
&& expr
.equals(((PsiAssignmentExpression
)parent
).getLExpression())){
301 PsiAssignmentExpression assignment
= (PsiAssignmentExpression
)parent
;
302 if (assignment
.getRExpression() == null) return;
303 PsiJavaToken opSign
= assignment
.getOperationSign();
304 IElementType opType
= opSign
.getTokenType();
305 if (opType
== JavaTokenType
.EQ
) {
307 if (!processSet
) return;
308 final int fieldIndex
= usage
.fieldIndex
;
309 final PsiExpression setterArgument
= assignment
.getRExpression();
311 PsiMethodCallExpression methodCall
= createSetterCall(fieldIndex
, setterArgument
, expr
);
313 if (methodCall
!= null) {
314 assignment
.replace(methodCall
);
316 //TODO: check if value is used!!!
319 else if (opType
== JavaTokenType
.ASTERISKEQ
|| opType
== JavaTokenType
.DIVEQ
|| opType
== JavaTokenType
.PERCEQ
||
320 opType
== JavaTokenType
.PLUSEQ
||
321 opType
== JavaTokenType
.MINUSEQ
||
322 opType
== JavaTokenType
.LTLTEQ
||
323 opType
== JavaTokenType
.GTGTEQ
||
324 opType
== JavaTokenType
.GTGTGTEQ
||
325 opType
== JavaTokenType
.ANDEQ
||
326 opType
== JavaTokenType
.OREQ
||
327 opType
== JavaTokenType
.XOREQ
) {
329 // Q: side effects of qualifier??!
331 String opName
= opSign
.getText();
332 LOG
.assertTrue(StringUtil
.endsWithChar(opName
, '='));
333 opName
= opName
.substring(0, opName
.length() - 1);
335 PsiExpression getExpr
= expr
;
337 final int fieldIndex
= usage
.fieldIndex
;
338 final PsiMethodCallExpression getterCall
= createGetterCall(fieldIndex
, expr
);
339 if (getterCall
!= null) {
340 getExpr
= getterCall
;
344 @NonNls String text
= "a" + opName
+ "b";
345 PsiBinaryExpression binExpr
= (PsiBinaryExpression
)factory
.createExpressionFromText(text
, expr
);
346 binExpr
= (PsiBinaryExpression
)CodeStyleManager
.getInstance(myProject
).reformat(binExpr
);
347 binExpr
.getLOperand().replace(getExpr
);
348 binExpr
.getROperand().replace(assignment
.getRExpression());
350 PsiExpression setExpr
;
352 setExpr
= createSetterCall(usage
.fieldIndex
, binExpr
, expr
);
356 PsiAssignmentExpression assignment1
= (PsiAssignmentExpression
)factory
.createExpressionFromText(text
, null);
357 assignment1
= (PsiAssignmentExpression
)CodeStyleManager
.getInstance(myProject
).reformat(assignment1
);
358 assignment1
.getLExpression().replace(expr
);
359 assignment1
.getRExpression().replace(binExpr
);
360 setExpr
= assignment1
;
363 assignment
.replace(setExpr
);
364 //TODO: check if value is used!!!
368 else if (RefactoringUtil
.isPlusPlusOrMinusMinus(parent
)){
370 if (parent
instanceof PsiPrefixExpression
){
371 sign
= ((PsiPrefixExpression
)parent
).getOperationSign();
374 sign
= ((PsiPostfixExpression
)parent
).getOperationSign();
376 IElementType tokenType
= sign
.getTokenType();
378 PsiExpression getExpr
= expr
;
380 final int fieldIndex
= usage
.fieldIndex
;
381 final PsiMethodCallExpression getterCall
= createGetterCall(fieldIndex
, expr
);
382 if(getterCall
!= null) {
383 getExpr
= getterCall
;
388 if (tokenType
== JavaTokenType
.PLUSPLUS
){
394 PsiBinaryExpression binExpr
= (PsiBinaryExpression
)factory
.createExpressionFromText(text
, null);
395 binExpr
= (PsiBinaryExpression
)CodeStyleManager
.getInstance(myProject
).reformat(binExpr
);
396 binExpr
.getLOperand().replace(getExpr
);
398 PsiExpression setExpr
;
400 final int fieldIndex
= usage
.fieldIndex
;
401 setExpr
= createSetterCall(fieldIndex
, binExpr
, expr
);
405 PsiAssignmentExpression assignment
= (PsiAssignmentExpression
)factory
.createExpressionFromText(text
, null);
406 assignment
= (PsiAssignmentExpression
)CodeStyleManager
.getInstance(myProject
).reformat(assignment
);
407 assignment
.getLExpression().replace(expr
);
408 assignment
.getRExpression().replace(binExpr
);
409 setExpr
= assignment
;
411 parent
.replace(setExpr
);
414 if (!processGet
) return;
415 PsiMethodCallExpression methodCall
= createGetterCall(usage
.fieldIndex
, expr
);
417 if (methodCall
!= null) {
418 expr
.replace(methodCall
);
422 catch(IncorrectOperationException e
){
427 private PsiMethodCallExpression
createSetterCall(final int fieldIndex
, final PsiExpression setterArgument
, PsiReferenceExpression expr
) throws IncorrectOperationException
{
428 String
[] setterNames
= myDialog
.getSetterNames();
429 PsiElementFactory factory
= JavaPsiFacade
.getInstance(expr
.getProject()).getElementFactory();
430 final String setterName
= setterNames
[fieldIndex
];
431 @NonNls String text
= setterName
+ "(a)";
432 PsiExpression qualifier
= expr
.getQualifierExpression();
433 if (qualifier
!= null){
436 PsiMethodCallExpression methodCall
= (PsiMethodCallExpression
)factory
.createExpressionFromText(text
, expr
);
437 methodCall
= (PsiMethodCallExpression
)CodeStyleManager
.getInstance(myProject
).reformat(methodCall
);
439 methodCall
.getArgumentList().getExpressions()[0].replace(setterArgument
);
440 if (qualifier
!= null){
441 methodCall
.getMethodExpression().getQualifierExpression().replace(qualifier
);
443 final PsiMethod targetMethod
= myNameToSetter
.get(setterName
);
444 methodCall
= checkMethodResolvable(methodCall
, targetMethod
, expr
);
445 if (methodCall
== null) {
446 VisibilityUtil
.escalateVisibility(myFields
[fieldIndex
], expr
);
452 private PsiMethodCallExpression
createGetterCall(final int fieldIndex
, PsiReferenceExpression expr
)
453 throws IncorrectOperationException
{
454 String
[] getterNames
= myDialog
.getGetterNames();
455 PsiElementFactory factory
= JavaPsiFacade
.getInstance(expr
.getProject()).getElementFactory();
456 final String getterName
= getterNames
[fieldIndex
];
457 @NonNls String text
= getterName
+ "()";
458 PsiExpression qualifier
= expr
.getQualifierExpression();
459 if (qualifier
!= null){
462 PsiMethodCallExpression methodCall
= (PsiMethodCallExpression
)factory
.createExpressionFromText(text
, expr
);
463 methodCall
= (PsiMethodCallExpression
)CodeStyleManager
.getInstance(myProject
).reformat(methodCall
);
465 if (qualifier
!= null){
466 methodCall
.getMethodExpression().getQualifierExpression().replace(qualifier
);
469 final PsiMethod targetMethod
= myNameToGetter
.get(getterName
);
470 methodCall
= checkMethodResolvable(methodCall
, targetMethod
, expr
);
471 if(methodCall
== null) {
472 VisibilityUtil
.escalateVisibility(myFields
[fieldIndex
], expr
);
478 private PsiMethodCallExpression
checkMethodResolvable(PsiMethodCallExpression methodCall
, final PsiMethod targetMethod
, PsiReferenceExpression context
) throws IncorrectOperationException
{
479 PsiElementFactory factory
= JavaPsiFacade
.getInstance(targetMethod
.getProject()).getElementFactory();
480 final PsiElement resolved
= methodCall
.getMethodExpression().resolve();
481 if (resolved
!= targetMethod
) {
482 PsiClass containingClass
;
483 if (resolved
instanceof PsiMethod
) {
484 containingClass
= ((PsiMethod
) resolved
).getContainingClass();
485 } else if (resolved
instanceof PsiClass
) {
486 containingClass
= (PsiClass
)resolved
;
491 if(containingClass
.isInheritor(myClass
, false)) {
492 final PsiExpression newMethodExpression
=
493 factory
.createExpressionFromText("super." + targetMethod
.getName(), context
);
494 methodCall
.getMethodExpression().replace(newMethodExpression
);
504 private static class MyUsageInfo
extends UsageInfo
{
505 public final int fieldIndex
;
507 public MyUsageInfo(PsiJavaCodeReferenceElement ref
, int fieldIndex
) {
509 this.fieldIndex
= fieldIndex
;