blob: 5bd1c20a0b1e2e69244e578b7c3bd6fcf66cc37e [file] [log] [blame]
/*
* Copyright 2000-2009 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.refactoring.move.moveMembers;
import com.intellij.codeInsight.ChangeContextUtil;
import com.intellij.codeInsight.highlighting.ReadWriteAccessDetector;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.resolve.JavaResolveUtil;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.LocalSearchScope;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.psi.util.*;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.util.*;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.VisibilityUtil;
import com.intellij.util.containers.MultiMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
* @author Maxim.Medvedev
*/
public class MoveJavaMemberHandler implements MoveMemberHandler {
@Override
@Nullable
public MoveMembersProcessor.MoveMembersUsageInfo getUsage(@NotNull PsiMember member,
@NotNull PsiReference psiReference,
@NotNull Set<PsiMember> membersToMove,
@NotNull PsiClass targetClass) {
PsiElement ref = psiReference.getElement();
if (ref instanceof PsiReferenceExpression) {
PsiReferenceExpression refExpr = (PsiReferenceExpression)ref;
PsiExpression qualifier = refExpr.getQualifierExpression();
if (RefactoringHierarchyUtil.willBeInTargetClass(refExpr, membersToMove, targetClass, true)) {
// both member and the reference to it will be in target class
if (!RefactoringUtil.isInMovedElement(refExpr, membersToMove)) {
if (qualifier != null) {
return new MoveMembersProcessor.MoveMembersUsageInfo(member, refExpr, null, qualifier, psiReference); // remove qualifier
}
}
else {
if (qualifier instanceof PsiReferenceExpression &&
((PsiReferenceExpression)qualifier).isReferenceTo(member.getContainingClass())) {
return new MoveMembersProcessor.MoveMembersUsageInfo(member, refExpr, targetClass, qualifier, psiReference); // change qualifier
}
}
}
else {
// member in target class, the reference will be outside target class
if (qualifier == null) {
return new MoveMembersProcessor.MoveMembersUsageInfo(member, refExpr, targetClass, refExpr, psiReference); // add qualifier
}
else {
return new MoveMembersProcessor.MoveMembersUsageInfo(member, refExpr, targetClass, qualifier, psiReference); // change qualifier
}
}
}
return null;
}
@Override
public void checkConflictsOnUsage(@NotNull MoveMembersProcessor.MoveMembersUsageInfo usageInfo,
@Nullable String newVisibility,
@Nullable PsiModifierList modifierListCopy,
@NotNull PsiClass targetClass,
@NotNull Set<PsiMember> membersToMove,
@NotNull MultiMap<PsiElement, String> conflicts) {
final PsiElement element = usageInfo.getElement();
if (element == null) return;
final PsiMember member = usageInfo.member;
if (element instanceof PsiReferenceExpression) {
PsiExpression qualifier = ((PsiReferenceExpression)element).getQualifierExpression();
PsiClass accessObjectClass = null;
if (qualifier != null) {
accessObjectClass = (PsiClass)PsiUtil.getAccessObjectClass(qualifier).getElement();
}
if (!JavaResolveUtil.isAccessible(member, targetClass, modifierListCopy, element, accessObjectClass, null)) {
String visibility = newVisibility != null ? newVisibility : VisibilityUtil.getVisibilityStringToDisplay(member);
String message = RefactoringBundle.message("0.with.1.visibility.is.not.accessible.from.2",
RefactoringUIUtil.getDescription(member, false),
visibility,
RefactoringUIUtil.getDescription(ConflictsUtil.getContainer(element), true));
conflicts.putValue(member, CommonRefactoringUtil.capitalize(message));
}
}
if (member instanceof PsiField && targetClass.isInterface()) {
ReadWriteAccessDetector accessDetector = ReadWriteAccessDetector.findDetector(member);
if (accessDetector != null) {
ReadWriteAccessDetector.Access access = accessDetector.getExpressionAccess(element);
if (access != ReadWriteAccessDetector.Access.Read) {
String message = RefactoringUIUtil.getDescription(member, true) + " has write access but is moved to an interface";
conflicts.putValue(element, CommonRefactoringUtil.capitalize(message));
}
}
} else if (member instanceof PsiField &&
usageInfo.reference instanceof PsiExpression &&
member.hasModifierProperty(PsiModifier.FINAL) &&
PsiUtil.isAccessedForWriting((PsiExpression)usageInfo.reference) &&
!RefactoringHierarchyUtil.willBeInTargetClass(usageInfo.reference, membersToMove, targetClass, true)) {
conflicts.putValue(usageInfo.member, "final variable initializer won't be available after move.");
}
final PsiReference reference = usageInfo.getReference();
if (reference != null) {
RefactoringConflictsUtil.checkAccessibilityConflicts(reference, member, modifierListCopy, targetClass, membersToMove, conflicts);
}
}
@Override
public void checkConflictsOnMember(@NotNull PsiMember member,
@Nullable String newVisibility,
@Nullable PsiModifierList modifierListCopy,
@NotNull PsiClass targetClass,
@NotNull Set<PsiMember> membersToMove,
@NotNull MultiMap<PsiElement, String> conflicts) {
if (member instanceof PsiMethod && hasMethod(targetClass, (PsiMethod)member) ||
member instanceof PsiField && hasField(targetClass, (PsiField)member)) {
String message = RefactoringBundle.message("0.already.exists.in.the.target.class", RefactoringUIUtil.getDescription(member, false));
conflicts.putValue(member, CommonRefactoringUtil.capitalize(message));
}
RefactoringConflictsUtil.checkUsedElements(member, member, membersToMove, null, targetClass, targetClass, conflicts);
}
protected static boolean hasMethod(PsiClass targetClass, PsiMethod method) {
PsiMethod[] targetClassMethods = targetClass.getMethods();
for (PsiMethod candidate : targetClassMethods) {
if (candidate != method &&
MethodSignatureUtil.areSignaturesEqual(method.getSignature(PsiSubstitutor.EMPTY),
candidate.getSignature(PsiSubstitutor.EMPTY))) {
return true;
}
}
return false;
}
protected static boolean hasField(PsiClass targetClass, PsiField field) {
String fieldName = field.getName();
PsiField[] targetClassFields = targetClass.getFields();
for (PsiField candidate : targetClassFields) {
if (candidate != field &&
fieldName.equals(candidate.getName())) {
return true;
}
}
return false;
}
@Override
public boolean changeExternalUsage(@NotNull MoveMembersOptions options, @NotNull MoveMembersProcessor.MoveMembersUsageInfo usage) {
final PsiElement element = usage.getElement();
if (element == null || !element.isValid()) return true;
if (usage.reference instanceof PsiReferenceExpression) {
PsiReferenceExpression refExpr = (PsiReferenceExpression)usage.reference;
PsiExpression qualifier = refExpr.getQualifierExpression();
if (qualifier != null) {
if (usage.qualifierClass != null && PsiTreeUtil.getParentOfType(refExpr, PsiSwitchLabelStatement.class) == null) {
changeQualifier(refExpr, usage.qualifierClass, usage.member);
}
else {
final PsiReferenceParameterList parameterList = refExpr.getParameterList();
if (parameterList != null && parameterList.getTypeArguments().length == 0 && !(refExpr instanceof PsiMethodReferenceExpression)){
refExpr.setQualifierExpression(null);
} else {
final Project project = element.getProject();
final PsiClass targetClass =
JavaPsiFacade.getInstance(project).findClass(options.getTargetClassName(), GlobalSearchScope.projectScope(project));
if (targetClass != null) {
changeQualifier(refExpr, targetClass, usage.member);
}
}
}
}
else { // no qualifier
if (usage.qualifierClass != null && (!usage.qualifierClass.isEnum() || PsiTreeUtil.getParentOfType(refExpr, PsiSwitchLabelStatement.class) == null)) {
changeQualifier(refExpr, usage.qualifierClass, usage.member);
}
}
return true;
}
return false;
}
protected static void changeQualifier(PsiReferenceExpression refExpr, PsiClass aClass, PsiMember member) throws IncorrectOperationException {
if (RefactoringUtil.hasOnDemandStaticImport(refExpr, aClass)) {
refExpr.setQualifierExpression(null);
}
else if (!RefactoringUtil.hasStaticImportOn(refExpr, member)){
PsiElementFactory factory = JavaPsiFacade.getInstance(refExpr.getProject()).getElementFactory();
refExpr.setQualifierExpression(factory.createReferenceExpression(aClass));
}
}
@Override
@NotNull
public PsiMember doMove(@NotNull MoveMembersOptions options, @NotNull PsiMember member, PsiElement anchor, @NotNull PsiClass targetClass) {
if (member instanceof PsiVariable) {
((PsiVariable)member).normalizeDeclaration();
}
ChangeContextUtil.encodeContextInfo(member, true);
final PsiMember memberCopy;
if (options.makeEnumConstant() &&
member instanceof PsiVariable &&
EnumConstantsUtil.isSuitableForEnumConstant(((PsiVariable)member).getType(), targetClass)) {
memberCopy = EnumConstantsUtil.createEnumConstant(targetClass, member.getName(), ((PsiVariable)member).getInitializer());
}
else {
memberCopy = (PsiMember)member.copy();
final PsiClass containingClass = member.getContainingClass();
if (containingClass != null && containingClass.isInterface() && !targetClass.isInterface()) {
// might need to make modifiers explicit, see IDEADEV-11416
final PsiModifierList list = memberCopy.getModifierList();
assert list != null;
list.setModifierProperty(PsiModifier.STATIC, member.hasModifierProperty(PsiModifier.STATIC));
list.setModifierProperty(PsiModifier.FINAL, member.hasModifierProperty(PsiModifier.FINAL));
VisibilityUtil.setVisibility(list, VisibilityUtil.getVisibilityModifier(member.getModifierList()));
}
}
member.delete();
return anchor != null ? (PsiMember)targetClass.addAfter(memberCopy, anchor) : (PsiMember)targetClass.add(memberCopy);
}
@Override
public void decodeContextInfo(@NotNull PsiElement scope) {
ChangeContextUtil.decodeContextInfo(scope, null, null);
}
@Override
@Nullable
public PsiElement getAnchor(@NotNull final PsiMember member, @NotNull final PsiClass targetClass, final Set<PsiMember> membersToMove) {
if (member instanceof PsiField && member.hasModifierProperty(PsiModifier.STATIC)) {
final List<PsiField> afterFields = new ArrayList<PsiField>();
final PsiExpression psiExpression = ((PsiField)member).getInitializer();
if (psiExpression != null) {
psiExpression.accept(new JavaRecursiveElementWalkingVisitor() {
@Override
public void visitReferenceExpression(final PsiReferenceExpression expression) {
super.visitReferenceExpression(expression);
final PsiElement psiElement = expression.resolve();
if (psiElement instanceof PsiField) {
final PsiField psiField = (PsiField)psiElement;
if ((psiField.getContainingClass() == targetClass || membersToMove.contains(psiField))&& !afterFields.contains(psiField)) {
afterFields.add(psiField);
}
}
}
});
}
if (!afterFields.isEmpty()) {
Collections.sort(afterFields, new Comparator<PsiField>() {
public int compare(final PsiField o1, final PsiField o2) {
return -PsiUtilCore.compareElementsByPosition(o1, o2);
}
});
return afterFields.get(0);
}
final List<PsiField> beforeFields = new ArrayList<PsiField>();
for (PsiReference psiReference : ReferencesSearch.search(member, new LocalSearchScope(targetClass))) {
final PsiField fieldWithReference = PsiTreeUtil.getParentOfType(psiReference.getElement(), PsiField.class);
if (fieldWithReference != null && !afterFields.contains(fieldWithReference) && fieldWithReference.getContainingClass() == targetClass) {
beforeFields.add(fieldWithReference);
}
}
Collections.sort(beforeFields, PsiUtil.BY_POSITION);
if (!beforeFields.isEmpty()) {
return beforeFields.get(0).getPrevSibling();
}
}
return null;
}
}