| /* |
| * 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. |
| */ |
| |
| /* |
| * Created by IntelliJ IDEA. |
| * User: dsl |
| * Date: 17.06.2002 |
| * Time: 15:40:16 |
| * To change template for new class use |
| * Code Style | Class Templates options (Tools | IDE Options). |
| */ |
| package com.intellij.refactoring.memberPullUp; |
| |
| import com.intellij.codeInsight.AnnotationUtil; |
| import com.intellij.openapi.util.Comparing; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.psi.*; |
| import com.intellij.psi.search.searches.ClassInheritorsSearch; |
| import com.intellij.psi.search.searches.FunctionalExpressionSearch; |
| import com.intellij.psi.util.*; |
| import com.intellij.refactoring.RefactoringBundle; |
| import com.intellij.refactoring.classMembers.MemberInfoBase; |
| import com.intellij.refactoring.util.CommonRefactoringUtil; |
| import com.intellij.refactoring.util.RefactoringConflictsUtil; |
| import com.intellij.refactoring.util.RefactoringHierarchyUtil; |
| import com.intellij.refactoring.util.RefactoringUIUtil; |
| import com.intellij.refactoring.util.classMembers.ClassMemberReferencesVisitor; |
| import com.intellij.refactoring.util.classMembers.InterfaceContainmentVerifier; |
| import com.intellij.usageView.UsageInfo; |
| import com.intellij.util.VisibilityUtil; |
| import com.intellij.util.containers.ContainerUtil; |
| import com.intellij.util.containers.MultiMap; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.util.ArrayList; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Set; |
| |
| public class PullUpConflictsUtil { |
| private PullUpConflictsUtil() {} |
| |
| public static MultiMap<PsiElement, String> checkConflicts(MemberInfoBase<? extends PsiMember>[] infos, |
| PsiClass subclass, |
| @Nullable PsiClass superClass, |
| @NotNull PsiPackage targetPackage, |
| @NotNull PsiDirectory targetDirectory, |
| final InterfaceContainmentVerifier interfaceContainmentVerifier) { |
| return checkConflicts(infos, subclass, superClass, targetPackage, targetDirectory, interfaceContainmentVerifier, true); |
| } |
| |
| public static MultiMap<PsiElement, String> checkConflicts(final MemberInfoBase<? extends PsiMember>[] infos, |
| @NotNull final PsiClass subclass, |
| @Nullable PsiClass superClass, |
| @NotNull final PsiPackage targetPackage, |
| @NotNull PsiDirectory targetDirectory, |
| final InterfaceContainmentVerifier interfaceContainmentVerifier, |
| boolean movedMembers2Super) { |
| final Set<PsiMember> movedMembers = new HashSet<PsiMember>(); |
| final Set<PsiMethod> abstractMethods = new HashSet<PsiMethod>(); |
| final boolean isInterfaceTarget; |
| final PsiElement targetRepresentativeElement; |
| if (superClass != null) { |
| isInterfaceTarget = superClass.isInterface(); |
| targetRepresentativeElement = superClass; |
| } |
| else { |
| isInterfaceTarget = false; |
| targetRepresentativeElement = targetDirectory; |
| } |
| for (MemberInfoBase<? extends PsiMember> info : infos) { |
| PsiMember member = info.getMember(); |
| if (member instanceof PsiMethod) { |
| if (!info.isToAbstract() && !isInterfaceTarget) { |
| movedMembers.add(member); |
| } |
| else { |
| abstractMethods.add((PsiMethod)member); |
| } |
| } |
| else { |
| movedMembers.add(member); |
| } |
| } |
| final MultiMap<PsiElement, String> conflicts = new MultiMap<PsiElement, String>(); |
| final Set<PsiMethod> abstrMethods = new HashSet<PsiMethod>(abstractMethods); |
| if (superClass != null) { |
| for (PsiMethod method : subclass.getMethods()) { |
| if (!movedMembers.contains(method) && !method.hasModifierProperty(PsiModifier.PRIVATE)) { |
| if (method.findSuperMethods(superClass).length > 0) { |
| abstrMethods.add(method); |
| } |
| } |
| } |
| |
| if (newAbstractMethodInSuper(infos)) { |
| final PsiAnnotation annotation = AnnotationUtil.findAnnotation(superClass, CommonClassNames.JAVA_LANG_FUNCTIONAL_INTERFACE); |
| if (annotation != null) { |
| conflicts.putValue(annotation, RefactoringBundle.message("functional.interface.broken")); |
| } else { |
| final PsiFunctionalExpression functionalExpression = FunctionalExpressionSearch.search(superClass).findFirst(); |
| if (functionalExpression != null) { |
| conflicts.putValue(functionalExpression, RefactoringBundle.message("functional.interface.broken")); |
| } |
| } |
| } |
| } |
| RefactoringConflictsUtil.analyzeAccessibilityConflicts(movedMembers, superClass, conflicts, VisibilityUtil.ESCALATE_VISIBILITY, targetRepresentativeElement, abstrMethods); |
| if (superClass != null) { |
| if (movedMembers2Super) { |
| checkSuperclassMembers(superClass, infos, conflicts); |
| if (isInterfaceTarget) { |
| checkInterfaceTarget(infos, conflicts); |
| } |
| } else { |
| final String qualifiedName = superClass.getQualifiedName(); |
| assert qualifiedName != null; |
| if (superClass.hasModifierProperty(PsiModifier.PACKAGE_LOCAL)) { |
| if (!Comparing.strEqual(StringUtil.getPackageName(qualifiedName), targetPackage.getQualifiedName())) { |
| conflicts.putValue(superClass, RefactoringUIUtil.getDescription(superClass, true) + " won't be accessible from " +RefactoringUIUtil.getDescription(targetPackage, true)); |
| } |
| } |
| } |
| } |
| // check if moved methods use other members in the classes between Subclass and Superclass |
| List<PsiElement> checkModuleConflictsList = new ArrayList<PsiElement>(); |
| for (PsiMember member : movedMembers) { |
| if (member instanceof PsiMethod || member instanceof PsiClass && !(member instanceof PsiCompiledElement)) { |
| ClassMemberReferencesVisitor visitor = |
| movedMembers2Super? new ConflictingUsagesOfSubClassMembers(member, movedMembers, abstractMethods, subclass, superClass, |
| superClass != null ? null : targetPackage, conflicts, |
| interfaceContainmentVerifier) |
| : new ConflictingUsagesOfSuperClassMembers(member, subclass, targetPackage, movedMembers, conflicts); |
| member.accept(visitor); |
| } |
| ContainerUtil.addIfNotNull(checkModuleConflictsList, member); |
| } |
| for (final PsiMethod method : abstractMethods) { |
| ContainerUtil.addIfNotNull(checkModuleConflictsList, method.getParameterList()); |
| ContainerUtil.addIfNotNull(checkModuleConflictsList, method.getReturnTypeElement()); |
| ContainerUtil.addIfNotNull(checkModuleConflictsList, method.getTypeParameterList()); |
| } |
| RefactoringConflictsUtil.analyzeModuleConflicts(subclass.getProject(), checkModuleConflictsList, |
| new UsageInfo[0], targetRepresentativeElement, conflicts); |
| final String fqName = subclass.getQualifiedName(); |
| final String packageName; |
| if (fqName != null) { |
| packageName = StringUtil.getPackageName(fqName); |
| } else { |
| final PsiFile psiFile = PsiTreeUtil.getParentOfType(subclass, PsiFile.class); |
| if (psiFile instanceof PsiClassOwner) { |
| packageName = ((PsiClassOwner)psiFile).getPackageName(); |
| } else { |
| packageName = null; |
| } |
| } |
| final boolean toDifferentPackage = !Comparing.strEqual(targetPackage.getQualifiedName(), packageName); |
| for (final PsiMethod abstractMethod : abstractMethods) { |
| abstractMethod.accept(new ClassMemberReferencesVisitor(subclass) { |
| @Override |
| protected void visitClassMemberReferenceElement(PsiMember classMember, PsiJavaCodeReferenceElement classMemberReference) { |
| if (classMember != null && willBeMoved(classMember, movedMembers)) { |
| boolean isAccessible = false; |
| if (classMember.hasModifierProperty(PsiModifier.PRIVATE)) { |
| isAccessible = true; |
| } |
| else if (classMember.hasModifierProperty(PsiModifier.PACKAGE_LOCAL) && |
| toDifferentPackage) { |
| isAccessible = true; |
| } |
| if (isAccessible) { |
| String message = RefactoringUIUtil.getDescription(abstractMethod, false) + |
| " uses " + |
| RefactoringUIUtil.getDescription(classMember, true) + |
| " which won't be accessible from the subclass."; |
| message = CommonRefactoringUtil.capitalize(message); |
| conflicts.putValue(classMember, message); |
| } |
| } |
| } |
| }); |
| if (abstractMethod.hasModifierProperty(PsiModifier.PACKAGE_LOCAL) && toDifferentPackage) { |
| if (!isInterfaceTarget) { |
| String message = "Can't make " + RefactoringUIUtil.getDescription(abstractMethod, false) + |
| " abstract as it won't be accessible from the subclass."; |
| message = CommonRefactoringUtil.capitalize(message); |
| conflicts.putValue(abstractMethod, message); |
| } |
| } |
| } |
| return conflicts; |
| } |
| |
| private static boolean newAbstractMethodInSuper(MemberInfoBase<? extends PsiMember>[] infos) { |
| boolean toAbstract = false; |
| for (MemberInfoBase<? extends PsiMember> info : infos) { |
| if (info.isToAbstract()) { |
| toAbstract = true; |
| } |
| } |
| return toAbstract; |
| } |
| |
| private static void checkInterfaceTarget(MemberInfoBase<? extends PsiMember>[] infos, MultiMap<PsiElement, String> conflictsList) { |
| for (MemberInfoBase<? extends PsiMember> info : infos) { |
| PsiElement member = info.getMember(); |
| |
| if (member instanceof PsiField || member instanceof PsiClass) { |
| |
| if (!((PsiModifierListOwner)member).hasModifierProperty(PsiModifier.STATIC) |
| && !(member instanceof PsiClass && ((PsiClass)member).isInterface())) { |
| String message = |
| RefactoringBundle.message("0.is.not.static.it.cannot.be.moved.to.the.interface", RefactoringUIUtil.getDescription(member, false)); |
| message = CommonRefactoringUtil.capitalize(message); |
| conflictsList.putValue(member, message); |
| } |
| } |
| |
| if (member instanceof PsiField && ((PsiField)member).getInitializer() == null) { |
| String message = RefactoringBundle.message("0.is.not.initialized.in.declaration.such.fields.are.not.allowed.in.interfaces", |
| RefactoringUIUtil.getDescription(member, false)); |
| conflictsList.putValue(member, CommonRefactoringUtil.capitalize(message)); |
| } |
| } |
| } |
| |
| private static void checkSuperclassMembers(PsiClass superClass, |
| MemberInfoBase<? extends PsiMember>[] infos, |
| MultiMap<PsiElement, String> conflictsList) { |
| for (MemberInfoBase<? extends PsiMember> info : infos) { |
| PsiMember member = info.getMember(); |
| boolean isConflict = false; |
| if (member instanceof PsiField) { |
| String name = member.getName(); |
| |
| isConflict = superClass.findFieldByName(name, false) != null; |
| } |
| else if (member instanceof PsiMethod) { |
| PsiSubstitutor superSubstitutor = TypeConversionUtil.getSuperClassSubstitutor(superClass, member.getContainingClass(), PsiSubstitutor.EMPTY); |
| MethodSignature signature = ((PsiMethod) member).getSignature(superSubstitutor); |
| final PsiMethod superClassMethod = MethodSignatureUtil.findMethodBySignature(superClass, signature, false); |
| isConflict = superClassMethod != null; |
| } |
| |
| if (isConflict) { |
| String message = RefactoringBundle.message("0.already.contains.a.1", |
| RefactoringUIUtil.getDescription(superClass, false), |
| RefactoringUIUtil.getDescription(member, false)); |
| message = CommonRefactoringUtil.capitalize(message); |
| conflictsList.putValue(superClass, message); |
| } |
| |
| if (member instanceof PsiMethod) { |
| final PsiMethod method = (PsiMethod)member; |
| final PsiModifierList modifierList = method.getModifierList(); |
| if (!modifierList.hasModifierProperty(PsiModifier.PRIVATE)) { |
| for (PsiClass subClass : ClassInheritorsSearch.search(superClass)) { |
| if (method.getContainingClass() != subClass) { |
| MethodSignature signature = ((PsiMethod) member).getSignature(TypeConversionUtil.getSuperClassSubstitutor(superClass, subClass, PsiSubstitutor.EMPTY)); |
| final PsiMethod wouldBeOverriden = MethodSignatureUtil.findMethodBySignature(subClass, signature, false); |
| if (wouldBeOverriden != null && VisibilityUtil.compare(VisibilityUtil.getVisibilityModifier(wouldBeOverriden.getModifierList()), |
| VisibilityUtil.getVisibilityModifier(modifierList)) > 0) { |
| conflictsList.putValue(wouldBeOverriden, CommonRefactoringUtil.capitalize(RefactoringUIUtil.getDescription(method, true) + " in super class would clash with local method from " + RefactoringUIUtil.getDescription(subClass, true))); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| } |
| |
| private static boolean willBeMoved(PsiElement element, Set<PsiMember> movedMembers) { |
| PsiElement parent = element; |
| while (parent != null) { |
| if (movedMembers.contains(parent)) return true; |
| parent = parent.getParent(); |
| } |
| return false; |
| } |
| |
| private static class ConflictingUsagesOfSuperClassMembers extends ClassMemberReferencesVisitor { |
| |
| private PsiMember myMember; |
| private PsiClass mySubClass; |
| private PsiPackage myTargetPackage; |
| private Set<PsiMember> myMovedMembers; |
| private MultiMap<PsiElement, String> myConflicts; |
| |
| public ConflictingUsagesOfSuperClassMembers(PsiMember member, PsiClass aClass, |
| PsiPackage targetPackage, |
| Set<PsiMember> movedMembers, |
| MultiMap<PsiElement, String> conflicts) { |
| super(aClass); |
| myMember = member; |
| mySubClass = aClass; |
| myTargetPackage = targetPackage; |
| myMovedMembers = movedMembers; |
| myConflicts = conflicts; |
| } |
| |
| @Override |
| protected void visitClassMemberReferenceElement(PsiMember classMember, PsiJavaCodeReferenceElement classMemberReference) { |
| if (classMember != null && !willBeMoved(classMember, myMovedMembers)) { |
| final PsiClass containingClass = classMember.getContainingClass(); |
| if (containingClass != null) { |
| if (!PsiUtil.isAccessibleFromPackage(classMember, myTargetPackage)) { |
| if (classMember.hasModifierProperty(PsiModifier.PACKAGE_LOCAL)) { |
| myConflicts.putValue(myMember, RefactoringUIUtil.getDescription(classMember, true) + " won't be accessible"); |
| } |
| else if (classMember.hasModifierProperty(PsiModifier.PROTECTED) && !mySubClass.isInheritor(containingClass, true)) { |
| myConflicts.putValue(myMember, RefactoringUIUtil.getDescription(classMember, true) + " won't be accessible"); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| private static class ConflictingUsagesOfSubClassMembers extends ClassMemberReferencesVisitor { |
| private final PsiElement myScope; |
| private final Set<PsiMember> myMovedMembers; |
| private final Set<PsiMethod> myAbstractMethods; |
| private final PsiClass mySubclass; |
| private final PsiClass mySuperClass; |
| private final PsiPackage myTargetPackage; |
| private final MultiMap<PsiElement, String> myConflictsList; |
| private final InterfaceContainmentVerifier myInterfaceContainmentVerifier; |
| |
| ConflictingUsagesOfSubClassMembers(PsiElement scope, |
| Set<PsiMember> movedMembers, Set<PsiMethod> abstractMethods, |
| PsiClass subclass, PsiClass superClass, |
| PsiPackage targetPackage, MultiMap<PsiElement, String> conflictsList, |
| InterfaceContainmentVerifier interfaceContainmentVerifier) { |
| super(subclass); |
| myScope = scope; |
| myMovedMembers = movedMembers; |
| myAbstractMethods = abstractMethods; |
| mySubclass = subclass; |
| mySuperClass = superClass; |
| myTargetPackage = targetPackage; |
| myConflictsList = conflictsList; |
| myInterfaceContainmentVerifier = interfaceContainmentVerifier; |
| } |
| |
| protected void visitClassMemberReferenceElement(PsiMember classMember, |
| PsiJavaCodeReferenceElement classMemberReference) { |
| if (classMember != null |
| && RefactoringHierarchyUtil.isMemberBetween(mySuperClass, mySubclass, classMember)) { |
| if (classMember.hasModifierProperty(PsiModifier.STATIC) |
| && !willBeMoved(classMember, myMovedMembers)) { |
| final boolean isAccessible; |
| if (mySuperClass != null) { |
| isAccessible = PsiUtil.isAccessible(classMember, mySuperClass, null); |
| } |
| else if (myTargetPackage != null) { |
| isAccessible = PsiUtil.isAccessibleFromPackage(classMember, myTargetPackage); |
| } |
| else { |
| isAccessible = classMember.hasModifierProperty(PsiModifier.PUBLIC); |
| } |
| if (!isAccessible) { |
| String message = RefactoringBundle.message("0.uses.1.which.is.not.accessible.from.the.superclass", |
| RefactoringUIUtil.getDescription(myScope, false), |
| RefactoringUIUtil.getDescription(classMember, true)); |
| message = CommonRefactoringUtil.capitalize(message); |
| myConflictsList.putValue(classMember, message); |
| |
| } |
| return; |
| } |
| if (!myAbstractMethods.contains(classMember) && !willBeMoved(classMember, myMovedMembers)) { |
| if (!existsInSuperClass(classMember)) { |
| String message = RefactoringBundle.message("0.uses.1.which.is.not.moved.to.the.superclass", |
| RefactoringUIUtil.getDescription(myScope, false), |
| RefactoringUIUtil.getDescription(classMember, true)); |
| message = CommonRefactoringUtil.capitalize(message); |
| myConflictsList.putValue(classMember, message); |
| } |
| } |
| } |
| } |
| |
| |
| |
| private boolean existsInSuperClass(PsiElement classMember) { |
| if (!(classMember instanceof PsiMethod)) return false; |
| final PsiMethod method = ((PsiMethod)classMember); |
| if (myInterfaceContainmentVerifier.checkedInterfacesContain(method)) return true; |
| if (mySuperClass == null) return false; |
| final PsiMethod methodBySignature = mySuperClass.findMethodBySignature(method, true); |
| return methodBySignature != null; |
| } |
| } |
| |
| |
| } |