blob: bb09c69c9fcc0db5723ea2d65c3e417f5c54e7fe [file] [log] [blame]
/*
* Copyright 2000-2014 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.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.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 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,
MemberInfo[] memberInfos,
PsiClass aClass,
DocCommentPolicy javaDocPolicy) {
super(project);
myMemberInfos = memberInfos;
myClass = aClass;
myJavaDocPolicy = javaDocPolicy;
}
@Override
protected String getCommandName() {
return JavaPushDownHandler.REFACTORING_NAME;
}
@Override
@NotNull
protected UsageViewDescriptor createUsageViewDescriptor(UsageInfo[] usages) {
return new PushDownUsageViewDescriptor(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(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(final Ref<UsageInfo[]> refUsages) {
final UsageInfo[] usagesIn = refUsages.get();
final PushDownConflicts pushDownConflicts = new PushDownConflicts(myClass, myMemberInfos);
pushDownConflicts.checkSourceClassConflicts();
if (usagesIn.length == 0) {
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;
}
}
Runnable runnable = new Runnable() {
@Override
public void run() {
ApplicationManager.getApplication().runReadAction(new Runnable() {
@Override
public void run() {
for (UsageInfo usage : usagesIn) {
final PsiElement element = usage.getElement();
if (element instanceof PsiClass) {
pushDownConflicts.checkTargetClassConflicts((PsiClass)element, usagesIn.length > 1, element);
}
}
}
});
}
};
if (!ProgressManager.getInstance().runProcessWithProgressSynchronously(runnable, RefactoringBundle.message("detecting.possible.conflicts"), true, myProject)) {
return false;
}
for (UsageInfo info : usagesIn) {
final PsiElement element = info.getElement();
if (element instanceof PsiFunctionalExpression) {
pushDownConflicts.getConflicts().putValue(element, RefactoringBundle.message("functional.interface.broken"));
}
}
final PsiAnnotation annotation = AnnotationUtil.findAnnotation(myClass, CommonClassNames.JAVA_LANG_FUNCTIONAL_INTERFACE);
if (annotation != null && isMoved(LambdaUtil.getFunctionalInterfaceMethod(myClass))) {
pushDownConflicts.getConflicts().putValue(annotation, RefactoringBundle.message("functional.interface.broken"));
}
return showConflicts(pushDownConflicts.getConflicts(), usagesIn);
}
@Override
protected void refreshElements(PsiElement[] elements) {
if(elements.length == 1 && elements[0] instanceof PsiClass) {
myClass = (PsiClass) elements[0];
}
else {
LOG.assertTrue(false);
}
}
@Override
protected void performRefactoring(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) {
try {
ChangeContextUtil.decodeContextInfo(member, null, null);
}
catch (IncorrectOperationException e) {
LOG.error(e);
}
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());
}
}
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, new Comparator<PsiReference>() {
@Override
public int compare(PsiReference o1, PsiReference o2) {
return PsiUtil.BY_POSITION.compare(o1.getElement(), o2.getElement());
}
});
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;
}
}