move member: restore qualifier in switch labels for non-enum constants (IDEA-147539)
[idea/community.git] / java / java-impl / src / com / intellij / refactoring / move / moveMembers / MoveJavaMemberHandler.java
1 /*
2  * Copyright 2000-2009 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 package com.intellij.refactoring.move.moveMembers;
17
18 import com.intellij.codeInsight.ChangeContextUtil;
19 import com.intellij.codeInsight.highlighting.ReadWriteAccessDetector;
20 import com.intellij.openapi.project.Project;
21 import com.intellij.psi.*;
22 import com.intellij.psi.impl.source.resolve.JavaResolveUtil;
23 import com.intellij.psi.search.GlobalSearchScope;
24 import com.intellij.psi.search.LocalSearchScope;
25 import com.intellij.psi.search.searches.ReferencesSearch;
26 import com.intellij.psi.util.*;
27 import com.intellij.refactoring.RefactoringBundle;
28 import com.intellij.refactoring.util.*;
29 import com.intellij.util.IncorrectOperationException;
30 import com.intellij.util.VisibilityUtil;
31 import com.intellij.util.containers.MultiMap;
32 import org.jetbrains.annotations.NotNull;
33 import org.jetbrains.annotations.Nullable;
34
35 import java.util.*;
36
37 /**
38  * @author Maxim.Medvedev
39  */
40 public class MoveJavaMemberHandler implements MoveMemberHandler {
41   @Override
42   @Nullable
43   public MoveMembersProcessor.MoveMembersUsageInfo getUsage(@NotNull PsiMember member,
44                                                             @NotNull PsiReference psiReference,
45                                                             @NotNull Set<PsiMember> membersToMove,
46                                                             @NotNull PsiClass targetClass) {
47     PsiElement ref = psiReference.getElement();
48     if (ref instanceof PsiReferenceExpression) {
49       PsiReferenceExpression refExpr = (PsiReferenceExpression)ref;
50       PsiExpression qualifier = refExpr.getQualifierExpression();
51       if (RefactoringHierarchyUtil.willBeInTargetClass(refExpr, membersToMove, targetClass, true)) {
52         // both member and the reference to it will be in target class
53         if (!RefactoringUtil.isInMovedElement(refExpr, membersToMove)) {
54           if (qualifier != null) {
55             return new MoveMembersProcessor.MoveMembersUsageInfo(member, refExpr, null, qualifier, psiReference);  // remove qualifier
56           }
57         }
58         else {
59           if (qualifier instanceof PsiReferenceExpression &&
60               ((PsiReferenceExpression)qualifier).isReferenceTo(member.getContainingClass())) {
61             return new MoveMembersProcessor.MoveMembersUsageInfo(member, refExpr, targetClass, qualifier, psiReference);  // change qualifier
62           }
63         }
64       }
65       else {
66         // member in target class, the reference will be outside target class
67         if (qualifier == null) {
68           return new MoveMembersProcessor.MoveMembersUsageInfo(member, refExpr, targetClass, refExpr, psiReference); // add qualifier
69         }
70         else {
71           return new MoveMembersProcessor.MoveMembersUsageInfo(member, refExpr, targetClass, qualifier, psiReference); // change qualifier
72         }
73       }
74     }
75     return null;
76   }
77
78   @Override
79   public void checkConflictsOnUsage(@NotNull MoveMembersProcessor.MoveMembersUsageInfo usageInfo,
80                                     @Nullable String newVisibility,
81                                     @Nullable PsiModifierList modifierListCopy,
82                                     @NotNull PsiClass targetClass,
83                                     @NotNull Set<PsiMember> membersToMove,
84                                     @NotNull MultiMap<PsiElement, String> conflicts) {
85     final PsiElement element = usageInfo.getElement();
86     if (element == null) return;
87
88     final PsiMember member = usageInfo.member;
89     if (element instanceof PsiReferenceExpression) {
90       PsiExpression qualifier = ((PsiReferenceExpression)element).getQualifierExpression();
91       PsiClass accessObjectClass = null;
92       if (qualifier != null) {
93         accessObjectClass = (PsiClass)PsiUtil.getAccessObjectClass(qualifier).getElement();
94       }
95
96       if (!JavaResolveUtil.isAccessible(member, targetClass, modifierListCopy, element, accessObjectClass, null)) {
97         String visibility = newVisibility != null ? newVisibility : VisibilityUtil.getVisibilityStringToDisplay(member);
98         String message = RefactoringBundle.message("0.with.1.visibility.is.not.accessible.from.2",
99                                                    RefactoringUIUtil.getDescription(member, false),
100                                                    visibility,
101                                                    RefactoringUIUtil.getDescription(ConflictsUtil.getContainer(element), true));
102         conflicts.putValue(member, CommonRefactoringUtil.capitalize(message));
103       }
104     }
105
106     if (member instanceof PsiField && targetClass.isInterface()) {
107       ReadWriteAccessDetector accessDetector = ReadWriteAccessDetector.findDetector(member);
108       if (accessDetector != null) {
109         ReadWriteAccessDetector.Access access = accessDetector.getExpressionAccess(element);
110         if (access != ReadWriteAccessDetector.Access.Read) {
111           String message = RefactoringUIUtil.getDescription(member, true) + " has write access but is moved to an interface";
112           conflicts.putValue(element, CommonRefactoringUtil.capitalize(message));
113         }
114       }
115     } else if (member instanceof PsiField &&
116                usageInfo.reference instanceof PsiExpression &&
117                member.hasModifierProperty(PsiModifier.FINAL) &&
118                PsiUtil.isAccessedForWriting((PsiExpression)usageInfo.reference) &&
119                !RefactoringHierarchyUtil.willBeInTargetClass(usageInfo.reference, membersToMove, targetClass, true)) {
120       conflicts.putValue(usageInfo.member, "final variable initializer won't be available after move.");
121     }
122
123     final PsiReference reference = usageInfo.getReference();
124     if (reference != null) {
125       RefactoringConflictsUtil.checkAccessibilityConflicts(reference, member, modifierListCopy, targetClass, membersToMove, conflicts);
126     }
127   }
128
129   @Override
130   public void checkConflictsOnMember(@NotNull PsiMember member,
131                                      @Nullable String newVisibility,
132                                      @Nullable PsiModifierList modifierListCopy,
133                                      @NotNull PsiClass targetClass,
134                                      @NotNull Set<PsiMember> membersToMove,
135                                      @NotNull MultiMap<PsiElement, String> conflicts) {
136     if (member instanceof PsiMethod && hasMethod(targetClass, (PsiMethod)member) ||
137         member instanceof PsiField && hasField(targetClass, (PsiField)member)) {
138       String message = RefactoringBundle.message("0.already.exists.in.the.target.class", RefactoringUIUtil.getDescription(member, false));
139       conflicts.putValue(member, CommonRefactoringUtil.capitalize(message));
140     }
141
142     RefactoringConflictsUtil.checkUsedElements(member, member, membersToMove, null, targetClass, targetClass, conflicts);
143   }
144
145   protected static boolean hasMethod(PsiClass targetClass, PsiMethod method) {
146     PsiMethod[] targetClassMethods = targetClass.getMethods();
147     for (PsiMethod candidate : targetClassMethods) {
148       if (candidate != method &&
149           MethodSignatureUtil.areSignaturesEqual(method.getSignature(PsiSubstitutor.EMPTY),
150                                                  candidate.getSignature(PsiSubstitutor.EMPTY))) {
151         return true;
152       }
153     }
154     return false;
155   }
156
157   protected static boolean hasField(PsiClass targetClass, PsiField field) {
158     String fieldName = field.getName();
159     PsiField[] targetClassFields = targetClass.getFields();
160     for (PsiField candidate : targetClassFields) {
161       if (candidate != field &&
162           fieldName.equals(candidate.getName())) {
163         return true;
164       }
165     }
166     return false;
167   }
168
169   @Override
170   public boolean changeExternalUsage(@NotNull MoveMembersOptions options, @NotNull MoveMembersProcessor.MoveMembersUsageInfo usage) {
171     final PsiElement element = usage.getElement();
172     if (element == null || !element.isValid()) return true;
173
174     if (usage.reference instanceof PsiReferenceExpression) {
175       PsiReferenceExpression refExpr = (PsiReferenceExpression)usage.reference;
176       PsiExpression qualifier = refExpr.getQualifierExpression();
177       if (qualifier != null) {
178         if (usage.qualifierClass != null && PsiTreeUtil.getParentOfType(refExpr, PsiSwitchLabelStatement.class) == null) {
179           changeQualifier(refExpr, usage.qualifierClass, usage.member);
180         }
181         else {
182           final Project project = element.getProject();
183           final PsiClass targetClass =
184             JavaPsiFacade.getInstance(project).findClass(options.getTargetClassName(), GlobalSearchScope.projectScope(project));
185           if (targetClass != null) {
186             final PsiReferenceParameterList parameterList = refExpr.getParameterList();
187             if ((targetClass.isEnum() || PsiTreeUtil.isAncestor(targetClass, element, true)) && parameterList != null && parameterList.getTypeArguments().length == 0 && !(refExpr instanceof PsiMethodReferenceExpression)) {
188               refExpr.setQualifierExpression(null);
189             }
190             else {
191               changeQualifier(refExpr, targetClass, usage.member);
192             }
193           }
194         }
195       }
196       else { // no qualifier
197         if (usage.qualifierClass != null && (!usage.qualifierClass.isEnum() || PsiTreeUtil.getParentOfType(refExpr, PsiSwitchLabelStatement.class) == null)) {
198           changeQualifier(refExpr, usage.qualifierClass, usage.member);
199         }
200       }
201       return true;
202     }
203     return false;
204   }
205
206   protected static void changeQualifier(PsiReferenceExpression refExpr, PsiClass aClass, PsiMember member) throws IncorrectOperationException {
207     if (RefactoringUtil.hasOnDemandStaticImport(refExpr, aClass) && !(refExpr instanceof PsiMethodReferenceExpression)) {
208       refExpr.setQualifierExpression(null);
209     }
210     else if (!ImportsUtil.hasStaticImportOn(refExpr, member, false)){
211       PsiElementFactory factory = JavaPsiFacade.getInstance(refExpr.getProject()).getElementFactory();
212       refExpr.setQualifierExpression(factory.createReferenceExpression(aClass));
213     }
214   }
215
216   @Override
217   @NotNull
218   public PsiMember doMove(@NotNull MoveMembersOptions options, @NotNull PsiMember member, PsiElement anchor, @NotNull PsiClass targetClass) {
219     if (member instanceof PsiVariable) {
220       ((PsiVariable)member).normalizeDeclaration();
221     }
222
223     ChangeContextUtil.encodeContextInfo(member, true);
224
225     final PsiMember memberCopy;
226     if (options.makeEnumConstant() &&
227         member instanceof PsiVariable &&
228         EnumConstantsUtil.isSuitableForEnumConstant(((PsiVariable)member).getType(), targetClass)) {
229       memberCopy = EnumConstantsUtil.createEnumConstant(targetClass, member.getName(), ((PsiVariable)member).getInitializer());
230     }
231     else {
232       memberCopy = (PsiMember)member.copy();
233       final PsiClass containingClass = member.getContainingClass();
234       if (containingClass != null && containingClass.isInterface() && !targetClass.isInterface()) {
235         // might need to make modifiers explicit, see IDEADEV-11416
236         final PsiModifierList list = memberCopy.getModifierList();
237         assert list != null;
238         list.setModifierProperty(PsiModifier.STATIC, member.hasModifierProperty(PsiModifier.STATIC));
239         list.setModifierProperty(PsiModifier.FINAL, member.hasModifierProperty(PsiModifier.FINAL));
240         VisibilityUtil.setVisibility(list, VisibilityUtil.getVisibilityModifier(member.getModifierList()));
241       }
242     }
243     member.delete();
244     return anchor != null ? (PsiMember)targetClass.addAfter(memberCopy, anchor) : (PsiMember)targetClass.add(memberCopy);
245   }
246
247   @Override
248   public void decodeContextInfo(@NotNull PsiElement scope) {
249     ChangeContextUtil.decodeContextInfo(scope, null, null);
250   }
251
252   @Override
253   @Nullable
254   public PsiElement getAnchor(@NotNull final PsiMember member, @NotNull final PsiClass targetClass, final Set<PsiMember> membersToMove) {
255     if (member instanceof PsiField && member.hasModifierProperty(PsiModifier.STATIC)) {
256       final List<PsiField> afterFields = new ArrayList<PsiField>();
257       final PsiExpression psiExpression = ((PsiField)member).getInitializer();
258       if (psiExpression != null) {
259         psiExpression.accept(new JavaRecursiveElementWalkingVisitor() {
260           @Override
261           public void visitReferenceExpression(final PsiReferenceExpression expression) {
262             super.visitReferenceExpression(expression);
263             final PsiElement psiElement = expression.resolve();
264             if (psiElement instanceof PsiField) {
265               final PsiField psiField = (PsiField)psiElement;
266               if ((psiField.getContainingClass() == targetClass || membersToMove.contains(psiField))&& !afterFields.contains(psiField)) {
267                 afterFields.add(psiField);
268               }
269             }
270           }
271         });
272       }
273
274       if (!afterFields.isEmpty()) {
275         Collections.sort(afterFields, new Comparator<PsiField>() {
276           public int compare(final PsiField o1, final PsiField o2) {
277             return -PsiUtilCore.compareElementsByPosition(o1, o2);
278           }
279         });
280         return afterFields.get(0);
281       }
282
283       final List<PsiField> beforeFields = new ArrayList<PsiField>();
284       for (PsiReference psiReference : ReferencesSearch.search(member, new LocalSearchScope(targetClass))) {
285         final PsiField fieldWithReference = PsiTreeUtil.getParentOfType(psiReference.getElement(), PsiField.class);
286         if (fieldWithReference != null && !afterFields.contains(fieldWithReference) && fieldWithReference.getContainingClass() == targetClass) {
287           beforeFields.add(fieldWithReference);
288         }
289       }
290       Collections.sort(beforeFields, PsiUtil.BY_POSITION);
291       if (!beforeFields.isEmpty()) {
292         return beforeFields.get(0).getPrevSibling();
293       }
294     }
295     return null;
296   }
297 }