blob: aece13de076405cf7cc0fca01adf2feb11333cb6 [file] [log] [blame]
/*
* Copyright 2000-2013 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.util;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtil;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ModuleRootManager;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.impl.light.LightElement;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.PsiSearchScopeUtil;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.PsiUtilCore;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.usageView.UsageInfo;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.VisibilityUtil;
import com.intellij.util.containers.HashSet;
import com.intellij.util.containers.MultiMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.Set;
/**
* @author anna
* Date: 05-Oct-2009
*/
public class RefactoringConflictsUtil {
private RefactoringConflictsUtil() { }
public static void analyzeAccessibilityConflicts(@NotNull Set<PsiMember> membersToMove,
@NotNull PsiClass targetClass,
@NotNull MultiMap<PsiElement, String> conflicts,
@Nullable String newVisibility) {
analyzeAccessibilityConflicts(membersToMove, targetClass, conflicts, newVisibility, targetClass, null);
}
public static void analyzeAccessibilityConflicts(@NotNull Set<PsiMember> membersToMove,
@Nullable PsiClass targetClass,
@NotNull MultiMap<PsiElement, String> conflicts,
@Nullable String newVisibility,
@NotNull PsiElement context,
@Nullable Set<PsiMethod> abstractMethods) {
if (VisibilityUtil.ESCALATE_VISIBILITY.equals(newVisibility)) { //Still need to check for access object
newVisibility = PsiModifier.PUBLIC;
}
for (PsiMember member : membersToMove) {
checkUsedElements(member, member, membersToMove, abstractMethods, targetClass, context, conflicts);
checkAccessibilityConflicts(member, newVisibility, targetClass, membersToMove, conflicts);
}
}
public static void checkAccessibilityConflicts(@NotNull PsiMember member,
@PsiModifier.ModifierConstant @Nullable String newVisibility,
@Nullable PsiClass targetClass,
@NotNull Set<? extends PsiMember> membersToMove,
@NotNull MultiMap<PsiElement, String> conflicts) {
PsiModifierList modifierListCopy = member.getModifierList();
if (modifierListCopy != null) {
modifierListCopy = (PsiModifierList)modifierListCopy.copy();
final PsiClass containingClass = member.getContainingClass();
if (containingClass != null && containingClass.isInterface()) {
VisibilityUtil.setVisibility(modifierListCopy, PsiModifier.PUBLIC);
}
}
if (newVisibility != null && modifierListCopy != null) {
try {
VisibilityUtil.setVisibility(modifierListCopy, newVisibility);
}
catch (IncorrectOperationException ignore) { } // do nothing and hope for the best
}
checkAccessibilityConflicts(member, modifierListCopy, targetClass, membersToMove, conflicts);
}
public static void checkAccessibilityConflicts(@NotNull PsiMember member,
@Nullable PsiModifierList modifierListCopy,
@Nullable PsiClass targetClass,
@NotNull Set<? extends PsiMember> membersToMove,
@NotNull MultiMap<PsiElement, String> conflicts) {
for (PsiReference psiReference : ReferencesSearch.search(member)) {
checkAccessibilityConflicts(psiReference, member, modifierListCopy, targetClass, membersToMove, conflicts);
}
}
public static void checkAccessibilityConflicts(@NotNull PsiReference reference,
@NotNull PsiMember member,
@Nullable PsiModifierList modifierListCopy,
@Nullable PsiClass targetClass,
@NotNull Set<? extends PsiMember> membersToMove,
@NotNull MultiMap<PsiElement, String> conflicts) {
JavaPsiFacade manager = JavaPsiFacade.getInstance(member.getProject());
PsiElement ref = reference.getElement();
if (!RefactoringHierarchyUtil.willBeInTargetClass(ref, membersToMove, targetClass, false)) {
// check for target class accessibility
if (targetClass != null && !manager.getResolveHelper().isAccessible(targetClass, targetClass.getModifierList(), ref, null, null)) {
String message = RefactoringBundle.message("0.is.1.and.will.not.be.accessible.from.2.in.the.target.class",
RefactoringUIUtil.getDescription(targetClass, true),
VisibilityUtil.getVisibilityStringToDisplay(targetClass),
RefactoringUIUtil.getDescription(ConflictsUtil.getContainer(ref), true));
message = CommonRefactoringUtil.capitalize(message);
conflicts.putValue(targetClass, message);
}
// check for member accessibility
else if (!manager.getResolveHelper().isAccessible(member, modifierListCopy, ref, targetClass, null)) {
String message = RefactoringBundle.message("0.is.1.and.will.not.be.accessible.from.2.in.the.target.class",
RefactoringUIUtil.getDescription(member, true),
VisibilityUtil.toPresentableText(VisibilityUtil.getVisibilityModifier(modifierListCopy)),
RefactoringUIUtil.getDescription(ConflictsUtil.getContainer(ref), true));
message = CommonRefactoringUtil.capitalize(message);
conflicts.putValue(member, message);
}
}
}
public static void checkUsedElements(PsiMember member,
PsiElement scope,
@NotNull Set<PsiMember> membersToMove,
@Nullable Set<PsiMethod> abstractMethods,
@Nullable PsiClass targetClass,
@NotNull PsiElement context,
MultiMap<PsiElement, String> conflicts) {
final Set<PsiMember> moving = new HashSet<PsiMember>(membersToMove);
if (abstractMethods != null) {
moving.addAll(abstractMethods);
}
if (scope instanceof PsiReferenceExpression) {
PsiReferenceExpression refExpr = (PsiReferenceExpression)scope;
PsiElement refElement = refExpr.resolve();
if (refElement instanceof PsiMember) {
PsiExpression qualifier = refExpr.getQualifierExpression();
PsiClass accessClass = (PsiClass)(qualifier != null ? PsiUtil.getAccessObjectClass(qualifier).getElement() : null);
if (!RefactoringHierarchyUtil.willBeInTargetClass(refElement, moving, targetClass, false) &&
(accessClass == null || !RefactoringHierarchyUtil.willBeInTargetClass(accessClass, moving, targetClass, false))) {
checkAccessibility((PsiMember)refElement, context, accessClass, member, conflicts);
}
}
}
else if (scope instanceof PsiNewExpression) {
final PsiNewExpression newExpression = (PsiNewExpression)scope;
final PsiAnonymousClass anonymousClass = newExpression.getAnonymousClass();
if (anonymousClass != null) {
if (!RefactoringHierarchyUtil.willBeInTargetClass(anonymousClass, moving, targetClass, false)) {
checkAccessibility(anonymousClass, context, anonymousClass, member, conflicts);
}
}
else {
final PsiMethod refElement = newExpression.resolveConstructor();
if (refElement != null) {
if (!RefactoringHierarchyUtil.willBeInTargetClass(refElement, moving, targetClass, false)) {
checkAccessibility(refElement, context, null, member, conflicts);
}
}
}
}
else if (scope instanceof PsiJavaCodeReferenceElement) {
PsiJavaCodeReferenceElement refExpr = (PsiJavaCodeReferenceElement)scope;
PsiElement refElement = refExpr.resolve();
if (refElement instanceof PsiMember) {
if (!RefactoringHierarchyUtil.willBeInTargetClass(refElement, moving, targetClass, false)) {
checkAccessibility((PsiMember)refElement, context, null, member, conflicts);
}
}
}
for (PsiElement child : scope.getChildren()) {
if (child instanceof PsiWhiteSpace || child instanceof PsiComment) continue;
checkUsedElements(member, child, membersToMove, abstractMethods, targetClass, context, conflicts);
}
}
public static void checkAccessibility(PsiMember refMember,
@NotNull PsiElement newContext,
@Nullable PsiClass accessClass,
PsiMember member,
MultiMap<PsiElement, String> conflicts) {
if (!PsiUtil.isAccessible(refMember, newContext, accessClass)) {
String message = RefactoringBundle.message("0.is.1.and.will.not.be.accessible.from.2.in.the.target.class",
RefactoringUIUtil.getDescription(refMember, true),
VisibilityUtil.getVisibilityStringToDisplay(refMember),
RefactoringUIUtil.getDescription(member, false));
message = CommonRefactoringUtil.capitalize(message);
conflicts.putValue(refMember, message);
}
else if (newContext instanceof PsiClass && refMember instanceof PsiField && refMember.getContainingClass() == member.getContainingClass()) {
final PsiField fieldInSubClass = ((PsiClass)newContext).findFieldByName(refMember.getName(), false);
if (fieldInSubClass != null && fieldInSubClass != refMember) {
conflicts.putValue(refMember, CommonRefactoringUtil.capitalize(RefactoringUIUtil.getDescription(fieldInSubClass, true) +
" would hide " + RefactoringUIUtil.getDescription(refMember, true) +
" which is used by moved " + RefactoringUIUtil.getDescription(member, false)));
}
}
}
public static void analyzeModuleConflicts(final Project project,
final Collection<? extends PsiElement> scopes,
final UsageInfo[] usages,
final PsiElement target,
final MultiMap<PsiElement,String> conflicts) {
if (scopes == null) return;
final VirtualFile vFile = PsiUtilCore.getVirtualFile(target);
if (vFile == null) return;
analyzeModuleConflicts(project, scopes, usages, vFile, conflicts);
}
public static void analyzeModuleConflicts(final Project project,
final Collection<? extends PsiElement> scopes,
final UsageInfo[] usages,
final VirtualFile vFile,
final MultiMap<PsiElement, String> conflicts) {
if (scopes == null) return;
for (final PsiElement scope : scopes) {
if (scope instanceof PsiPackage) return;
}
final Module targetModule = ModuleUtil.findModuleForFile(vFile, project);
if (targetModule == null) return;
final GlobalSearchScope resolveScope = GlobalSearchScope.moduleWithDependenciesAndLibrariesScope(targetModule);
final HashSet<PsiElement> reported = new HashSet<PsiElement>();
for (final PsiElement scope : scopes) {
scope.accept(new JavaRecursiveElementVisitor() {
@Override public void visitReferenceElement(PsiJavaCodeReferenceElement reference) {
super.visitReferenceElement(reference);
final PsiElement resolved = reference.resolve();
if (resolved != null &&
!reported.contains(resolved) &&
!CommonRefactoringUtil.isAncestor(resolved, scopes) &&
!PsiSearchScopeUtil.isInScope(resolveScope, resolved) &&
!(resolved instanceof LightElement)) {
final String scopeDescription = RefactoringUIUtil.getDescription(ConflictsUtil.getContainer(reference), true);
final String message = RefactoringBundle.message("0.referenced.in.1.will.not.be.accessible.in.module.2",
RefactoringUIUtil.getDescription(resolved, true),
scopeDescription,
CommonRefactoringUtil.htmlEmphasize(targetModule.getName()));
conflicts.putValue(resolved, CommonRefactoringUtil.capitalize(message));
reported.add(resolved);
}
}
});
}
boolean isInTestSources = ModuleRootManager.getInstance(targetModule).getFileIndex().isInTestSourceContent(vFile);
NextUsage:
for (UsageInfo usage : usages) {
final PsiElement element = usage.getElement();
if (element != null && PsiTreeUtil.getParentOfType(element, PsiImportStatement.class, false) == null) {
for (PsiElement scope : scopes) {
if (PsiTreeUtil.isAncestor(scope, element, false)) continue NextUsage;
}
final GlobalSearchScope resolveScope1 = element.getResolveScope();
if (!resolveScope1.isSearchInModuleContent(targetModule, isInTestSources)) {
final PsiFile usageFile = element.getContainingFile();
PsiElement container;
if (usageFile instanceof PsiJavaFile) {
container = ConflictsUtil.getContainer(element);
}
else {
container = usageFile;
}
final String scopeDescription = RefactoringUIUtil.getDescription(container, true);
final VirtualFile usageVFile = usageFile.getVirtualFile();
if (usageVFile != null) {
Module module = ProjectRootManager.getInstance(project).getFileIndex().getModuleForFile(usageVFile);
if (module != null) {
final String message;
final PsiElement referencedElement;
if (usage instanceof MoveRenameUsageInfo) {
referencedElement = ((MoveRenameUsageInfo)usage).getReferencedElement();
}
else {
referencedElement = usage.getElement();
}
assert referencedElement != null : usage;
if (module == targetModule && isInTestSources) {
message = RefactoringBundle.message("0.referenced.in.1.will.not.be.accessible.from.production.of.module.2",
RefactoringUIUtil.getDescription(referencedElement, true),
scopeDescription,
CommonRefactoringUtil.htmlEmphasize(module.getName()));
}
else {
message = RefactoringBundle.message("0.referenced.in.1.will.not.be.accessible.from.module.2",
RefactoringUIUtil.getDescription(referencedElement, true),
scopeDescription,
CommonRefactoringUtil.htmlEmphasize(module.getName()));
}
conflicts.putValue(referencedElement, CommonRefactoringUtil.capitalize(message));
}
}
}
}
}
}
}