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.moveMembers;
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;
38 * @author Maxim.Medvedev
40 public class MoveJavaMemberHandler implements MoveMemberHandler {
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
59 if (qualifier instanceof PsiReferenceExpression &&
60 ((PsiReferenceExpression)qualifier).isReferenceTo(member.getContainingClass())) {
61 return new MoveMembersProcessor.MoveMembersUsageInfo(member, refExpr, targetClass, qualifier, psiReference); // change qualifier
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
71 return new MoveMembersProcessor.MoveMembersUsageInfo(member, refExpr, targetClass, qualifier, psiReference); // change qualifier
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;
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();
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),
101 RefactoringUIUtil.getDescription(ConflictsUtil.getContainer(element), true));
102 conflicts.putValue(member, CommonRefactoringUtil.capitalize(message));
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));
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.");
123 final PsiReference reference = usageInfo.getReference();
124 if (reference != null) {
125 RefactoringConflictsUtil.checkAccessibilityConflicts(reference, member, modifierListCopy, targetClass, membersToMove, conflicts);
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));
142 RefactoringConflictsUtil.checkUsedElements(member, member, membersToMove, null, targetClass, targetClass, conflicts);
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))) {
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())) {
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;
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);
182 final PsiReferenceParameterList parameterList = refExpr.getParameterList();
183 if (parameterList != null && parameterList.getTypeArguments().length == 0 && !(refExpr instanceof PsiMethodReferenceExpression)){
184 refExpr.setQualifierExpression(null);
186 final Project project = element.getProject();
187 final PsiClass targetClass =
188 JavaPsiFacade.getInstance(project).findClass(options.getTargetClassName(), GlobalSearchScope.projectScope(project));
189 if (targetClass != null) {
190 changeQualifier(refExpr, targetClass, usage.member);
195 else { // no qualifier
196 if (usage.qualifierClass != null && (!usage.qualifierClass.isEnum() || PsiTreeUtil.getParentOfType(refExpr, PsiSwitchLabelStatement.class) == null)) {
197 changeQualifier(refExpr, usage.qualifierClass, usage.member);
205 protected static void changeQualifier(PsiReferenceExpression refExpr, PsiClass aClass, PsiMember member) throws IncorrectOperationException {
206 if (RefactoringUtil.hasOnDemandStaticImport(refExpr, aClass) && !(refExpr instanceof PsiMethodReferenceExpression)) {
207 refExpr.setQualifierExpression(null);
209 else if (!ImportsUtil.hasStaticImportOn(refExpr, member, false)){
210 PsiElementFactory factory = JavaPsiFacade.getInstance(refExpr.getProject()).getElementFactory();
211 refExpr.setQualifierExpression(factory.createReferenceExpression(aClass));
217 public PsiMember doMove(@NotNull MoveMembersOptions options, @NotNull PsiMember member, PsiElement anchor, @NotNull PsiClass targetClass) {
218 if (member instanceof PsiVariable) {
219 ((PsiVariable)member).normalizeDeclaration();
222 ChangeContextUtil.encodeContextInfo(member, true);
224 final PsiMember memberCopy;
225 if (options.makeEnumConstant() &&
226 member instanceof PsiVariable &&
227 EnumConstantsUtil.isSuitableForEnumConstant(((PsiVariable)member).getType(), targetClass)) {
228 memberCopy = EnumConstantsUtil.createEnumConstant(targetClass, member.getName(), ((PsiVariable)member).getInitializer());
231 memberCopy = (PsiMember)member.copy();
232 final PsiClass containingClass = member.getContainingClass();
233 if (containingClass != null && containingClass.isInterface() && !targetClass.isInterface()) {
234 // might need to make modifiers explicit, see IDEADEV-11416
235 final PsiModifierList list = memberCopy.getModifierList();
237 list.setModifierProperty(PsiModifier.STATIC, member.hasModifierProperty(PsiModifier.STATIC));
238 list.setModifierProperty(PsiModifier.FINAL, member.hasModifierProperty(PsiModifier.FINAL));
239 VisibilityUtil.setVisibility(list, VisibilityUtil.getVisibilityModifier(member.getModifierList()));
243 return anchor != null ? (PsiMember)targetClass.addAfter(memberCopy, anchor) : (PsiMember)targetClass.add(memberCopy);
247 public void decodeContextInfo(@NotNull PsiElement scope) {
248 ChangeContextUtil.decodeContextInfo(scope, null, null);
253 public PsiElement getAnchor(@NotNull final PsiMember member, @NotNull final PsiClass targetClass, final Set<PsiMember> membersToMove) {
254 if (member instanceof PsiField && member.hasModifierProperty(PsiModifier.STATIC)) {
255 final List<PsiField> afterFields = new ArrayList<PsiField>();
256 final PsiExpression psiExpression = ((PsiField)member).getInitializer();
257 if (psiExpression != null) {
258 psiExpression.accept(new JavaRecursiveElementWalkingVisitor() {
260 public void visitReferenceExpression(final PsiReferenceExpression expression) {
261 super.visitReferenceExpression(expression);
262 final PsiElement psiElement = expression.resolve();
263 if (psiElement instanceof PsiField) {
264 final PsiField psiField = (PsiField)psiElement;
265 if ((psiField.getContainingClass() == targetClass || membersToMove.contains(psiField))&& !afterFields.contains(psiField)) {
266 afterFields.add(psiField);
273 if (!afterFields.isEmpty()) {
274 Collections.sort(afterFields, new Comparator<PsiField>() {
275 public int compare(final PsiField o1, final PsiField o2) {
276 return -PsiUtilCore.compareElementsByPosition(o1, o2);
279 return afterFields.get(0);
282 final List<PsiField> beforeFields = new ArrayList<PsiField>();
283 for (PsiReference psiReference : ReferencesSearch.search(member, new LocalSearchScope(targetClass))) {
284 final PsiField fieldWithReference = PsiTreeUtil.getParentOfType(psiReference.getElement(), PsiField.class);
285 if (fieldWithReference != null && !afterFields.contains(fieldWithReference) && fieldWithReference.getContainingClass() == targetClass) {
286 beforeFields.add(fieldWithReference);
289 Collections.sort(beforeFields, PsiUtil.BY_POSITION);
290 if (!beforeFields.isEmpty()) {
291 return beforeFields.get(0).getPrevSibling();