push down delegate to allow inter language pushes (inspired by java -> kotlin)

This commit is contained in:
Anna Kozlova
2016-02-23 20:20:03 +01:00
parent 5fb9e445ce
commit 91f1d0ef0e
15 changed files with 868 additions and 534 deletions
@@ -213,7 +213,7 @@ public class InlineSuperClassRefactoringProcessor extends FixableUsagesRefactori
@Override
protected boolean preprocessUsages(@NotNull final Ref<UsageInfo[]> refUsages) {
final MultiMap<PsiElement, String> conflicts = new MultiMap<PsiElement, String>();
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<PsiElement, String> conflictsMap = pushDownConflicts.getConflicts();
for (PsiElement element : conflictsMap.keySet()) {
conflicts.put(element, conflictsMap.get(element));
}
if (myCurrentInheritor != null) {
ReferencesSearch.search(myCurrentInheritor).forEach(new Processor<PsiReference>() {
@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<PsiElement, String> 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);
}
@@ -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<Boolean> REMOVE_QUALIFIER_KEY = Key.create("REMOVE_QUALIFIER_KEY");
public static final Key<PsiClass> 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<PsiElement> findInheritors(PushDownData pushDownData) {
final List<PsiElement> 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<? extends PsiElement> 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<PsiElement, String> conflicts) {
new PushDownConflicts((PsiClass)pushDownData.getSourceClass(), (MemberInfo[])pushDownData.getMembersToMove(), conflicts).checkSourceClassConflicts();
}
@Override
public void checkTargetClassConflicts(PsiElement targetClass,
PushDownData pushDownData,
boolean checkStatic,
PsiElement context,
MultiMap<PsiElement, String> 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<PsiMember> movedMembers = new HashSet<PsiMember>();
for (MemberInfoBase<? extends PsiElement> memberInfo : pushDownData.getMembersToMove()) {
movedMembers.add((PsiMember)memberInfo.getMember());
}
for (MemberInfoBase<? extends PsiElement> 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<? extends PsiElement> memberInfo : pushDownData.getMembersToMove()) {
PsiMember member = (PsiMember)memberInfo.getMember();
final List<PsiReference> refsToRebind = new ArrayList<PsiReference>();
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<? extends PsiElement> 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<PsiMember> 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);
}
}
}
@@ -37,7 +37,7 @@ public class PushDownConflicts {
private final MultiMap<PsiElement, String> myConflicts;
public PushDownConflicts(PsiClass aClass, MemberInfo[] memberInfos) {
public PushDownConflicts(PsiClass aClass, MemberInfo[] memberInfos, MultiMap<PsiElement, String> conflicts) {
myClass = aClass;
myMovedMembers = new HashSet<PsiMember>();
@@ -52,7 +52,7 @@ public class PushDownConflicts {
}
}
myConflicts = new MultiMap<PsiElement, String>();
myConflicts = conflicts;
}
public boolean isAnyConflicts() {
@@ -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<PsiMember, PsiClass, MemberInfo> {
@@ -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<MemberInfo>(myClass, myMemberInfos);
}
@NotNull
@Override
protected Collection<? extends PsiElement> 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<MemberInfo, PsiElement>() {
@Override
public PsiElement fun(MemberInfo info) {
return info.getMember();
}
});
return data;
}
@Nullable
@Override
protected RefactoringEventData getAfterData(@NotNull UsageInfo[] usages) {
final List<PsiElement> elements = new ArrayList<PsiElement>();
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<UsageInfo> usages = new ArrayList<UsageInfo>(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<PsiFunctionalExpression>() {
@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<UsageInfo[]> 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<Boolean> REMOVE_QUALIFIER_KEY = Key.create("REMOVE_QUALIFIER_KEY");
private static final Key<PsiClass> REPLACE_QUALIFIER_KEY = Key.create("REPLACE_QUALIFIER_KEY");
protected void encodeRefs() {
final Set<PsiMember> movedMembers = new HashSet<PsiMember>();
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<PsiMember> 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<PsiReference> refsToRebind = new ArrayList<PsiReference>();
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;
}
}
@@ -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);
@@ -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<PsiElement, String> 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<PsiElement, String> conflicts, UsageInfo[] usages) {
@@ -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<? extends PsiElement>[] 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<? extends PsiElement> 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<MemberInfoBase<? extends PsiElement>, PsiElement>() {
@Override
public PsiElement fun(MemberInfoBase<? extends PsiElement> info) {
return info.getMember();
}
});
return data;
}
@Nullable
@Override
protected RefactoringEventData getAfterData(@NotNull UsageInfo[] usages) {
final List<PsiElement> elements = new ArrayList<PsiElement>();
for (UsageInfo usage : usages) {
elements.add(usage.getElement());
}
RefactoringEventData data = new RefactoringEventData();
data.addElements(elements);
return data;
}
@Override
@NotNull
protected UsageInfo[] findUsages() {
final List<PsiElement> inheritors = myDelegate.findInheritors(myPushDownData);
return ContainerUtil.map2Array(inheritors, UsageInfo.EMPTY_ARRAY, myDelegate::createUsageInfo);
}
@Override
protected boolean preprocessUsages(@NotNull final Ref<UsageInfo[]> refUsages) {
final MultiMap<PsiElement, String> 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);
}
}
@@ -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;
}
}
@@ -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<? extends PsiElement>[] myMembersToMove;
private final DocCommentPolicy myCommentPolicy;
public PushDownData(PsiElement sourceClass,
MemberInfoBase<? extends PsiElement>[] membersToMove,
DocCommentPolicy commentPolicy) {
mySourceClass = sourceClass;
myMembersToMove = membersToMove;
myCommentPolicy = commentPolicy;
}
public PsiElement getSourceClass() {
return mySourceClass;
}
public MemberInfoBase<? extends PsiElement>[] getMembersToMove() {
return myMembersToMove;
}
public DocCommentPolicy getCommentPolicy() {
return myCommentPolicy;
}
public void setSourceClass(PsiElement sourceClass) {
mySourceClass = sourceClass;
}
}
@@ -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<PushDownDelegate> EP_NAME = new LanguageExtension<PushDownDelegate>("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<PsiElement> 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<PsiElement, String> 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<PsiElement, String> 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 <code>subClassData.getNewClassName()</code> in the specified context if no inheritors were found
*/
@Nullable
protected PsiElement createSubClass(PsiElement aClass, NewSubClassData subClassData) {
return null;
}
}
@@ -25,7 +25,7 @@ import com.intellij.util.Function;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
public class PushDownUsageViewDescriptor<M extends MemberInfoBase> implements UsageViewDescriptor {
public class PushDownUsageViewDescriptor implements UsageViewDescriptor {
private final PsiElement[] myMembers;
private final String myProcessedElementsHeader;
@@ -33,10 +33,10 @@ public class PushDownUsageViewDescriptor<M extends MemberInfoBase> implements Us
this(aClass, null);
}
public PushDownUsageViewDescriptor(PsiElement aClass, M[] memberInfos) {
myMembers = memberInfos != null ? ContainerUtil.map(memberInfos, new Function<M, PsiElement>() {
public PushDownUsageViewDescriptor(PsiElement aClass, MemberInfoBase<? extends PsiElement>[] memberInfos) {
myMembers = memberInfos != null ? ContainerUtil.map(memberInfos, new Function<MemberInfoBase<? extends PsiElement>, PsiElement>() {
@Override
public PsiElement fun(M info) {
public PsiElement fun(MemberInfoBase<? extends PsiElement> info) {
return info.getMember();
}
}, PsiElement.EMPTY_ARRAY) : new PsiElement[] {aClass};
@@ -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...
@@ -373,6 +373,9 @@
<extensionPoint name="refactoring.copyHandler" interface="com.intellij.refactoring.copy.CopyHandlerDelegate"/>
<extensionPoint name="refactoring.moveHandler" interface="com.intellij.refactoring.move.MoveHandlerDelegate"/>
<extensionPoint name="refactoring.invertBoolean" interface="com.intellij.refactoring.invertBoolean.InvertBooleanDelegate"/>
<extensionPoint name="refactoring.pushDown" beanClass="com.intellij.lang.LanguageExtensionPoint">
<with attribute="implementationClass" implements="com.intellij.refactoring.memberPushDown.PushDownDelegate"/>
</extensionPoint>
<extensionPoint name="refactoring.moveDirectoryWithClassesHelper"
interface="com.intellij.refactoring.move.moveClassesOrPackages.MoveDirectoryWithClassesHelper"/>
+1
View File
@@ -1724,6 +1724,7 @@
<refactoring.moveInnerClassUsagesHandler language="JAVA" implementationClass="com.intellij.refactoring.move.moveInner.MoveInnerClassJavaUsagesHandler" id="java"/>
<actionPromoter implementation="com.intellij.execution.testframework.TestTreeViewActionsPromoter"/>
<refactoring.invertBoolean implementation="com.intellij.refactoring.invertBoolean.JavaInvertBooleanDelegate"/>
<refactoring.pushDown language="JAVA" implementationClass="com.intellij.refactoring.memberPushDown.JavaPushDownDelegate" id="java"/>
<library.javaSourceRootDetector implementation="com.intellij.openapi.roots.ui.configuration.JavaSourceRootDetector"/>
</extensions>