push down: add option to create a subclass if a class has no inheriters (IDEA-46563)

This commit is contained in:
anna
2010-01-18 14:07:45 +03:00
parent dc44613c33
commit bfa671478e
4 changed files with 67 additions and 37 deletions
@@ -37,7 +37,6 @@ import com.intellij.openapi.fileEditor.ex.IdeDocumentHistory;
import com.intellij.openapi.module.ModuleUtil;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.PostprocessReformattingAspect;
@@ -87,50 +86,61 @@ public class CreateSubclassAction extends PsiElementBaseIntentionAction {
TextRange declarationRange = HighlightNamesUtil.getClassDeclarationTextRange(psiClass);
if (!declarationRange.contains(element.getTextRange())) return false;
myText = psiClass.isInterface()
myText = getTitle(psiClass);
return true;
}
private static String getTitle(PsiClass psiClass) {
return psiClass.isInterface()
? CodeInsightBundle.message("intention.implement.abstract.class.interface.text")
: psiClass.hasModifierProperty(PsiModifier.ABSTRACT)
? CodeInsightBundle.message("intention.implement.abstract.class.default.text")
: CodeInsightBundle.message("intention.implement.abstract.class.subclass.text");
return true;
}
public void invoke(@NotNull final Project project, Editor editor, final PsiFile file) throws IncorrectOperationException {
PsiElement element = file.findElementAt(editor.getCaretModel().getOffset());
final PsiClass psiClass = PsiTreeUtil.getParentOfType(element, PsiClass.class);
PsiDirectory sourceDir = file.getContainingDirectory();
final CreateClassDialog dlg = chooseSubclassToCreate(psiClass);
if (dlg != null) {
createSubclass(psiClass, dlg.getTargetDirectory(), dlg.getClassName());
}
}
@Nullable
public static CreateClassDialog chooseSubclassToCreate(PsiClass psiClass) {
PsiDirectory sourceDir = psiClass.getContainingFile().getContainingDirectory();
final PsiPackage aPackage = JavaDirectoryService.getInstance().getPackage(sourceDir);
final CreateClassDialog dialog = new CreateClassDialog(
project,
myText,
psiClass.getProject(), getTitle(psiClass),
psiClass.getName() + IMPL_SUFFIX,
aPackage != null ? aPackage.getQualifiedName() : "",
CreateClassKind.CLASS, true, ModuleUtil.findModuleForPsiElement(file));
CreateClassKind.CLASS, true, ModuleUtil.findModuleForPsiElement(psiClass));
dialog.show();
if (!dialog.isOK()) return;
if (!dialog.isOK()) return null;
final PsiDirectory targetDirectory = dialog.getTargetDirectory();
if (targetDirectory == null) return;
createSubclass(psiClass, targetDirectory, dialog.getClassName());
if (targetDirectory == null) return null;
return dialog;
}
public static void createSubclass(final PsiClass psiClass, final PsiDirectory targetDirectory, final String className) {
public static PsiClass createSubclass(final PsiClass psiClass, final PsiDirectory targetDirectory, final String className) {
final Project project = psiClass.getProject();
final PsiClass[] targetClass = new PsiClass[1];
PostprocessReformattingAspect.getInstance(project).postponeFormattingInside(new Runnable () {
public void run() {
PsiClass targetClass = ApplicationManager.getApplication().runWriteAction(new Computable<PsiClass>() {
public PsiClass compute() {
ApplicationManager.getApplication().runWriteAction(new Runnable() {
public void run() {
IdeDocumentHistory.getInstance(project).includeCurrentPlaceAsChangePlace();
final PsiTypeParameterList oldTypeParameterList = psiClass.getTypeParameterList();
PsiClass targetClass;
try {
targetClass = JavaDirectoryService.getInstance().createClass(targetDirectory, className);
targetClass[0] = JavaDirectoryService.getInstance().createClass(targetDirectory, className);
if (psiClass.hasTypeParameters()) {
final PsiTypeParameterList typeParameterList = targetClass.getTypeParameterList();
final PsiTypeParameterList typeParameterList = targetClass[0].getTypeParameterList();
assert typeParameterList != null;
typeParameterList.replace(oldTypeParameterList);
}
@@ -143,16 +153,16 @@ public class CreateSubclassAction extends PsiElementBaseIntentionAction {
CodeInsightBundle.message("intention.error.cannot.create.class.title"));
}
});
return null;
return;
}
final PsiElementFactory elementFactory = JavaPsiFacade.getInstance(project).getElementFactory();
PsiJavaCodeReferenceElement ref = elementFactory.createClassReferenceElement(psiClass);
try {
if (psiClass.isInterface()) {
ref = (PsiJavaCodeReferenceElement)targetClass.getImplementsList().add(ref);
ref = (PsiJavaCodeReferenceElement)targetClass[0].getImplementsList().add(ref);
}
else {
ref = (PsiJavaCodeReferenceElement)targetClass.getExtendsList().add(ref);
ref = (PsiJavaCodeReferenceElement)targetClass[0].getExtendsList().add(ref);
}
if (oldTypeParameterList != null) {
@@ -164,19 +174,18 @@ public class CreateSubclassAction extends PsiElementBaseIntentionAction {
catch (IncorrectOperationException e) {
LOG.error(e);
}
return targetClass;
}
});
if (targetClass == null) return;
if (targetClass[0] == null) return;
if (!ApplicationManager.getApplication().isUnitTestMode()) {
final Editor editor1 = CodeInsightUtil.positionCursor(project, targetClass.getContainingFile(), targetClass.getLBrace());
final Editor editor1 = CodeInsightUtil.positionCursor(project, targetClass[0].getContainingFile(), targetClass[0].getLBrace());
if (editor1 == null) return;
OverrideImplementUtil.chooseAndImplementMethods(project, editor1, targetClass);
OverrideImplementUtil.chooseAndImplementMethods(project, editor1, targetClass[0]);
}
}
});
return targetClass[0];
}
public boolean startInWriteAction() {
@@ -32,7 +32,7 @@ import java.util.Set;
public class PushDownConflicts {
private final PsiClass myClass;
private final Set<PsiMember> myMovedMembers;
private final Set<PsiMember> myAbstractMembers;
private final Set<PsiMethod> myAbstractMembers;
private final MultiMap<PsiElement, String> myConflicts;
@@ -40,13 +40,13 @@ public class PushDownConflicts {
myClass = aClass;
myMovedMembers = new HashSet<PsiMember>();
myAbstractMembers = new HashSet<PsiMember>();
myAbstractMembers = new HashSet<PsiMethod>();
for (MemberInfo memberInfo : memberInfos) {
final PsiMember member = memberInfo.getMember();
if (memberInfo.isChecked() && (!(memberInfo.getMember() instanceof PsiClass) || memberInfo.getOverrides() == null)) {
myMovedMembers.add(member);
if (memberInfo.isToAbstract()) {
myAbstractMembers.add(member);
myAbstractMembers.add((PsiMethod)member);
}
}
}
@@ -71,9 +71,11 @@ public class PushDownConflicts {
}
}
public void checkTargetClassConflicts(PsiClass targetClass, boolean checkStatic) {
for (final PsiMember movedMember : myMovedMembers) {
checkMemberPlacementInTargetClassConflict(targetClass, movedMember);
public void checkTargetClassConflicts(PsiClass targetClass, boolean checkStatic, PsiElement context) {
if (targetClass != null) {
for (final PsiMember movedMember : myMovedMembers) {
checkMemberPlacementInTargetClassConflict(targetClass, movedMember);
}
}
Members:
for (PsiMember member : myMovedMembers) {
@@ -106,7 +108,7 @@ public class PushDownConflicts {
}
}
}
RefactoringConflictsUtil.analyzeAccessibilityConflicts(myMovedMembers, targetClass, myConflicts, null);
RefactoringConflictsUtil.analyzeAccessibilityConflicts(myMovedMembers, targetClass, myConflicts, null, context, myAbstractMembers);
}
public void checkMemberPlacementInTargetClassConflict(final PsiClass targetClass, final PsiMember movedMember) {
@@ -17,8 +17,11 @@ 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.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.Ref;
@@ -49,6 +52,7 @@ public class PushDownProcessor extends BaseRefactoringProcessor {
private final MemberInfo[] myMemberInfos;
private PsiClass myClass;
private final DocCommentPolicy myJavaDocPolicy;
private CreateClassDialog myCreateClassDlg;
public PushDownProcessor(Project project,
MemberInfo[] memberInfos,
@@ -78,7 +82,7 @@ public class PushDownProcessor extends BaseRefactoringProcessor {
return usages;
}
protected boolean preprocessUsages(Ref<UsageInfo[]> refUsages) {
protected boolean preprocessUsages(final Ref<UsageInfo[]> refUsages) {
UsageInfo[] usagesIn = refUsages.get();
final PushDownConflicts pushDownConflicts = new PushDownConflicts(myClass, myMemberInfos);
pushDownConflicts.checkSourceClassConflicts();
@@ -88,13 +92,21 @@ public class PushDownProcessor extends BaseRefactoringProcessor {
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.showYesNoDialog(message, JavaPushDownHandler.REFACTORING_NAME, Messages.getWarningIcon());
if (answer != 0) return false;
final int answer = Messages.showYesNoCancelDialog(message, JavaPushDownHandler.REFACTORING_NAME, Messages.getWarningIcon());
if (answer == DialogWrapper.OK_EXIT_CODE) {
myCreateClassDlg = CreateSubclassAction.chooseSubclassToCreate(myClass);
if (myCreateClassDlg != null) {
pushDownConflicts.checkTargetClassConflicts(null, false, myCreateClassDlg.getTargetDirectory());
return showConflicts(pushDownConflicts.getConflicts());
} else {
return false;
}
} else if (answer != 1) return false;
}
for (UsageInfo usage : usagesIn) {
final PsiElement element = usage.getElement();
if (element instanceof PsiClass) {
pushDownConflicts.checkTargetClassConflicts((PsiClass)element, usagesIn.length > 1);
pushDownConflicts.checkTargetClassConflicts((PsiClass)element, usagesIn.length > 1, element);
}
}
@@ -113,6 +125,13 @@ public class PushDownProcessor extends BaseRefactoringProcessor {
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();
@@ -472,7 +472,7 @@ push.down.javadoc.panel.title=JavaDoc for abstracts
push.down.members.elements.header=Push down members from
interface.0.does.not.have.inheritors=Interface {0} does not have inheritors.
class.0.does.not.have.inheritors=Class {0} does not have inheritors.
push.down.will.delete.members=Pushing members down will result in them being deleted. Continue?
push.down.will.delete.members=Pushing members down will result in them being deleted. Would you like to create a new subclass?
edit.migration.map.title=Edit Migration Map
migration.map.name.prompt=Map name:
migration.map.description.label=Map description: