encapsulate fields: javadoc placement settings (IDEA-52195)
[fedora-idea.git] / java / java-impl / src / com / intellij / refactoring / encapsulateFields / EncapsulateFieldsProcessor.java
blobb2d86624a06f88abd0510728ab49ebff88e42c44
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.javadoc.PsiDocComment;
26 import com.intellij.psi.search.searches.ClassInheritorsSearch;
27 import com.intellij.psi.search.searches.ReferencesSearch;
28 import com.intellij.psi.tree.IElementType;
29 import com.intellij.psi.util.InheritanceUtil;
30 import com.intellij.psi.util.PsiFormatUtil;
31 import com.intellij.psi.util.PsiTreeUtil;
32 import com.intellij.psi.util.PsiUtil;
33 import com.intellij.refactoring.BaseRefactoringProcessor;
34 import com.intellij.refactoring.RefactoringBundle;
35 import com.intellij.refactoring.util.CommonRefactoringUtil;
36 import com.intellij.refactoring.util.DocCommentPolicy;
37 import com.intellij.refactoring.util.RefactoringUIUtil;
38 import com.intellij.refactoring.util.RefactoringUtil;
39 import com.intellij.usageView.UsageInfo;
40 import com.intellij.usageView.UsageViewDescriptor;
41 import com.intellij.usageView.UsageViewUtil;
42 import com.intellij.util.IncorrectOperationException;
43 import com.intellij.util.VisibilityUtil;
44 import com.intellij.util.containers.HashMap;
45 import com.intellij.util.containers.MultiMap;
46 import org.jetbrains.annotations.NonNls;
47 import org.jetbrains.annotations.NotNull;
48 import org.jetbrains.annotations.Nullable;
50 import java.util.*;
52 public class EncapsulateFieldsProcessor extends BaseRefactoringProcessor {
53 private static final Logger LOG = Logger.getInstance("#com.intellij.refactoring.encapsulateFields.EncapsulateFieldsProcessor");
55 private PsiClass myClass;
56 @NotNull
57 private final EncapsulateFieldsDescriptor myDescriptor;
58 private PsiField[] myFields;
60 private HashMap<String,PsiMethod> myNameToGetter;
61 private HashMap<String,PsiMethod> myNameToSetter;
63 public EncapsulateFieldsProcessor(Project project, @NotNull EncapsulateFieldsDescriptor descriptor) {
64 super(project);
65 myDescriptor = descriptor;
66 myFields = myDescriptor.getSelectedFields();
67 myClass = myFields[0].getContainingClass();
70 protected UsageViewDescriptor createUsageViewDescriptor(UsageInfo[] usages) {
71 PsiField[] fields = new PsiField[myFields.length];
72 System.arraycopy(myFields, 0, fields, 0, myFields.length);
73 return new EncapsulateFieldsViewDescriptor(fields);
76 protected String getCommandName() {
77 return RefactoringBundle.message("encapsulate.fields.command.name", UsageViewUtil.getDescriptiveName(myClass));
80 protected boolean preprocessUsages(Ref<UsageInfo[]> refUsages) {
81 final MultiMap<PsiElement, String> conflicts = new MultiMap<PsiElement, String>();
83 final PsiMethod[] getterPrototypes = myDescriptor.getGetterPrototypes();
84 final PsiMethod[] setterPrototypes = myDescriptor.getSetterPrototypes();
86 checkExistingMethods(getterPrototypes, conflicts, true);
87 checkExistingMethods(setterPrototypes, conflicts, false);
88 final Collection<PsiClass> classes = ClassInheritorsSearch.search(myClass).findAll();
89 for (int i = 0; i < myFields.length; i++) {
90 final PsiField field = myFields[i];
91 final Set<PsiMethod> setters = new HashSet<PsiMethod>();
92 final Set<PsiMethod> getters = new HashSet<PsiMethod>();
94 for (PsiClass aClass : classes) {
95 final PsiMethod getterOverrider = getterPrototypes != null ? aClass.findMethodBySignature(getterPrototypes[i], false) : null;
96 if (getterOverrider != null) {
97 getters.add(getterOverrider);
99 final PsiMethod setterOverrider = setterPrototypes != null ? aClass.findMethodBySignature(setterPrototypes[i], false) : null;
100 if (setterOverrider != null) {
101 setters.add(setterOverrider);
104 if (!getters.isEmpty() || !setters.isEmpty()) {
105 for (PsiReference reference : ReferencesSearch.search(field)) {
106 final PsiElement place = reference.getElement();
107 LOG.assertTrue(place instanceof PsiReferenceExpression);
108 final PsiExpression qualifierExpression = ((PsiReferenceExpression)place).getQualifierExpression();
109 final PsiClass ancestor;
110 if (qualifierExpression == null) {
111 ancestor = PsiTreeUtil.getParentOfType(place, PsiClass.class, false);
113 else {
114 ancestor = PsiUtil.resolveClassInType(qualifierExpression.getType());
117 final boolean isGetter = !PsiUtil.isAccessedForWriting((PsiExpression)place);
118 for (PsiMethod overridden : isGetter ? getters : setters) {
119 if (InheritanceUtil.isInheritorOrSelf(myClass, ancestor, true)) {
120 conflicts.putValue(overridden, "There is already a " +
121 CommonRefactoringUtil.htmlEmphasize(RefactoringUIUtil.getDescription(overridden, true)) +
122 " which would hide generated " +
123 (isGetter ? "getter" : "setter") + " for " + place.getText());
124 break;
130 return showConflicts(conflicts);
133 private void checkExistingMethods(PsiMethod[] prototypes, MultiMap<PsiElement, String> conflicts, boolean isGetter) {
134 if(prototypes == null) return;
135 for (PsiMethod prototype : prototypes) {
136 final PsiType prototypeReturnType = prototype.getReturnType();
137 PsiMethod existing = myClass.findMethodBySignature(prototype, true);
138 if (existing != null) {
139 final PsiType returnType = existing.getReturnType();
140 if (!RefactoringUtil.equivalentTypes(prototypeReturnType, returnType, myClass.getManager())) {
141 final String descr = PsiFormatUtil.formatMethod(existing,
142 PsiSubstitutor.EMPTY,
143 PsiFormatUtil.SHOW_NAME | PsiFormatUtil.SHOW_PARAMETERS | PsiFormatUtil.SHOW_TYPE,
144 PsiFormatUtil.SHOW_TYPE
146 String message = isGetter ?
147 RefactoringBundle.message("encapsulate.fields.getter.exists", CommonRefactoringUtil.htmlEmphasize(descr),
148 CommonRefactoringUtil.htmlEmphasize(prototype.getName())) :
149 RefactoringBundle.message("encapsulate.fields.setter.exists", CommonRefactoringUtil.htmlEmphasize(descr),
150 CommonRefactoringUtil.htmlEmphasize(prototype.getName()));
151 conflicts.putValue(existing, message);
153 } else {
154 PsiClass containingClass = myClass.getContainingClass();
155 while (containingClass != null && existing == null) {
156 existing = containingClass.findMethodBySignature(prototype, true);
157 if (existing != null) {
158 for (PsiReference reference : ReferencesSearch.search(existing)) {
159 final PsiElement place = reference.getElement();
160 LOG.assertTrue(place instanceof PsiReferenceExpression);
161 final PsiExpression qualifierExpression = ((PsiReferenceExpression)place).getQualifierExpression();
162 final PsiClass inheritor;
163 if (qualifierExpression == null) {
164 inheritor = PsiTreeUtil.getParentOfType(place, PsiClass.class, false);
165 } else {
166 inheritor = PsiUtil.resolveClassInType(qualifierExpression.getType());
169 if (InheritanceUtil.isInheritorOrSelf(inheritor, myClass, true)) {
170 conflicts.putValue(existing, "There is already a " + CommonRefactoringUtil.htmlEmphasize(RefactoringUIUtil.getDescription(existing, true)) + " which would be hidden by generated " + (isGetter ? "getter" : "setter"));
171 break;
175 containingClass = containingClass.getContainingClass();
181 @NotNull protected UsageInfo[] findUsages() {
182 boolean findGet = myDescriptor.isToEncapsulateGet();
183 boolean findSet = myDescriptor.isToEncapsulateSet();
184 PsiModifierList newModifierList = null;
185 final JavaPsiFacade facade = JavaPsiFacade.getInstance(myProject);
186 if (!myDescriptor.isToUseAccessorsWhenAccessible()){
187 PsiElementFactory factory = facade.getElementFactory();
188 try{
189 PsiField field = factory.createField("a", PsiType.INT);
190 setNewFieldVisibility(field);
191 newModifierList = field.getModifierList();
193 catch(IncorrectOperationException e){
194 LOG.error(e);
197 PsiMethod[] getterPrototypes = myDescriptor.getGetterPrototypes();
198 PsiMethod[] setterPrototypes = myDescriptor.getSetterPrototypes();
199 ArrayList<UsageInfo> array = new ArrayList<UsageInfo>();
200 PsiField[] fields = myFields;
201 for(int i = 0; i < fields.length; i++){
202 PsiField field = fields[i];
203 for (final PsiReference reference : ReferencesSearch.search(field)) {
204 if (!(reference instanceof PsiReferenceExpression)) continue;
205 PsiReferenceExpression ref = (PsiReferenceExpression)reference;
206 // [Jeka] to avoid recursion in the field's accessors
207 if (findGet && isUsedInExistingAccessor(getterPrototypes[i], ref)) continue;
208 if (findSet && isUsedInExistingAccessor(setterPrototypes[i], ref)) continue;
209 if (!findGet) {
210 if (!PsiUtil.isAccessedForWriting(ref)) continue;
212 if (!findSet || field.hasModifierProperty(PsiModifier.FINAL)) {
213 if (!PsiUtil.isAccessedForReading(ref)) continue;
215 if (!myDescriptor.isToUseAccessorsWhenAccessible()) {
216 PsiClass accessObjectClass = null;
217 PsiExpression qualifier = ref.getQualifierExpression();
218 if (qualifier != null) {
219 accessObjectClass = (PsiClass)PsiUtil.getAccessObjectClass(qualifier).getElement();
221 if (facade.getResolveHelper()
222 .isAccessible(field, newModifierList, ref, accessObjectClass, null)) {
223 continue;
226 UsageInfo usageInfo = new MyUsageInfo(ref, i);
227 array.add(usageInfo);
230 MyUsageInfo[] usageInfos = array.toArray(new MyUsageInfo[array.size()]);
231 return UsageViewUtil.removeDuplicatedUsages(usageInfos);
234 protected void refreshElements(PsiElement[] elements) {
235 LOG.assertTrue(elements.length == myFields.length);
237 for (int idx = 0; idx < elements.length; idx++) {
238 PsiElement element = elements[idx];
240 LOG.assertTrue(element instanceof PsiField);
242 myFields[idx] = (PsiField)element;
245 myClass = myFields[0].getContainingClass();
248 protected void performRefactoring(UsageInfo[] usages) {
249 // change visibility of fields
250 if (myDescriptor.getFieldsVisibility() != null){
251 // "as is"
252 for (PsiField field : myFields) {
253 setNewFieldVisibility(field);
257 // generate accessors
258 myNameToGetter = new HashMap<String, PsiMethod>();
259 myNameToSetter = new HashMap<String, PsiMethod>();
260 for(int i = 0; i < myFields.length; i++){
261 final DocCommentPolicy<PsiDocComment> commentPolicy = new DocCommentPolicy<PsiDocComment>(myDescriptor.getJavadocPolicy());
262 PsiField field = myFields[i];
263 final PsiDocComment docComment = field.getDocComment();
264 if (myDescriptor.isToEncapsulateGet()){
265 PsiMethod[] prototypes = myDescriptor.getGetterPrototypes();
266 assert prototypes != null;
267 final PsiMethod getter = addOrChangeAccessor(prototypes[i], myNameToGetter);
268 if (docComment != null) {
269 final PsiDocComment getterJavadoc = (PsiDocComment)getter.addBefore(docComment, getter.getFirstChild());
270 commentPolicy.processNewJavaDoc(getterJavadoc);
273 if (myDescriptor.isToEncapsulateSet() && !field.hasModifierProperty(PsiModifier.FINAL)){
274 PsiMethod[] prototypes = myDescriptor.getSetterPrototypes();
275 assert prototypes != null;
276 addOrChangeAccessor(prototypes[i], myNameToSetter);
279 if (docComment != null) {
280 commentPolicy.processOldJavaDoc(docComment);
284 Map<PsiFile, List<MyUsageInfo>> usagesInFiles = new HashMap<PsiFile, List<MyUsageInfo>>();
285 for (UsageInfo usage : usages) {
286 PsiElement element = usage.getElement();
287 if (element == null) continue;
288 final PsiFile file = element.getContainingFile();
289 List<MyUsageInfo> usagesInFile = usagesInFiles.get(file);
290 if (usagesInFile == null) {
291 usagesInFile = new ArrayList<MyUsageInfo>();
292 usagesInFiles.put(file, usagesInFile);
294 usagesInFile.add(((MyUsageInfo)usage));
297 for (List<MyUsageInfo> usageInfos : usagesInFiles.values()) {
298 //this is to avoid elements to become invalid as a result of processUsage
299 final MyUsageInfo[] infos = usageInfos.toArray(new MyUsageInfo[usageInfos.size()]);
300 RefactoringUtil.sortDepthFirstRightLeftOrder(infos);
302 for (MyUsageInfo info : infos) {
303 processUsage(info);
308 private void setNewFieldVisibility(PsiField field) {
309 try{
310 if (myDescriptor.getFieldsVisibility() != null){
311 field.normalizeDeclaration();
312 PsiUtil.setModifierProperty(field, myDescriptor.getFieldsVisibility(), true);
315 catch(IncorrectOperationException e){
316 LOG.error(e);
320 private PsiMethod addOrChangeAccessor(PsiMethod prototype, HashMap<String,PsiMethod> nameToAncestor) {
321 PsiMethod existing = myClass.findMethodBySignature(prototype, false);
322 PsiMethod result = existing;
323 try{
324 if (existing == null){
325 PsiUtil.setModifierProperty(prototype, myDescriptor.getAccessorsVisibility(), true);
326 result = (PsiMethod) myClass.add(prototype);
328 else{
329 //TODO : change visibility
331 nameToAncestor.put(prototype.getName(), result);
332 return result;
334 catch(IncorrectOperationException e){
335 LOG.error(e);
337 return null;
340 private boolean isUsedInExistingAccessor(PsiMethod prototype, PsiElement element) {
341 PsiMethod existingAccessor = myClass.findMethodBySignature(prototype, false);
342 if (existingAccessor != null) {
343 PsiElement parent = element;
344 while (parent != null) {
345 if (existingAccessor.equals(parent)) return true;
346 parent = parent.getParent();
349 return false;
352 private void processUsage(MyUsageInfo usage) {
353 PsiField field = myFields[usage.fieldIndex];
354 boolean processGet = myDescriptor.isToEncapsulateGet();
355 boolean processSet = myDescriptor.isToEncapsulateSet() && !field.hasModifierProperty(PsiModifier.FINAL);
356 if (!processGet && !processSet) return;
357 PsiElementFactory factory = JavaPsiFacade.getInstance(myProject).getElementFactory();
359 try{
360 final PsiReferenceExpression expr = (PsiReferenceExpression)usage.getElement();
361 if (expr == null) return;
362 final PsiElement parent = expr.getParent();
363 if (parent instanceof PsiAssignmentExpression && expr.equals(((PsiAssignmentExpression)parent).getLExpression())){
364 PsiAssignmentExpression assignment = (PsiAssignmentExpression)parent;
365 if (assignment.getRExpression() == null) return;
366 PsiJavaToken opSign = assignment.getOperationSign();
367 IElementType opType = opSign.getTokenType();
368 if (opType == JavaTokenType.EQ) {
370 if (!processSet) return;
371 final int fieldIndex = usage.fieldIndex;
372 final PsiExpression setterArgument = assignment.getRExpression();
374 PsiMethodCallExpression methodCall = createSetterCall(fieldIndex, setterArgument, expr);
376 if (methodCall != null) {
377 assignment.replace(methodCall);
379 //TODO: check if value is used!!!
382 else if (opType == JavaTokenType.ASTERISKEQ || opType == JavaTokenType.DIVEQ || opType == JavaTokenType.PERCEQ ||
383 opType == JavaTokenType.PLUSEQ ||
384 opType == JavaTokenType.MINUSEQ ||
385 opType == JavaTokenType.LTLTEQ ||
386 opType == JavaTokenType.GTGTEQ ||
387 opType == JavaTokenType.GTGTGTEQ ||
388 opType == JavaTokenType.ANDEQ ||
389 opType == JavaTokenType.OREQ ||
390 opType == JavaTokenType.XOREQ) {
392 // Q: side effects of qualifier??!
394 String opName = opSign.getText();
395 LOG.assertTrue(StringUtil.endsWithChar(opName, '='));
396 opName = opName.substring(0, opName.length() - 1);
398 PsiExpression getExpr = expr;
399 if (processGet) {
400 final int fieldIndex = usage.fieldIndex;
401 final PsiMethodCallExpression getterCall = createGetterCall(fieldIndex, expr);
402 if (getterCall != null) {
403 getExpr = getterCall;
407 @NonNls String text = "a" + opName + "b";
408 PsiBinaryExpression binExpr = (PsiBinaryExpression)factory.createExpressionFromText(text, expr);
409 binExpr = (PsiBinaryExpression)CodeStyleManager.getInstance(myProject).reformat(binExpr);
410 binExpr.getLOperand().replace(getExpr);
411 binExpr.getROperand().replace(assignment.getRExpression());
413 PsiExpression setExpr;
414 if (processSet) {
415 setExpr = createSetterCall(usage.fieldIndex, binExpr, expr);
417 else {
418 text = "a = b";
419 PsiAssignmentExpression assignment1 = (PsiAssignmentExpression)factory.createExpressionFromText(text, null);
420 assignment1 = (PsiAssignmentExpression)CodeStyleManager.getInstance(myProject).reformat(assignment1);
421 assignment1.getLExpression().replace(expr);
422 assignment1.getRExpression().replace(binExpr);
423 setExpr = assignment1;
426 assignment.replace(setExpr);
427 //TODO: check if value is used!!!
431 else if (RefactoringUtil.isPlusPlusOrMinusMinus(parent)){
432 PsiJavaToken sign;
433 if (parent instanceof PsiPrefixExpression){
434 sign = ((PsiPrefixExpression)parent).getOperationSign();
436 else{
437 sign = ((PsiPostfixExpression)parent).getOperationSign();
439 IElementType tokenType = sign.getTokenType();
441 PsiExpression getExpr = expr;
442 if (processGet){
443 final int fieldIndex = usage.fieldIndex;
444 final PsiMethodCallExpression getterCall = createGetterCall(fieldIndex, expr);
445 if(getterCall != null) {
446 getExpr = getterCall;
450 @NonNls String text;
451 if (tokenType == JavaTokenType.PLUSPLUS){
452 text = "a+1";
454 else{
455 text = "a-1";
457 PsiBinaryExpression binExpr = (PsiBinaryExpression)factory.createExpressionFromText(text, null);
458 binExpr = (PsiBinaryExpression)CodeStyleManager.getInstance(myProject).reformat(binExpr);
459 binExpr.getLOperand().replace(getExpr);
461 PsiExpression setExpr;
462 if (processSet){
463 final int fieldIndex = usage.fieldIndex;
464 setExpr = createSetterCall(fieldIndex, binExpr, expr);
466 else{
467 text = "a = b";
468 PsiAssignmentExpression assignment = (PsiAssignmentExpression)factory.createExpressionFromText(text, null);
469 assignment = (PsiAssignmentExpression)CodeStyleManager.getInstance(myProject).reformat(assignment);
470 assignment.getLExpression().replace(expr);
471 assignment.getRExpression().replace(binExpr);
472 setExpr = assignment;
474 parent.replace(setExpr);
476 else{
477 if (!processGet) return;
478 PsiMethodCallExpression methodCall = createGetterCall(usage.fieldIndex, expr);
480 if (methodCall != null) {
481 expr.replace(methodCall);
485 catch(IncorrectOperationException e){
486 LOG.error(e);
490 private PsiMethodCallExpression createSetterCall(final int fieldIndex, final PsiExpression setterArgument, PsiReferenceExpression expr) throws IncorrectOperationException {
491 String[] setterNames = myDescriptor.getSetterNames();
492 PsiElementFactory factory = JavaPsiFacade.getInstance(expr.getProject()).getElementFactory();
493 final String setterName = setterNames[fieldIndex];
494 @NonNls String text = setterName + "(a)";
495 PsiExpression qualifier = expr.getQualifierExpression();
496 if (qualifier != null){
497 text = "q." + text;
499 PsiMethodCallExpression methodCall = (PsiMethodCallExpression)factory.createExpressionFromText(text, expr);
500 methodCall = (PsiMethodCallExpression)CodeStyleManager.getInstance(myProject).reformat(methodCall);
502 methodCall.getArgumentList().getExpressions()[0].replace(setterArgument);
503 if (qualifier != null){
504 methodCall.getMethodExpression().getQualifierExpression().replace(qualifier);
506 final PsiMethod targetMethod = myNameToSetter.get(setterName);
507 methodCall = checkMethodResolvable(methodCall, targetMethod, expr);
508 if (methodCall == null) {
509 VisibilityUtil.escalateVisibility(myFields[fieldIndex], expr);
511 return methodCall;
514 @Nullable
515 private PsiMethodCallExpression createGetterCall(final int fieldIndex, PsiReferenceExpression expr)
516 throws IncorrectOperationException {
517 String[] getterNames = myDescriptor.getGetterNames();
518 PsiElementFactory factory = JavaPsiFacade.getInstance(expr.getProject()).getElementFactory();
519 final String getterName = getterNames[fieldIndex];
520 @NonNls String text = getterName + "()";
521 PsiExpression qualifier = expr.getQualifierExpression();
522 if (qualifier != null){
523 text = "q." + text;
525 PsiMethodCallExpression methodCall = (PsiMethodCallExpression)factory.createExpressionFromText(text, expr);
526 methodCall = (PsiMethodCallExpression)CodeStyleManager.getInstance(myProject).reformat(methodCall);
528 if (qualifier != null){
529 methodCall.getMethodExpression().getQualifierExpression().replace(qualifier);
532 final PsiMethod targetMethod = myNameToGetter.get(getterName);
533 methodCall = checkMethodResolvable(methodCall, targetMethod, expr);
534 if(methodCall == null) {
535 VisibilityUtil.escalateVisibility(myFields[fieldIndex], expr);
537 return methodCall;
540 @Nullable
541 private PsiMethodCallExpression checkMethodResolvable(PsiMethodCallExpression methodCall, final PsiMethod targetMethod, PsiReferenceExpression context) throws IncorrectOperationException {
542 PsiElementFactory factory = JavaPsiFacade.getInstance(targetMethod.getProject()).getElementFactory();
543 final PsiElement resolved = methodCall.getMethodExpression().resolve();
544 if (resolved != targetMethod) {
545 PsiClass containingClass;
546 if (resolved instanceof PsiMethod) {
547 containingClass = ((PsiMethod) resolved).getContainingClass();
548 } else if (resolved instanceof PsiClass) {
549 containingClass = (PsiClass)resolved;
551 else {
552 return null;
554 if(containingClass != null && containingClass.isInheritor(myClass, false)) {
555 final PsiExpression newMethodExpression =
556 factory.createExpressionFromText("super." + targetMethod.getName(), context);
557 methodCall.getMethodExpression().replace(newMethodExpression);
558 } else {
559 methodCall = null;
562 return methodCall;
567 private static class MyUsageInfo extends UsageInfo {
568 public final int fieldIndex;
570 public MyUsageInfo(PsiJavaCodeReferenceElement ref, int fieldIndex) {
571 super(ref);
572 this.fieldIndex = fieldIndex;