update copyright
[fedora-idea.git] / java / java-impl / src / com / intellij / refactoring / encapsulateFields / EncapsulateFieldsProcessor.java
blobb87c48778b83e695634b7e91f0b6ce62a5f8139a
2 /*
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;
49 import java.util.Map;
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) {
62 super(project);
63 myDialog = 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));
76 public void doRun() {
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);
81 return;
83 myClass = myFields[0].getContainingClass();
85 super.doRun();
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);
97 dialog.show();
98 if(!dialog.isOK()){
99 if (dialog.isShowConflicts()) prepareSuccessful();
100 return false;
105 prepareSuccessful();
106 return true;
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();
140 try{
141 PsiField field = factory.createField("a", PsiType.INT);
142 setNewFieldVisibility(field);
143 newModifierList = field.getModifierList();
145 catch(IncorrectOperationException e){
146 LOG.error(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;
161 if (!findGet) {
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)) {
175 continue;
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){
203 // "as is"
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) {
242 processUsage(info);
247 private void setNewFieldVisibility(PsiField field) {
248 try{
249 if (myDialog.getFieldsVisibility() != null){
250 field.normalizeDeclaration();
251 PsiUtil.setModifierProperty(field, myDialog.getFieldsVisibility(), true);
254 catch(IncorrectOperationException e){
255 LOG.error(e);
259 private void addOrChangeAccessor(PsiMethod prototype, HashMap<String,PsiMethod> nameToAncestor) {
260 PsiMethod existing = myClass.findMethodBySignature(prototype, false);
261 PsiMethod result = existing;
262 try{
263 if (existing == null){
264 PsiUtil.setModifierProperty(prototype, myDialog.getAccessorsVisibility(), true);
265 result = (PsiMethod) myClass.add(prototype);
267 else{
268 //TODO : change visibility
270 nameToAncestor.put(prototype.getName(), result);
272 catch(IncorrectOperationException e){
273 LOG.error(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();
286 return false;
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();
296 try{
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;
336 if (processGet) {
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;
351 if (processSet) {
352 setExpr = createSetterCall(usage.fieldIndex, binExpr, expr);
354 else {
355 text = "a = b";
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)){
369 PsiJavaToken sign;
370 if (parent instanceof PsiPrefixExpression){
371 sign = ((PsiPrefixExpression)parent).getOperationSign();
373 else{
374 sign = ((PsiPostfixExpression)parent).getOperationSign();
376 IElementType tokenType = sign.getTokenType();
378 PsiExpression getExpr = expr;
379 if (processGet){
380 final int fieldIndex = usage.fieldIndex;
381 final PsiMethodCallExpression getterCall = createGetterCall(fieldIndex, expr);
382 if(getterCall != null) {
383 getExpr = getterCall;
387 @NonNls String text;
388 if (tokenType == JavaTokenType.PLUSPLUS){
389 text = "a+1";
391 else{
392 text = "a-1";
394 PsiBinaryExpression binExpr = (PsiBinaryExpression)factory.createExpressionFromText(text, null);
395 binExpr = (PsiBinaryExpression)CodeStyleManager.getInstance(myProject).reformat(binExpr);
396 binExpr.getLOperand().replace(getExpr);
398 PsiExpression setExpr;
399 if (processSet){
400 final int fieldIndex = usage.fieldIndex;
401 setExpr = createSetterCall(fieldIndex, binExpr, expr);
403 else{
404 text = "a = b";
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);
413 else{
414 if (!processGet) return;
415 PsiMethodCallExpression methodCall = createGetterCall(usage.fieldIndex, expr);
417 if (methodCall != null) {
418 expr.replace(methodCall);
422 catch(IncorrectOperationException e){
423 LOG.error(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){
434 text = "q." + text;
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);
448 return methodCall;
451 @Nullable
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){
460 text = "q." + text;
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);
474 return methodCall;
477 @Nullable
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;
488 else {
489 return null;
491 if(containingClass.isInheritor(myClass, false)) {
492 final PsiExpression newMethodExpression =
493 factory.createExpressionFromText("super." + targetMethod.getName(), context);
494 methodCall.getMethodExpression().replace(newMethodExpression);
495 } else {
496 methodCall = null;
499 return methodCall;
504 private static class MyUsageInfo extends UsageInfo {
505 public final int fieldIndex;
507 public MyUsageInfo(PsiJavaCodeReferenceElement ref, int fieldIndex) {
508 super(ref);
509 this.fieldIndex = fieldIndex;