diff --git a/java/java-impl/src/com/intellij/refactoring/inlineSuperClass/InlineSuperClassRefactoringProcessor.java b/java/java-impl/src/com/intellij/refactoring/inlineSuperClass/InlineSuperClassRefactoringProcessor.java index 96071b0b5e2c..ad9f122daca0 100644 --- a/java/java-impl/src/com/intellij/refactoring/inlineSuperClass/InlineSuperClassRefactoringProcessor.java +++ b/java/java-impl/src/com/intellij/refactoring/inlineSuperClass/InlineSuperClassRefactoringProcessor.java @@ -213,7 +213,7 @@ public class InlineSuperClassRefactoringProcessor extends FixableUsagesRefactori @Override protected boolean preprocessUsages(@NotNull final Ref refUsages) { final MultiMap conflicts = new MultiMap(); - final PushDownConflicts pushDownConflicts = new PushDownConflicts(mySuperClass, myMemberInfos); + final PushDownConflicts pushDownConflicts = new PushDownConflicts(mySuperClass, myMemberInfos, conflicts); for (PsiClass targetClass : myTargetClasses) { for (MemberInfo info : myMemberInfos) { final PsiMember member = info.getMember(); @@ -221,10 +221,6 @@ public class InlineSuperClassRefactoringProcessor extends FixableUsagesRefactori } //todo check accessibility conflicts } - final MultiMap conflictsMap = pushDownConflicts.getConflicts(); - for (PsiElement element : conflictsMap.keySet()) { - conflicts.put(element, conflictsMap.get(element)); - } if (myCurrentInheritor != null) { ReferencesSearch.search(myCurrentInheritor).forEach(new Processor() { @Override @@ -276,7 +272,8 @@ public class InlineSuperClassRefactoringProcessor extends FixableUsagesRefactori } protected void performRefactoring(@NotNull final UsageInfo[] usages) { - new PushDownProcessor(mySuperClass.getProject(), myMemberInfos, mySuperClass, new DocCommentPolicy(myPolicy)) { + final DocCommentPolicy docPolicy = new DocCommentPolicy(myPolicy); + new PushDownProcessor(mySuperClass, myMemberInfos, docPolicy) { //push down conflicts are already collected @Override protected boolean showConflicts(@NotNull MultiMap conflicts, UsageInfo[] usages) { @@ -286,8 +283,7 @@ public class InlineSuperClassRefactoringProcessor extends FixableUsagesRefactori @Override protected void performRefactoring(@NotNull UsageInfo[] pushDownUsages) { if (myCurrentInheritor != null) { - encodeRefs(); - pushDownToClass(myCurrentInheritor); + pushDownToDedicatedClass(myCurrentInheritor); } else { super.performRefactoring(pushDownUsages); } diff --git a/java/java-impl/src/com/intellij/refactoring/memberPushDown/JavaPushDownDelegate.java b/java/java-impl/src/com/intellij/refactoring/memberPushDown/JavaPushDownDelegate.java new file mode 100644 index 000000000000..b4284a7d5a9c --- /dev/null +++ b/java/java-impl/src/com/intellij/refactoring/memberPushDown/JavaPushDownDelegate.java @@ -0,0 +1,438 @@ +/* + * Copyright 2000-2016 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.memberPushDown; + +import com.intellij.codeInsight.AnnotationUtil; +import com.intellij.codeInsight.ChangeContextUtil; +import com.intellij.codeInsight.generation.OverrideImplementUtil; +import com.intellij.codeInsight.intention.impl.CreateClassDialog; +import com.intellij.codeInsight.intention.impl.CreateSubclassAction; +import com.intellij.lang.java.JavaLanguage; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.util.Key; +import com.intellij.psi.*; +import com.intellij.psi.codeStyle.JavaCodeStyleManager; +import com.intellij.psi.javadoc.PsiDocComment; +import com.intellij.psi.search.searches.ClassInheritorsSearch; +import com.intellij.psi.search.searches.FunctionalExpressionSearch; +import com.intellij.psi.search.searches.ReferencesSearch; +import com.intellij.psi.util.MethodSignatureUtil; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.psi.util.PsiUtil; +import com.intellij.psi.util.TypeConversionUtil; +import com.intellij.refactoring.RefactoringBundle; +import com.intellij.refactoring.classMembers.MemberInfoBase; +import com.intellij.refactoring.listeners.JavaRefactoringListenerManager; +import com.intellij.refactoring.listeners.impl.JavaRefactoringListenerManagerImpl; +import com.intellij.refactoring.util.DocCommentPolicy; +import com.intellij.refactoring.util.RefactoringUtil; +import com.intellij.refactoring.util.classMembers.MemberInfo; +import com.intellij.util.IncorrectOperationException; +import com.intellij.util.containers.HashSet; +import com.intellij.util.containers.MultiMap; +import org.jetbrains.annotations.NotNull; + +import java.util.*; + +public class JavaPushDownDelegate extends PushDownDelegate { + public static final Key REMOVE_QUALIFIER_KEY = Key.create("REMOVE_QUALIFIER_KEY"); + public static final Key REPLACE_QUALIFIER_KEY = Key.create("REPLACE_QUALIFIER_KEY"); + + private static final Logger LOG = Logger.getInstance("#" + JavaPushDownDelegate.class.getName()); + + @Override + public boolean isApplicableForSource(@NotNull PsiElement sourceClass) { + return sourceClass.getLanguage().isKindOf(JavaLanguage.INSTANCE); + } + + @Override + public List findInheritors(PushDownData pushDownData) { + final List result = new ArrayList<>(); + final PsiClass aClass = (PsiClass)pushDownData.getSourceClass(); + ClassInheritorsSearch.search(aClass, false).forEach((iClass) -> { + result.add(iClass); + return true; + }); + + final PsiMethod interfaceMethod = LambdaUtil.getFunctionalInterfaceMethod(aClass); + if (interfaceMethod != null) { + for (MemberInfoBase info : pushDownData.getMembersToMove()) { + if (interfaceMethod == info.getMember()) { + FunctionalExpressionSearch.search(aClass).forEach(expression -> { + result.add(expression); + return true; + }); + break; + } + } + } + return result; + } + + @Override + public void checkSourceClassConflicts(PushDownData pushDownData, MultiMap conflicts) { + new PushDownConflicts((PsiClass)pushDownData.getSourceClass(), (MemberInfo[])pushDownData.getMembersToMove(), conflicts).checkSourceClassConflicts(); + } + + @Override + public void checkTargetClassConflicts(PsiElement targetClass, + PushDownData pushDownData, + boolean checkStatic, + PsiElement context, + MultiMap conflicts) { + new PushDownConflicts((PsiClass)pushDownData.getSourceClass(), (MemberInfo[])pushDownData.getMembersToMove(), conflicts).checkTargetClassConflicts(targetClass, checkStatic, context); + } + + @Override + public NewSubClassData preprocessNoInheritorsFound(PsiElement sourceClass, String conflictDialogTitle) { + final PsiClass aClass = (PsiClass)sourceClass; + if (aClass.isEnum() || aClass.hasModifierProperty(PsiModifier.FINAL)) { + if (Messages.showOkCancelDialog((aClass.isEnum() ? "Enum " + aClass.getQualifiedName() + " doesn't have constants to inline to. " : "Final class " + aClass.getQualifiedName() + "does not have inheritors. ") + + "Pushing members down will result in them being deleted. " + + "Would you like to proceed?", conflictDialogTitle, Messages.getWarningIcon()) != Messages.OK) { + return NewSubClassData.EMPTY; + } + } else { + String noInheritors = aClass.isInterface() ? + RefactoringBundle.message("interface.0.does.not.have.inheritors", aClass.getQualifiedName()) : + RefactoringBundle.message("class.0.does.not.have.inheritors", aClass.getQualifiedName()); + final String message = noInheritors + "\n" + RefactoringBundle.message("push.down.will.delete.members"); + final int answer = Messages.showYesNoCancelDialog(message, conflictDialogTitle, Messages.getWarningIcon()); + if (answer == Messages.YES) { + final CreateClassDialog classDialog = CreateSubclassAction.chooseSubclassToCreate(aClass); + if (classDialog != null) { + return new NewSubClassData(classDialog.getTargetDirectory(), classDialog.getClassName()); + } else { + return NewSubClassData.EMPTY; + } + } + else if (answer != Messages.NO) { + return NewSubClassData.EMPTY; + } + } + return null; + } + + @Override + public void prepareToPush(PushDownData pushDownData) { + final Set movedMembers = new HashSet(); + for (MemberInfoBase memberInfo : pushDownData.getMembersToMove()) { + movedMembers.add((PsiMember)memberInfo.getMember()); + } + + for (MemberInfoBase memberInfo : pushDownData.getMembersToMove()) { + final PsiElement member = memberInfo.getMember(); + member.accept(new JavaRecursiveElementVisitor() { + @Override public void visitReferenceExpression(PsiReferenceExpression expression) { + encodeRef((PsiClass)pushDownData.getSourceClass(), expression, movedMembers, expression); + super.visitReferenceExpression(expression); + } + + @Override public void visitNewExpression(PsiNewExpression expression) { + final PsiJavaCodeReferenceElement classReference = expression.getClassReference(); + if (classReference != null) { + encodeRef((PsiClass)pushDownData.getSourceClass(), classReference, movedMembers, expression); + } + super.visitNewExpression(expression); + } + + @Override + public void visitTypeElement(final PsiTypeElement type) { + final PsiJavaCodeReferenceElement referenceElement = type.getInnermostComponentReferenceElement(); + if (referenceElement != null) { + encodeRef((PsiClass)pushDownData.getSourceClass(), referenceElement, movedMembers, type); + } + super.visitTypeElement(type); + } + }); + ChangeContextUtil.encodeContextInfo(member, false); + } + } + + @Override + public void pushDownToClass(PsiElement targetElement, PushDownData pushDownData) { + final PsiElementFactory factory = JavaPsiFacade.getInstance(pushDownData.getSourceClass().getProject()).getElementFactory(); + final PsiClass targetClass = targetElement instanceof PsiClass ? (PsiClass)targetElement : null; + if (targetClass == null) { + return; + } + final PsiClass sourceClass = (PsiClass)pushDownData.getSourceClass(); + final PsiSubstitutor substitutor = TypeConversionUtil.getSuperClassSubstitutor(sourceClass, targetClass, PsiSubstitutor.EMPTY); + for (MemberInfoBase memberInfo : pushDownData.getMembersToMove()) { + PsiMember member = (PsiMember)memberInfo.getMember(); + final List refsToRebind = new ArrayList(); + final PsiModifierList list = member.getModifierList(); + LOG.assertTrue(list != null); + if (list.hasModifierProperty(PsiModifier.STATIC)) { + for (final PsiReference reference : ReferencesSearch.search(member)) { + final PsiElement element = reference.getElement(); + if (element instanceof PsiReferenceExpression) { + final PsiExpression qualifierExpression = ((PsiReferenceExpression)element).getQualifierExpression(); + if (qualifierExpression instanceof PsiReferenceExpression && !(((PsiReferenceExpression)qualifierExpression).resolve() instanceof PsiClass)) { + continue; + } + } + refsToRebind.add(reference); + } + } + member = (PsiMember)member.copy(); + RefactoringUtil.replaceMovedMemberTypeParameters(member, PsiUtil.typeParametersIterable(sourceClass), substitutor, factory); + PsiMember newMember = null; + if (member instanceof PsiField) { + ((PsiField)member).normalizeDeclaration(); + if (sourceClass.isInterface() && !targetClass.isInterface()) { + PsiUtil.setModifierProperty(member, PsiModifier.PUBLIC, true); + PsiUtil.setModifierProperty(member, PsiModifier.STATIC, true); + PsiUtil.setModifierProperty(member, PsiModifier.FINAL, true); + } + newMember = (PsiMember)targetClass.add(member); + } + else if (member instanceof PsiMethod) { + PsiMethod method = (PsiMethod)member; + PsiMethod methodBySignature = MethodSignatureUtil.findMethodBySuperSignature(targetClass, method.getSignature(substitutor), false); + if (methodBySignature == null) { + newMember = (PsiMethod)targetClass.add(method); + if (sourceClass.isInterface()) { + if (!targetClass.isInterface()) { + PsiUtil.setModifierProperty(newMember, PsiModifier.PUBLIC, true); + if (newMember.hasModifierProperty(PsiModifier.DEFAULT)) { + PsiUtil.setModifierProperty(newMember, PsiModifier.DEFAULT, false); + } + else { + PsiUtil.setModifierProperty(newMember, PsiModifier.ABSTRACT, true); + } + } + } + else if (memberInfo.isToAbstract()) { + if (newMember.hasModifierProperty(PsiModifier.PRIVATE)) { + PsiUtil.setModifierProperty(newMember, PsiModifier.PROTECTED, true); + } + pushDownData.getCommentPolicy().processNewJavaDoc(((PsiMethod)newMember).getDocComment()); + } + if (memberInfo.isToAbstract()) { + OverrideImplementUtil.annotateOnOverrideImplement((PsiMethod)newMember, targetClass, (PsiMethod)memberInfo.getMember()); + } + } + else { //abstract method: remove @Override + final PsiAnnotation annotation = AnnotationUtil.findAnnotation(methodBySignature, "java.lang.Override"); + if (annotation != null && !leaveOverrideAnnotation(sourceClass, substitutor, method)) { + annotation.delete(); + } + final PsiDocComment oldDocComment = method.getDocComment(); + if (oldDocComment != null) { + final PsiDocComment docComment = methodBySignature.getDocComment(); + final int policy = pushDownData.getCommentPolicy().getJavaDocPolicy(); + if (policy == DocCommentPolicy.COPY || policy == DocCommentPolicy.MOVE) { + if (docComment != null) { + docComment.replace(oldDocComment); + } + else { + methodBySignature.getParent().addBefore(oldDocComment, methodBySignature); + } + } + } + } + } + else if (member instanceof PsiClass) { + if (Boolean.FALSE.equals(memberInfo.getOverrides())) { + final PsiClass psiClass = (PsiClass)memberInfo.getMember(); + PsiClassType classType = null; + if (!targetClass.isInheritor(psiClass, false)) { + final PsiClassType[] types = ((MemberInfo)memberInfo).getSourceReferenceList().getReferencedTypes(); + for (PsiClassType type : types) { + if (type.resolve() == psiClass) { + classType = (PsiClassType)substitutor.substitute(type); + } + } + PsiJavaCodeReferenceElement classRef = classType != null ? factory.createReferenceElementByType(classType) : factory.createClassReferenceElement(psiClass); + if (psiClass.isInterface()) { + targetClass.getImplementsList().add(classRef); + } else { + targetClass.getExtendsList().add(classRef); + } + } + } + else { + newMember = (PsiMember)targetClass.add(member); + } + } + + if (newMember != null) { + decodeRefs(sourceClass, newMember, targetClass); + //rebind imports first + Collections.sort(refsToRebind, Comparator.comparing(PsiReference::getElement, PsiUtil.BY_POSITION)); + for (PsiReference psiReference : refsToRebind) { + JavaCodeStyleManager.getInstance(sourceClass.getProject()).shortenClassReferences(psiReference.bindToElement(newMember)); + } + final JavaRefactoringListenerManager listenerManager = JavaRefactoringListenerManager.getInstance(newMember.getProject()); + ((JavaRefactoringListenerManagerImpl)listenerManager).fireMemberMoved(sourceClass, newMember); + } + } + } + + @Override + public void removeFromSourceClass(PushDownData pushDownData) { + for (MemberInfoBase memberInfo : pushDownData.getMembersToMove()) { + final PsiElement member = memberInfo.getMember(); + + if (member instanceof PsiField) { + member.delete(); + } + else if (member instanceof PsiMethod) { + if (memberInfo.isToAbstract()) { + final PsiMethod method = (PsiMethod)member; + if (method.hasModifierProperty(PsiModifier.PRIVATE)) { + PsiUtil.setModifierProperty(method, PsiModifier.PROTECTED, true); + } + RefactoringUtil.makeMethodAbstract((PsiClass)pushDownData.getSourceClass(), method); + pushDownData.getCommentPolicy().processOldJavaDoc(method.getDocComment()); + } + else { + member.delete(); + } + } + else if (member instanceof PsiClass) { + if (Boolean.FALSE.equals(memberInfo.getOverrides())) { + RefactoringUtil.removeFromReferenceList(((PsiClass)pushDownData.getSourceClass()).getImplementsList(), (PsiClass)member); + } + else { + member.delete(); + } + } + } + } + + @Override + public PsiElement createSubClass(PsiElement aClass, NewSubClassData subClassData) { + return CreateSubclassAction.createSubclass((PsiClass)aClass, (PsiDirectory)subClassData.getContext(), subClassData.getNewClassName()); + } + + private static boolean leaveOverrideAnnotation(PsiClass aClass, PsiSubstitutor substitutor, PsiMethod method) { + final PsiMethod methodBySignature = MethodSignatureUtil.findMethodBySignature(aClass, method.getSignature(substitutor), false); + if (methodBySignature == null) return false; + final PsiMethod[] superMethods = methodBySignature.findDeepestSuperMethods(); + if (superMethods.length == 0) return false; + final boolean is15 = !PsiUtil.isLanguageLevel6OrHigher(methodBySignature); + if (is15) { + for (PsiMethod psiMethod : superMethods) { + final PsiClass psiClass = psiMethod.getContainingClass(); + if (psiClass != null && psiClass.isInterface()) { + return false; + } + } + } + return true; + } + + private static void encodeRef(PsiClass aClass, + final PsiJavaCodeReferenceElement expression, + final Set movedMembers, + final PsiElement toPut) { + final PsiElement resolved = expression.resolve(); + if (resolved == null) return; + final PsiElement qualifier = expression.getQualifier(); + for (PsiMember movedMember : movedMembers) { + if (movedMember.equals(resolved)) { + if (qualifier == null) { + toPut.putCopyableUserData(REMOVE_QUALIFIER_KEY, Boolean.TRUE); + } else { + if (qualifier instanceof PsiJavaCodeReferenceElement && + ((PsiJavaCodeReferenceElement)qualifier).isReferenceTo(aClass)) { + toPut.putCopyableUserData(REPLACE_QUALIFIER_KEY, aClass); + } + } + } else if (movedMember instanceof PsiClass && PsiTreeUtil.getParentOfType(resolved, PsiClass.class, false) == movedMember) { + if (qualifier instanceof PsiJavaCodeReferenceElement && ((PsiJavaCodeReferenceElement)qualifier).isReferenceTo(movedMember)) { + toPut.putCopyableUserData(REPLACE_QUALIFIER_KEY, (PsiClass)movedMember); + } + } else { + if (qualifier instanceof PsiThisExpression) { + final PsiJavaCodeReferenceElement qElement = ((PsiThisExpression)qualifier).getQualifier(); + if (qElement != null && qElement.isReferenceTo(aClass)) { + toPut.putCopyableUserData(REPLACE_QUALIFIER_KEY, aClass); + } + } + } + } + } + + private static void decodeRefs(PsiClass sourceClass, final PsiMember member, final PsiClass targetClass) { + final PsiElementFactory factory = JavaPsiFacade.getInstance(sourceClass.getProject()).getElementFactory(); + member.accept(new JavaRecursiveElementWalkingVisitor() { + @Override public void visitReferenceExpression(PsiReferenceExpression expression) { + decodeRef(sourceClass, expression, factory, targetClass, expression); + super.visitReferenceExpression(expression); + } + + @Override public void visitNewExpression(PsiNewExpression expression) { + final PsiJavaCodeReferenceElement classReference = expression.getClassReference(); + if (classReference != null) decodeRef(sourceClass, classReference, factory, targetClass, expression); + super.visitNewExpression(expression); + } + + @Override + public void visitTypeElement(final PsiTypeElement type) { + final PsiJavaCodeReferenceElement referenceElement = type.getInnermostComponentReferenceElement(); + if (referenceElement != null) decodeRef(sourceClass, referenceElement, factory, targetClass, type); + super.visitTypeElement(type); + } + }); + } + + private static void decodeRef(PsiClass sourceClass, + final PsiJavaCodeReferenceElement ref, + final PsiElementFactory factory, + final PsiClass targetClass, + final PsiElement toGet) { + try { + if (toGet.getCopyableUserData(REMOVE_QUALIFIER_KEY) != null) { + toGet.putCopyableUserData(REMOVE_QUALIFIER_KEY, null); + final PsiElement qualifier = ref.getQualifier(); + if (qualifier != null) qualifier.delete(); + } + else { + PsiClass psiClass = toGet.getCopyableUserData(REPLACE_QUALIFIER_KEY); + if (psiClass != null) { + toGet.putCopyableUserData(REPLACE_QUALIFIER_KEY, null); + PsiElement qualifier = ref.getQualifier(); + if (qualifier != null) { + + if (psiClass == sourceClass) { + psiClass = targetClass; + } else if (psiClass.getContainingClass() == sourceClass) { + psiClass = targetClass.findInnerClassByName(psiClass.getName(), false); + LOG.assertTrue(psiClass != null); + } + + if (!(qualifier instanceof PsiThisExpression) && ref instanceof PsiReferenceExpression) { + ((PsiReferenceExpression)ref).setQualifierExpression(factory.createReferenceExpression(psiClass)); + } + else { + if (qualifier instanceof PsiThisExpression) { + qualifier = ((PsiThisExpression)qualifier).getQualifier(); + } + qualifier.replace(factory.createReferenceElementByType(factory.createType(psiClass))); + } + } + } + } + } + catch (IncorrectOperationException e) { + LOG.error(e); + } + } +} diff --git a/java/java-impl/src/com/intellij/refactoring/memberPushDown/PushDownConflicts.java b/java/java-impl/src/com/intellij/refactoring/memberPushDown/PushDownConflicts.java index f64e278e7994..55acbe88e55a 100644 --- a/java/java-impl/src/com/intellij/refactoring/memberPushDown/PushDownConflicts.java +++ b/java/java-impl/src/com/intellij/refactoring/memberPushDown/PushDownConflicts.java @@ -37,7 +37,7 @@ public class PushDownConflicts { private final MultiMap myConflicts; - public PushDownConflicts(PsiClass aClass, MemberInfo[] memberInfos) { + public PushDownConflicts(PsiClass aClass, MemberInfo[] memberInfos, MultiMap conflicts) { myClass = aClass; myMovedMembers = new HashSet(); @@ -52,7 +52,7 @@ public class PushDownConflicts { } } - myConflicts = new MultiMap(); + myConflicts = conflicts; } public boolean isAnyConflicts() { diff --git a/java/java-impl/src/com/intellij/refactoring/memberPushDown/PushDownDialog.java b/java/java-impl/src/com/intellij/refactoring/memberPushDown/PushDownDialog.java index d0ce36fab2d2..6e1a3b331af2 100644 --- a/java/java-impl/src/com/intellij/refactoring/memberPushDown/PushDownDialog.java +++ b/java/java-impl/src/com/intellij/refactoring/memberPushDown/PushDownDialog.java @@ -117,9 +117,7 @@ public class PushDownDialog extends RefactoringDialog { JavaRefactoringSettings.getInstance().PUSH_DOWN_PREVIEW_USAGES = isPreviewUsages(); - invokeRefactoring (new PushDownProcessor( - getProject(), getSelectedMemberInfos(), myClass, - new DocCommentPolicy(getJavaDocPolicy()))); + invokeRefactoring(new PushDownProcessor(myClass, getSelectedMemberInfos(), new DocCommentPolicy(getJavaDocPolicy()))); } private class MyMemberInfoModel extends UsedByDependencyMemberInfoModel { diff --git a/java/java-impl/src/com/intellij/refactoring/memberPushDown/PushDownProcessor.java b/java/java-impl/src/com/intellij/refactoring/memberPushDown/PushDownProcessor.java index 06520bbcb80d..02f30c23525c 100644 --- a/java/java-impl/src/com/intellij/refactoring/memberPushDown/PushDownProcessor.java +++ b/java/java-impl/src/com/intellij/refactoring/memberPushDown/PushDownProcessor.java @@ -15,63 +15,17 @@ */ package com.intellij.refactoring.memberPushDown; -import com.intellij.codeInsight.AnnotationUtil; -import com.intellij.codeInsight.ChangeContextUtil; -import com.intellij.codeInsight.generation.OverrideImplementUtil; -import com.intellij.codeInsight.intention.impl.CreateClassDialog; -import com.intellij.codeInsight.intention.impl.CreateSubclassAction; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.progress.ProgressManager; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.Messages; -import com.intellij.openapi.util.Key; -import com.intellij.openapi.util.Ref; -import com.intellij.psi.*; -import com.intellij.psi.codeStyle.JavaCodeStyleManager; -import com.intellij.psi.javadoc.PsiDocComment; -import com.intellij.psi.search.searches.ClassInheritorsSearch; -import com.intellij.psi.search.searches.FunctionalExpressionSearch; -import com.intellij.psi.search.searches.ReferencesSearch; -import com.intellij.psi.util.MethodSignatureUtil; -import com.intellij.psi.util.PsiTreeUtil; -import com.intellij.psi.util.PsiUtil; -import com.intellij.psi.util.TypeConversionUtil; -import com.intellij.refactoring.BaseRefactoringProcessor; -import com.intellij.refactoring.RefactoringBundle; -import com.intellij.refactoring.listeners.JavaRefactoringListenerManager; -import com.intellij.refactoring.listeners.RefactoringEventData; -import com.intellij.refactoring.listeners.impl.JavaRefactoringListenerManagerImpl; +import com.intellij.psi.PsiClass; import com.intellij.refactoring.util.DocCommentPolicy; -import com.intellij.refactoring.util.RefactoringUtil; import com.intellij.refactoring.util.classMembers.MemberInfo; -import com.intellij.usageView.UsageInfo; -import com.intellij.usageView.UsageViewDescriptor; -import com.intellij.util.Function; -import com.intellij.util.IncorrectOperationException; -import com.intellij.util.Processor; -import com.intellij.util.containers.HashSet; -import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.*; +public class PushDownProcessor extends AbstractPushDownProcessor { -public class PushDownProcessor extends BaseRefactoringProcessor { - private static final Logger LOG = Logger.getInstance("#com.intellij.refactoring.memberPushDown.PushDownProcessor"); - - private final MemberInfo[] myMemberInfos; - private PsiClass myClass; - private final DocCommentPolicy myJavaDocPolicy; - private CreateClassDialog myCreateClassDlg; - - public PushDownProcessor(Project project, + public PushDownProcessor(PsiClass aClass, MemberInfo[] memberInfos, - PsiClass aClass, DocCommentPolicy javaDocPolicy) { - super(project); - myMemberInfos = memberInfos; - myClass = aClass; - myJavaDocPolicy = javaDocPolicy; + super(aClass, memberInfos, javaDocPolicy); } @Override @@ -79,471 +33,9 @@ public class PushDownProcessor extends BaseRefactoringProcessor { return JavaPushDownHandler.REFACTORING_NAME; } - @Override - @NotNull - protected UsageViewDescriptor createUsageViewDescriptor(@NotNull UsageInfo[] usages) { - return new PushDownUsageViewDescriptor(myClass, myMemberInfos); - } - - @NotNull - @Override - protected Collection getElementsToWrite(@NotNull UsageViewDescriptor descriptor) { - return Collections.singletonList(myClass); - } - @Nullable @Override protected String getRefactoringId() { return "refactoring.push.down"; } - - @Nullable - @Override - protected RefactoringEventData getBeforeData() { - RefactoringEventData data = new RefactoringEventData(); - data.addElement(myClass); - data.addMembers(myMemberInfos, new Function() { - @Override - public PsiElement fun(MemberInfo info) { - return info.getMember(); - } - }); - return data; - } - - @Nullable - @Override - protected RefactoringEventData getAfterData(@NotNull UsageInfo[] usages) { - final List elements = new ArrayList(); - for (UsageInfo usage : usages) { - PsiElement element = usage.getElement(); - if (element instanceof PsiClass) { - elements.add(element); - } - } - RefactoringEventData data = new RefactoringEventData(); - data.addElements(elements); - return data; - } - - @Override - @NotNull - protected UsageInfo[] findUsages() { - final PsiClass[] inheritors = ClassInheritorsSearch.search(myClass, false).toArray(PsiClass.EMPTY_ARRAY); - final List usages = new ArrayList(inheritors.length); - for (PsiClass inheritor : inheritors) { - usages.add(new UsageInfo(inheritor)); - } - - final PsiMethod interfaceMethod = LambdaUtil.getFunctionalInterfaceMethod(myClass); - if (interfaceMethod != null && isMoved(interfaceMethod)) { - FunctionalExpressionSearch.search(myClass).forEach(new Processor() { - @Override - public boolean process(PsiFunctionalExpression expression) { - usages.add(new UsageInfo(expression)); - return true; - } - }); - } - - return usages.toArray(new UsageInfo[usages.size()]); - } - - private boolean isMoved(PsiMember member) { - for (MemberInfo info : myMemberInfos) { - if (member == info.getMember()) { - return true; - } - } - return false; - } - - @Override - protected boolean preprocessUsages(@NotNull final Ref refUsages) { - final UsageInfo[] usagesIn = refUsages.get(); - final PushDownConflicts pushDownConflicts = new PushDownConflicts(myClass, myMemberInfos); - pushDownConflicts.checkSourceClassConflicts(); - - if (usagesIn.length == 0) { - Boolean answered = preprocessNoInheritorsFound(usagesIn, pushDownConflicts); - if (answered != null) { - return answered; - } - } - Runnable runnable = new Runnable() { - @Override - public void run() { - ApplicationManager.getApplication().runReadAction(new Runnable() { - @Override - public void run() { - for (UsageInfo usage : usagesIn) { - pushDownConflicts.checkTargetClassConflicts(usage.getElement(), usagesIn.length > 1, usage.getElement()); - } - } - }); - } - }; - - if (!ProgressManager.getInstance().runProcessWithProgressSynchronously(runnable, RefactoringBundle.message("detecting.possible.conflicts"), true, myProject)) { - return false; - } - - return showConflicts(pushDownConflicts.getConflicts(), usagesIn); - } - - @Nullable - protected Boolean preprocessNoInheritorsFound(UsageInfo[] usagesIn, PushDownConflicts pushDownConflicts) { - if (myClass.isEnum() || myClass.hasModifierProperty(PsiModifier.FINAL)) { - if (Messages.showOkCancelDialog((myClass.isEnum() ? "Enum " + myClass.getQualifiedName() + " doesn't have constants to inline to. " : "Final class " + myClass.getQualifiedName() + "does not have inheritors. ") + - "Pushing members down will result in them being deleted. " + - "Would you like to proceed?", JavaPushDownHandler.REFACTORING_NAME, Messages.getWarningIcon()) != Messages.OK) { - return false; - } - } else { - String noInheritors = myClass.isInterface() ? - RefactoringBundle.message("interface.0.does.not.have.inheritors", myClass.getQualifiedName()) : - RefactoringBundle.message("class.0.does.not.have.inheritors", myClass.getQualifiedName()); - final String message = noInheritors + "\n" + RefactoringBundle.message("push.down.will.delete.members"); - final int answer = Messages.showYesNoCancelDialog(message, JavaPushDownHandler.REFACTORING_NAME, Messages.getWarningIcon()); - if (answer == Messages.YES) { - myCreateClassDlg = CreateSubclassAction.chooseSubclassToCreate(myClass); - if (myCreateClassDlg != null) { - pushDownConflicts.checkTargetClassConflicts(null, false, myCreateClassDlg.getTargetDirectory()); - return showConflicts(pushDownConflicts.getConflicts(), usagesIn); - } else { - return false; - } - } else if (answer != Messages.NO) return false; - } - return null; - } - - @Override - protected void refreshElements(@NotNull PsiElement[] elements) { - if(elements.length == 1 && elements[0] instanceof PsiClass) { - myClass = (PsiClass) elements[0]; - } - else { - LOG.assertTrue(false); - } - } - - @Override - protected void performRefactoring(@NotNull UsageInfo[] usages) { - try { - encodeRefs(); - if (myCreateClassDlg != null) { //usages.length == 0 - final PsiClass psiClass = - CreateSubclassAction.createSubclass(myClass, myCreateClassDlg.getTargetDirectory(), myCreateClassDlg.getClassName()); - if (psiClass != null) { - pushDownToClass(psiClass); - } - } - for (UsageInfo usage : usages) { - if (usage.getElement() instanceof PsiClass) { - final PsiClass targetClass = (PsiClass)usage.getElement(); - pushDownToClass(targetClass); - } - } - removeFromTargetClass(); - } - catch (IncorrectOperationException e) { - LOG.error(e); - } - } - - private static final Key REMOVE_QUALIFIER_KEY = Key.create("REMOVE_QUALIFIER_KEY"); - private static final Key REPLACE_QUALIFIER_KEY = Key.create("REPLACE_QUALIFIER_KEY"); - - protected void encodeRefs() { - final Set movedMembers = new HashSet(); - for (MemberInfo memberInfo : myMemberInfos) { - movedMembers.add(memberInfo.getMember()); - } - - for (MemberInfo memberInfo : myMemberInfos) { - final PsiMember member = memberInfo.getMember(); - member.accept(new JavaRecursiveElementVisitor() { - @Override public void visitReferenceExpression(PsiReferenceExpression expression) { - encodeRef(expression, movedMembers, expression); - super.visitReferenceExpression(expression); - } - - @Override public void visitNewExpression(PsiNewExpression expression) { - final PsiJavaCodeReferenceElement classReference = expression.getClassReference(); - if (classReference != null) { - encodeRef(classReference, movedMembers, expression); - } - super.visitNewExpression(expression); - } - - @Override - public void visitTypeElement(final PsiTypeElement type) { - final PsiJavaCodeReferenceElement referenceElement = type.getInnermostComponentReferenceElement(); - if (referenceElement != null) { - encodeRef(referenceElement, movedMembers, type); - } - super.visitTypeElement(type); - } - }); - ChangeContextUtil.encodeContextInfo(member, false); - } - } - - private void encodeRef(final PsiJavaCodeReferenceElement expression, final Set movedMembers, final PsiElement toPut) { - final PsiElement resolved = expression.resolve(); - if (resolved == null) return; - final PsiElement qualifier = expression.getQualifier(); - for (PsiMember movedMember : movedMembers) { - if (movedMember.equals(resolved)) { - if (qualifier == null) { - toPut.putCopyableUserData(REMOVE_QUALIFIER_KEY, Boolean.TRUE); - } else { - if (qualifier instanceof PsiJavaCodeReferenceElement && - ((PsiJavaCodeReferenceElement)qualifier).isReferenceTo(myClass)) { - toPut.putCopyableUserData(REPLACE_QUALIFIER_KEY, myClass); - } - } - } else if (movedMember instanceof PsiClass && PsiTreeUtil.getParentOfType(resolved, PsiClass.class, false) == movedMember) { - if (qualifier instanceof PsiJavaCodeReferenceElement && ((PsiJavaCodeReferenceElement)qualifier).isReferenceTo(movedMember)) { - toPut.putCopyableUserData(REPLACE_QUALIFIER_KEY, (PsiClass)movedMember); - } - } else { - if (qualifier instanceof PsiThisExpression) { - final PsiJavaCodeReferenceElement qElement = ((PsiThisExpression)qualifier).getQualifier(); - if (qElement != null && qElement.isReferenceTo(myClass)) { - toPut.putCopyableUserData(REPLACE_QUALIFIER_KEY, myClass); - } - } - } - } - } - - private void decodeRefs(final PsiMember member, final PsiClass targetClass) { - final PsiElementFactory factory = JavaPsiFacade.getInstance(myProject).getElementFactory(); - member.accept(new JavaRecursiveElementWalkingVisitor() { - @Override public void visitReferenceExpression(PsiReferenceExpression expression) { - decodeRef(expression, factory, targetClass, expression); - super.visitReferenceExpression(expression); - } - - @Override public void visitNewExpression(PsiNewExpression expression) { - final PsiJavaCodeReferenceElement classReference = expression.getClassReference(); - if (classReference != null) decodeRef(classReference, factory, targetClass, expression); - super.visitNewExpression(expression); - } - - @Override - public void visitTypeElement(final PsiTypeElement type) { - final PsiJavaCodeReferenceElement referenceElement = type.getInnermostComponentReferenceElement(); - if (referenceElement != null) decodeRef(referenceElement, factory, targetClass, type); - super.visitTypeElement(type); - } - }); - } - - private void decodeRef(final PsiJavaCodeReferenceElement ref, - final PsiElementFactory factory, - final PsiClass targetClass, - final PsiElement toGet) { - try { - if (toGet.getCopyableUserData(REMOVE_QUALIFIER_KEY) != null) { - toGet.putCopyableUserData(REMOVE_QUALIFIER_KEY, null); - final PsiElement qualifier = ref.getQualifier(); - if (qualifier != null) qualifier.delete(); - } - else { - PsiClass psiClass = toGet.getCopyableUserData(REPLACE_QUALIFIER_KEY); - if (psiClass != null) { - toGet.putCopyableUserData(REPLACE_QUALIFIER_KEY, null); - PsiElement qualifier = ref.getQualifier(); - if (qualifier != null) { - - if (psiClass == myClass) { - psiClass = targetClass; - } else if (psiClass.getContainingClass() == myClass) { - psiClass = targetClass.findInnerClassByName(psiClass.getName(), false); - LOG.assertTrue(psiClass != null); - } - - if (!(qualifier instanceof PsiThisExpression) && ref instanceof PsiReferenceExpression) { - ((PsiReferenceExpression)ref).setQualifierExpression(factory.createReferenceExpression(psiClass)); - } - else { - if (qualifier instanceof PsiThisExpression) { - qualifier = ((PsiThisExpression)qualifier).getQualifier(); - } - qualifier.replace(factory.createReferenceElementByType(factory.createType(psiClass))); - } - } - } - } - } - catch (IncorrectOperationException e) { - LOG.error(e); - } - } - - private void removeFromTargetClass() throws IncorrectOperationException { - for (MemberInfo memberInfo : myMemberInfos) { - final PsiElement member = memberInfo.getMember(); - - if (member instanceof PsiField) { - member.delete(); - } - else if (member instanceof PsiMethod) { - if (memberInfo.isToAbstract()) { - final PsiMethod method = (PsiMethod)member; - if (method.hasModifierProperty(PsiModifier.PRIVATE)) { - PsiUtil.setModifierProperty(method, PsiModifier.PROTECTED, true); - } - RefactoringUtil.makeMethodAbstract(myClass, method); - myJavaDocPolicy.processOldJavaDoc(method.getDocComment()); - } - else { - member.delete(); - } - } - else if (member instanceof PsiClass) { - if (Boolean.FALSE.equals(memberInfo.getOverrides())) { - RefactoringUtil.removeFromReferenceList(myClass.getImplementsList(), (PsiClass)member); - } - else { - member.delete(); - } - } - } - } - - protected void pushDownToClass(PsiClass targetClass) throws IncorrectOperationException { - final PsiElementFactory factory = JavaPsiFacade.getInstance(myClass.getProject()).getElementFactory(); - final PsiSubstitutor substitutor = TypeConversionUtil.getSuperClassSubstitutor(myClass, targetClass, PsiSubstitutor.EMPTY); - for (MemberInfo memberInfo : myMemberInfos) { - PsiMember member = memberInfo.getMember(); - final List refsToRebind = new ArrayList(); - final PsiModifierList list = member.getModifierList(); - LOG.assertTrue(list != null); - if (list.hasModifierProperty(PsiModifier.STATIC)) { - for (final PsiReference reference : ReferencesSearch.search(member)) { - final PsiElement element = reference.getElement(); - if (element instanceof PsiReferenceExpression) { - final PsiExpression qualifierExpression = ((PsiReferenceExpression)element).getQualifierExpression(); - if (qualifierExpression instanceof PsiReferenceExpression && !(((PsiReferenceExpression)qualifierExpression).resolve() instanceof PsiClass)) { - continue; - } - } - refsToRebind.add(reference); - } - } - member = (PsiMember)member.copy(); - RefactoringUtil.replaceMovedMemberTypeParameters(member, PsiUtil.typeParametersIterable(myClass), substitutor, factory); - PsiMember newMember = null; - if (member instanceof PsiField) { - ((PsiField)member).normalizeDeclaration(); - if (myClass.isInterface() && !targetClass.isInterface()) { - PsiUtil.setModifierProperty(member, PsiModifier.PUBLIC, true); - PsiUtil.setModifierProperty(member, PsiModifier.STATIC, true); - PsiUtil.setModifierProperty(member, PsiModifier.FINAL, true); - } - newMember = (PsiMember)targetClass.add(member); - } - else if (member instanceof PsiMethod) { - PsiMethod method = (PsiMethod)member; - PsiMethod methodBySignature = MethodSignatureUtil.findMethodBySuperSignature(targetClass, method.getSignature(substitutor), false); - if (methodBySignature == null) { - newMember = (PsiMethod)targetClass.add(method); - if (myClass.isInterface()) { - if (!targetClass.isInterface()) { - PsiUtil.setModifierProperty(newMember, PsiModifier.PUBLIC, true); - if (newMember.hasModifierProperty(PsiModifier.DEFAULT)) { - PsiUtil.setModifierProperty(newMember, PsiModifier.DEFAULT, false); - } - else { - PsiUtil.setModifierProperty(newMember, PsiModifier.ABSTRACT, true); - } - } - } - else if (memberInfo.isToAbstract()) { - if (newMember.hasModifierProperty(PsiModifier.PRIVATE)) { - PsiUtil.setModifierProperty(newMember, PsiModifier.PROTECTED, true); - } - myJavaDocPolicy.processNewJavaDoc(((PsiMethod)newMember).getDocComment()); - } - if (memberInfo.isToAbstract()) { - OverrideImplementUtil.annotateOnOverrideImplement((PsiMethod)newMember, targetClass, (PsiMethod)memberInfo.getMember()); - } - } - else { //abstract method: remove @Override - final PsiAnnotation annotation = AnnotationUtil.findAnnotation(methodBySignature, "java.lang.Override"); - if (annotation != null && !leaveOverrideAnnotation(substitutor, method)) { - annotation.delete(); - } - final PsiDocComment oldDocComment = method.getDocComment(); - if (oldDocComment != null) { - final PsiDocComment docComment = methodBySignature.getDocComment(); - final int policy = myJavaDocPolicy.getJavaDocPolicy(); - if (policy == DocCommentPolicy.COPY || policy == DocCommentPolicy.MOVE) { - if (docComment != null) { - docComment.replace(oldDocComment); - } - else { - methodBySignature.getParent().addBefore(oldDocComment, methodBySignature); - } - } - } - } - } - else if (member instanceof PsiClass) { - if (Boolean.FALSE.equals(memberInfo.getOverrides())) { - final PsiClass aClass = (PsiClass)memberInfo.getMember(); - PsiClassType classType = null; - if (!targetClass.isInheritor(aClass, false)) { - final PsiClassType[] types = memberInfo.getSourceReferenceList().getReferencedTypes(); - for (PsiClassType type : types) { - if (type.resolve() == aClass) { - classType = (PsiClassType)substitutor.substitute(type); - } - } - PsiJavaCodeReferenceElement classRef = classType != null ? factory.createReferenceElementByType(classType) : factory.createClassReferenceElement(aClass); - if (aClass.isInterface()) { - targetClass.getImplementsList().add(classRef); - } else { - targetClass.getExtendsList().add(classRef); - } - } - } - else { - newMember = (PsiMember)targetClass.add(member); - } - } - - if (newMember != null) { - decodeRefs(newMember, targetClass); - //rebind imports first - Collections.sort(refsToRebind, Comparator.comparing(PsiReference::getElement, PsiUtil.BY_POSITION)); - for (PsiReference psiReference : refsToRebind) { - JavaCodeStyleManager.getInstance(myProject).shortenClassReferences(psiReference.bindToElement(newMember)); - } - final JavaRefactoringListenerManager listenerManager = JavaRefactoringListenerManager.getInstance(newMember.getProject()); - ((JavaRefactoringListenerManagerImpl)listenerManager).fireMemberMoved(myClass, newMember); - } - } - } - - private boolean leaveOverrideAnnotation(PsiSubstitutor substitutor, PsiMethod method) { - final PsiMethod methodBySignature = MethodSignatureUtil.findMethodBySignature(myClass, method.getSignature(substitutor), false); - if (methodBySignature == null) return false; - final PsiMethod[] superMethods = methodBySignature.findDeepestSuperMethods(); - if (superMethods.length == 0) return false; - final boolean is15 = !PsiUtil.isLanguageLevel6OrHigher(methodBySignature); - if (is15) { - for (PsiMethod psiMethod : superMethods) { - final PsiClass aClass = psiMethod.getContainingClass(); - if (aClass != null && aClass.isInterface()) { - return false; - } - } - } - return true; - } } diff --git a/java/java-tests/testSrc/com/intellij/refactoring/PushDownMultifileTest.java b/java/java-tests/testSrc/com/intellij/refactoring/PushDownMultifileTest.java index 2cc83bad518b..1abfcdb53004 100644 --- a/java/java-tests/testSrc/com/intellij/refactoring/PushDownMultifileTest.java +++ b/java/java-tests/testSrc/com/intellij/refactoring/PushDownMultifileTest.java @@ -67,7 +67,7 @@ public class PushDownMultifileTest extends MultiFileTestCase { memberInfo.setChecked(true); membersToMove[0] = memberInfo; - new PushDownProcessor(getProject(), membersToMove, srcClass, new DocCommentPolicy(DocCommentPolicy.ASIS)).run(); + new PushDownProcessor(srcClass, membersToMove, new DocCommentPolicy(DocCommentPolicy.ASIS)).run(); //LocalFileSystem.getInstance().refresh(false); @@ -120,7 +120,7 @@ public class PushDownMultifileTest extends MultiFileTestCase { memberInfo.setChecked(true); membersToMove[0] = memberInfo; - new PushDownProcessor(getProject(), membersToMove, srcClass, new DocCommentPolicy(DocCommentPolicy.ASIS)).run(); + new PushDownProcessor(srcClass, membersToMove, new DocCommentPolicy(DocCommentPolicy.ASIS)).run(); //LocalFileSystem.getInstance().refresh(false); diff --git a/java/java-tests/testSrc/com/intellij/refactoring/PushDownTest.java b/java/java-tests/testSrc/com/intellij/refactoring/PushDownTest.java index 344e3c904a7c..a135caefe629 100644 --- a/java/java-tests/testSrc/com/intellij/refactoring/PushDownTest.java +++ b/java/java-tests/testSrc/com/intellij/refactoring/PushDownTest.java @@ -105,7 +105,7 @@ public class PushDownTest extends LightRefactoringTestCase { memberInfo.setChecked(true); membersToMove.add(memberInfo); - new PushDownProcessor(getProject(), membersToMove.toArray(new MemberInfo[membersToMove.size()]), currentClass, + new PushDownProcessor(currentClass, membersToMove.toArray(new MemberInfo[membersToMove.size()]), new DocCommentPolicy(DocCommentPolicy.ASIS)) { @Override protected boolean showConflicts(@NotNull MultiMap conflicts, UsageInfo[] usages) { @@ -136,7 +136,7 @@ public class PushDownTest extends LightRefactoringTestCase { } } - new PushDownProcessor(getProject(), members.toArray(new MemberInfo[members.size()]), currentClass, + new PushDownProcessor(currentClass, members.toArray(new MemberInfo[members.size()]), new DocCommentPolicy(DocCommentPolicy.ASIS)) { @Override protected boolean showConflicts(@NotNull MultiMap conflicts, UsageInfo[] usages) { diff --git a/platform/lang-impl/src/com/intellij/refactoring/memberPushDown/AbstractPushDownProcessor.java b/platform/lang-impl/src/com/intellij/refactoring/memberPushDown/AbstractPushDownProcessor.java new file mode 100644 index 000000000000..f874021df7b9 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/refactoring/memberPushDown/AbstractPushDownProcessor.java @@ -0,0 +1,192 @@ +/* + * Copyright 2000-2016 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.memberPushDown; + +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.util.Ref; +import com.intellij.psi.PsiElement; +import com.intellij.refactoring.BaseRefactoringProcessor; +import com.intellij.refactoring.RefactoringBundle; +import com.intellij.refactoring.classMembers.MemberInfoBase; +import com.intellij.refactoring.listeners.RefactoringEventData; +import com.intellij.refactoring.util.DocCommentPolicy; +import com.intellij.usageView.UsageInfo; +import com.intellij.usageView.UsageViewDescriptor; +import com.intellij.util.Function; +import com.intellij.util.IncorrectOperationException; +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.Collection; +import java.util.Collections; +import java.util.List; + +public abstract class AbstractPushDownProcessor extends BaseRefactoringProcessor { + private static final Logger LOG = Logger.getInstance("#" + AbstractPushDownProcessor.class.getName()); + + private NewSubClassData mySubClassData; + private PushDownDelegate myDelegate; + private PushDownData myPushDownData; + + public AbstractPushDownProcessor(@NotNull PsiElement sourceClass, + @NotNull MemberInfoBase[] memberInfos, + @NotNull DocCommentPolicy javaDocPolicy) { + super(sourceClass.getProject()); + myDelegate = PushDownDelegate.findDelegate(sourceClass); + LOG.assertTrue(myDelegate != null); + myPushDownData = new PushDownData(sourceClass, memberInfos, javaDocPolicy); + } + + @Override + @NotNull + protected UsageViewDescriptor createUsageViewDescriptor(@NotNull UsageInfo[] usages) { + return new PushDownUsageViewDescriptor(myPushDownData.getSourceClass(), myPushDownData.getMembersToMove()); + } + + @NotNull + @Override + protected Collection getElementsToWrite(@NotNull UsageViewDescriptor descriptor) { + return Collections.singletonList(myPushDownData.getSourceClass()); + } + + @Nullable + @Override + protected RefactoringEventData getBeforeData() { + RefactoringEventData data = new RefactoringEventData(); + data.addElement(myPushDownData.getSourceClass()); + data.addMembers(myPushDownData.getMembersToMove(), new Function, PsiElement>() { + @Override + public PsiElement fun(MemberInfoBase info) { + return info.getMember(); + } + }); + return data; + } + + @Nullable + @Override + protected RefactoringEventData getAfterData(@NotNull UsageInfo[] usages) { + final List elements = new ArrayList(); + for (UsageInfo usage : usages) { + elements.add(usage.getElement()); + } + RefactoringEventData data = new RefactoringEventData(); + data.addElements(elements); + return data; + } + + @Override + @NotNull + protected UsageInfo[] findUsages() { + final List inheritors = myDelegate.findInheritors(myPushDownData); + return ContainerUtil.map2Array(inheritors, UsageInfo.EMPTY_ARRAY, myDelegate::createUsageInfo); + } + + @Override + protected boolean preprocessUsages(@NotNull final Ref refUsages) { + final MultiMap conflicts = new MultiMap<>(); + myDelegate.checkSourceClassConflicts(myPushDownData, conflicts); + final UsageInfo[] usagesIn = refUsages.get(); + if (usagesIn.length == 0) { + mySubClassData = myDelegate.preprocessNoInheritorsFound(myPushDownData.getSourceClass(), getCommandName()); + if (mySubClassData == NewSubClassData.EMPTY) { + return false; + } + } + Runnable runnable = new Runnable() { + @Override + public void run() { + ApplicationManager.getApplication().runReadAction(new Runnable() { + @Override + public void run() { + if (mySubClassData != null) { + myDelegate.checkTargetClassConflicts(null, myPushDownData, false, mySubClassData.getContext(), conflicts); + } + else { + for (UsageInfo usage : usagesIn) { + final PsiElement element = usage.getElement(); + if (element != null) { + final PushDownDelegate delegate = PushDownDelegate.findDelegateForTarget(myPushDownData.getSourceClass(), element); + if (delegate != null) { + delegate.checkTargetClassConflicts(element, myPushDownData, usagesIn.length > 1, element, conflicts); + } + else { + conflicts.putValue(element, "Not supported source/target pair detected"); + } + } + } + } + } + }); + } + }; + + if (!ProgressManager.getInstance().runProcessWithProgressSynchronously(runnable, RefactoringBundle.message("detecting.possible.conflicts"), true, myProject)) { + return false; + } + + return showConflicts(conflicts, usagesIn); + } + + @Override + protected void refreshElements(@NotNull PsiElement[] elements) { + if(elements.length == 1) { + myPushDownData.setSourceClass(elements[0]); + } + else { + LOG.assertTrue(false); + } + } + + @Override + protected void performRefactoring(@NotNull UsageInfo[] usages) { + try { + myDelegate.prepareToPush(myPushDownData); + final PsiElement sourceClass = myPushDownData.getSourceClass(); + if (mySubClassData != null) { + final PsiElement subClass = myDelegate.createSubClass(sourceClass, mySubClassData); + if (subClass != null) { + myDelegate.pushDownToClass(subClass, myPushDownData); + } + } + else { + for (UsageInfo usage : usages) { + final PsiElement element = usage.getElement(); + if (element != null) { + final PushDownDelegate targetDelegate = PushDownDelegate.findDelegateForTarget(sourceClass, element); + if (targetDelegate != null) { + targetDelegate.pushDownToClass(element, myPushDownData); + } + } + } + } + myDelegate.removeFromSourceClass(myPushDownData); + } + catch (IncorrectOperationException e) { + LOG.error(e); + } + } + + protected void pushDownToDedicatedClass(PsiElement currentInheritor) { + myDelegate.prepareToPush(myPushDownData); + myDelegate.pushDownToClass(currentInheritor, myPushDownData); + } +} diff --git a/platform/lang-impl/src/com/intellij/refactoring/memberPushDown/NewSubClassData.java b/platform/lang-impl/src/com/intellij/refactoring/memberPushDown/NewSubClassData.java new file mode 100644 index 000000000000..47c8cf4c2de5 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/refactoring/memberPushDown/NewSubClassData.java @@ -0,0 +1,38 @@ +/* + * Copyright 2000-2016 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.memberPushDown; + +import com.intellij.psi.PsiElement; + +public class NewSubClassData { + public static final NewSubClassData EMPTY = new NewSubClassData(null, null); + + private PsiElement myContext; + private String myNewClassName; + + public NewSubClassData(PsiElement context, String newClassName) { + myContext = context; + myNewClassName = newClassName; + } + + protected PsiElement getContext() { + return myContext; + } + + protected String getNewClassName() { + return myNewClassName; + } +} diff --git a/platform/lang-impl/src/com/intellij/refactoring/memberPushDown/PushDownData.java b/platform/lang-impl/src/com/intellij/refactoring/memberPushDown/PushDownData.java new file mode 100644 index 000000000000..df376a4d612b --- /dev/null +++ b/platform/lang-impl/src/com/intellij/refactoring/memberPushDown/PushDownData.java @@ -0,0 +1,50 @@ +/* + * Copyright 2000-2016 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.memberPushDown; + +import com.intellij.psi.PsiElement; +import com.intellij.refactoring.classMembers.MemberInfoBase; +import com.intellij.refactoring.util.DocCommentPolicy; + +public class PushDownData { + private PsiElement mySourceClass; + private final MemberInfoBase[] myMembersToMove; + private final DocCommentPolicy myCommentPolicy; + + public PushDownData(PsiElement sourceClass, + MemberInfoBase[] membersToMove, + DocCommentPolicy commentPolicy) { + mySourceClass = sourceClass; + myMembersToMove = membersToMove; + myCommentPolicy = commentPolicy; + } + + public PsiElement getSourceClass() { + return mySourceClass; + } + + public MemberInfoBase[] getMembersToMove() { + return myMembersToMove; + } + + public DocCommentPolicy getCommentPolicy() { + return myCommentPolicy; + } + + public void setSourceClass(PsiElement sourceClass) { + mySourceClass = sourceClass; + } +} diff --git a/platform/lang-impl/src/com/intellij/refactoring/memberPushDown/PushDownDelegate.java b/platform/lang-impl/src/com/intellij/refactoring/memberPushDown/PushDownDelegate.java new file mode 100644 index 000000000000..2296f204bfc8 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/refactoring/memberPushDown/PushDownDelegate.java @@ -0,0 +1,126 @@ +/* + * Copyright 2000-2016 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.memberPushDown; + +import com.intellij.lang.LanguageExtension; +import com.intellij.lang.findUsages.DescriptiveNameUtil; +import com.intellij.openapi.ui.Messages; +import com.intellij.psi.PsiElement; +import com.intellij.refactoring.RefactoringBundle; +import com.intellij.usageView.UsageInfo; +import com.intellij.util.containers.MultiMap; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public abstract class PushDownDelegate { + public static final LanguageExtension EP_NAME = new LanguageExtension("com.intellij.refactoring.pushDown"); + + @Nullable + protected static PushDownDelegate findDelegate(@NotNull PsiElement sourceClass) { + return EP_NAME.forLanguage(sourceClass.getLanguage()); + } + + @Nullable + protected static PushDownDelegate findDelegateForTarget(@NotNull PsiElement sourceClass, @NotNull PsiElement targetClass) { + for (PushDownDelegate delegate : EP_NAME.allForLanguage(targetClass.getLanguage())) { + if (delegate.isApplicableForSource(sourceClass)) { + return delegate; + } + } + return null; + } + + /** + * Check if delegate can process pushed members from the sourceClass. + * It is used to find appropriate delegate to process pushing from source class to target {@link #findDelegateForTarget(PsiElement, PsiElement)}. + * + * Implementations are supposed to override this method when overriding default behaviour for the language, + * e.g. pushing members from groovy class to java, groovy could provide additional delegate which inherits delegate for java and accepts groovy sources. + * Methods to process target class should be updated to cope with source of another language (e.g. calling super on PushDownData translated to java): + * {@link #checkTargetClassConflicts(PsiElement, PushDownData, boolean, PsiElement, MultiMap)}, + * {@link #pushDownToClass(PsiElement, PushDownData)} + */ + protected abstract boolean isApplicableForSource(@NotNull PsiElement sourceClass); + + /** + * Find classes to push members down. + */ + protected abstract List findInheritors(PushDownData pushDownData); + + protected UsageInfo createUsageInfo(PsiElement element) { + return new UsageInfo(element); + } + + /** + * Collect conflicts inside sourceClass assuming members would be removed, + * e.g. check if members remaining in source class do not depend on moved members + */ + protected abstract void checkSourceClassConflicts(PushDownData pushDownData, MultiMap conflicts); + + /** + * Collect conflicts inside targetClass assuming methods would be pushed, + * e.g. check if target class already has field with the same name, some references types + * won't be accessible anymore, etc + */ + protected abstract void checkTargetClassConflicts(PsiElement targetClass, + PushDownData pushDownData, + boolean checkStatic, + PsiElement context, + MultiMap conflicts); + + /** + * Could be used e.g. to encode mutual references between moved members + */ + protected void prepareToPush(PushDownData pushDownData) {} + + /** + * Push members to the target class adjusting visibility, comments according to the policy, etc + */ + protected abstract void pushDownToClass(PsiElement targetClass, PushDownData pushDownData); + + /** + * Remove members from the source class according to the abstract flag. + */ + protected abstract void removeFromSourceClass(PushDownData pushDownData); + + /** + * Called if no inheritors were found in {@link #findInheritors(PushDownData)}. Should warn that members would be deleted and + * suggest to create new target class if applicable + * + * @return NewSubClassData.EMPTY if refactoring should be aborted + * null to proceed without inheritors (members would be deleted from the source class and not added to the target) + * new NewSubClassData(context, name) if new inheritor should be created with {@link #createSubClass(PsiElement, NewSubClassData)} + */ + protected NewSubClassData preprocessNoInheritorsFound(PsiElement sourceClass, String conflictDialogTitle) { + final String message = RefactoringBundle.message("class.0.does.not.have.inheritors", DescriptiveNameUtil.getDescriptiveName(sourceClass)) + "\n" + + RefactoringBundle.message("push.down.will.delete.members"); + final int answer = Messages.showYesNoDialog(message, conflictDialogTitle, Messages.getWarningIcon()); + if (answer != Messages.YES) { + return NewSubClassData.EMPTY; + } + return null; + } + + /** + * Create sub class with subClassData.getNewClassName() in the specified context if no inheritors were found + */ + @Nullable + protected PsiElement createSubClass(PsiElement aClass, NewSubClassData subClassData) { + return null; + } +} diff --git a/platform/lang-impl/src/com/intellij/refactoring/memberPushDown/PushDownUsageViewDescriptor.java b/platform/lang-impl/src/com/intellij/refactoring/memberPushDown/PushDownUsageViewDescriptor.java index 6025b4275579..36e174957b76 100644 --- a/platform/lang-impl/src/com/intellij/refactoring/memberPushDown/PushDownUsageViewDescriptor.java +++ b/platform/lang-impl/src/com/intellij/refactoring/memberPushDown/PushDownUsageViewDescriptor.java @@ -25,7 +25,7 @@ import com.intellij.util.Function; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; -public class PushDownUsageViewDescriptor implements UsageViewDescriptor { +public class PushDownUsageViewDescriptor implements UsageViewDescriptor { private final PsiElement[] myMembers; private final String myProcessedElementsHeader; @@ -33,10 +33,10 @@ public class PushDownUsageViewDescriptor implements Us this(aClass, null); } - public PushDownUsageViewDescriptor(PsiElement aClass, M[] memberInfos) { - myMembers = memberInfos != null ? ContainerUtil.map(memberInfos, new Function() { + public PushDownUsageViewDescriptor(PsiElement aClass, MemberInfoBase[] memberInfos) { + myMembers = memberInfos != null ? ContainerUtil.map(memberInfos, new Function, PsiElement>() { @Override - public PsiElement fun(M info) { + public PsiElement fun(MemberInfoBase info) { return info.getMember(); } }, PsiElement.EMPTY_ARRAY) : new PsiElement[] {aClass}; diff --git a/platform/platform-resources-en/src/messages/RefactoringBundle.properties b/platform/platform-resources-en/src/messages/RefactoringBundle.properties index d4cd299f3079..5993bd3a04a1 100644 --- a/platform/platform-resources-en/src/messages/RefactoringBundle.properties +++ b/platform/platform-resources-en/src/messages/RefactoringBundle.properties @@ -784,7 +784,7 @@ rename.directory.command.name=Rename directory rename.directories.command.name=Rename directories target.destination.folder=Target &destination directory: move.method.is.not.supported.for.0=Move instance method is not supported for {0} -detecting.possible.conflicts=Detecting possible conflicts... +detecting.possible.conflicts=Detecting Possible Conflicts... path.completion.shortcut=Use {0} for path completion rename.project.action.title=Rename Project... diff --git a/platform/platform-resources/src/META-INF/LangExtensionPoints.xml b/platform/platform-resources/src/META-INF/LangExtensionPoints.xml index 4b597844c944..16301f8cb705 100644 --- a/platform/platform-resources/src/META-INF/LangExtensionPoints.xml +++ b/platform/platform-resources/src/META-INF/LangExtensionPoints.xml @@ -373,6 +373,9 @@ + + + diff --git a/resources/src/META-INF/IdeaPlugin.xml b/resources/src/META-INF/IdeaPlugin.xml index 112a44973957..ac9cb8fc667a 100644 --- a/resources/src/META-INF/IdeaPlugin.xml +++ b/resources/src/META-INF/IdeaPlugin.xml @@ -1724,6 +1724,7 @@ +