inline superclass refactoring: allow to collapse only one inheritance (IDEA-93319)

This commit is contained in:
Anna Kozlova
2013-06-14 18:19:52 +04:00
parent 1f9e8cbc6d
commit 2fd555eb23
15 changed files with 253 additions and 29 deletions

View File

@@ -81,6 +81,7 @@ public class JavaRefactoringSettings implements PersistentStateComponent<JavaRef
public int PULL_UP_MEMBERS_JAVADOC;
public boolean PUSH_DOWN_PREVIEW_USAGES;
public boolean INLINE_METHOD_THIS;
public boolean INLINE_SUPER_CLASS_THIS;
public boolean INLINE_FIELD_THIS;
//public boolean INHERITANCE_TO_DELEGATION_PREVIEW_USAGES;
public boolean INHERITANCE_TO_DELEGATION_DELEGATE_OTHER;

View File

@@ -24,22 +24,27 @@ import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiClass;
import com.intellij.refactoring.JavaRefactoringSettings;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.inline.InlineOptionsDialog;
import com.intellij.refactoring.ui.DocCommentPanel;
import com.intellij.refactoring.ui.RefactoringDialog;
import com.intellij.ui.IdeBorderFactory;
import com.intellij.util.Function;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import java.awt.*;
public class InlineSuperClassRefactoringDialog extends RefactoringDialog{
public class InlineSuperClassRefactoringDialog extends InlineOptionsDialog {
private final PsiClass mySuperClass;
private final PsiClass myCurrentInheritor;
private final PsiClass[] myTargetClasses;
private final DocCommentPanel myDocPanel;
protected InlineSuperClassRefactoringDialog(@NotNull Project project, PsiClass superClass, final PsiClass... targetClasses) {
super(project, false);
protected InlineSuperClassRefactoringDialog(@NotNull Project project, PsiClass superClass, PsiClass currentInheritor, final PsiClass... targetClasses) {
super(project, false, superClass);
mySuperClass = superClass;
myCurrentInheritor = currentInheritor;
myInvokedOnReference = currentInheritor != null;
myTargetClasses = targetClasses;
myDocPanel = new DocCommentPanel("JavaDoc for inlined members");
myDocPanel.setPolicy(JavaRefactoringSettings.getInstance().PULL_UP_MEMBERS_JAVADOC);
@@ -48,26 +53,64 @@ public class InlineSuperClassRefactoringDialog extends RefactoringDialog{
}
protected void doAction() {
invokeRefactoring(new InlineSuperClassRefactoringProcessor(getProject(), mySuperClass, myDocPanel.getPolicy(), myTargetClasses));
JavaRefactoringSettings settings = JavaRefactoringSettings.getInstance();
if(myRbInlineThisOnly.isEnabled() && myRbInlineAll.isEnabled()) {
settings.INLINE_SUPER_CLASS_THIS = isInlineThisOnly();
}
invokeRefactoring(new InlineSuperClassRefactoringProcessor(getProject(), isInlineThisOnly() ? myCurrentInheritor : null, mySuperClass, myDocPanel.getPolicy(), myTargetClasses));
}
@Override
protected JComponent createNorthPanel() {
return myDocPanel;
return null;
}
protected JComponent createCenterPanel() {
final JLabel label = new JLabel("<html>Inline \'" +
final JLabel label = new JLabel("<html>Super class \'" +
mySuperClass.getQualifiedName() +
"\' to " +
"\' inheritors: " +
(myTargetClasses.length > 1 ? " <br>&nbsp;&nbsp;&nbsp;\'" : "\'") +
StringUtil.join(myTargetClasses, new Function<PsiClass, String>() {
public String fun(final PsiClass psiClass) {
return psiClass.getQualifiedName();
}
}, "\',<br>&nbsp;&nbsp;&nbsp;\'") +
public String fun(final PsiClass psiClass) {
return psiClass.getQualifiedName();
}
}, "\',<br>&nbsp;&nbsp;&nbsp;\'") +
"\'</html>");
label.setBorder(IdeBorderFactory.createEmptyBorder(5, 5, 5, 5));
return label;
final JPanel panel = new JPanel(new GridBagLayout());
final GridBagConstraints gc =
new GridBagConstraints(0, GridBagConstraints.RELATIVE, 1, 1, 1, 0, GridBagConstraints.NORTHWEST, GridBagConstraints.HORIZONTAL,
new Insets(0, 0, 0, 0), 0, 0);
panel.add(myDocPanel, gc);
panel.add(label, gc);
gc.weighty = 1;
gc.fill = GridBagConstraints.BOTH;
panel.add(super.createCenterPanel(), gc);
return panel;
}
@Override
protected String getNameLabelText() {
return "Class " + mySuperClass.getQualifiedName();
}
@Override
protected String getBorderTitle() {
return "Inline";
}
@Override
protected String getInlineAllText() {
return RefactoringBundle.message("all.references.and.remove.super.class");
}
@Override
protected String getInlineThisText() {
return RefactoringBundle.message("this.reference.only.and.keep.super.class");
}
@Override
protected boolean isInlineThis() {
return JavaRefactoringSettings.getInstance().INLINE_SUPER_CLASS_THIS;
}
}

View File

@@ -20,12 +20,11 @@
*/
package com.intellij.refactoring.inlineSuperClass;
import com.intellij.codeInsight.TargetElementUtilBase;
import com.intellij.lang.StdLanguages;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiAnonymousClass;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiElement;
import com.intellij.psi.*;
import com.intellij.psi.search.searches.DirectClassInheritorsSearch;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.refactoring.inline.JavaInlineActionHandler;
@@ -67,6 +66,23 @@ public class InlineSuperClassRefactoringHandler extends JavaInlineActionHandler
}
}
new InlineSuperClassRefactoringDialog(project, superClass, inheritors.toArray(new PsiClass[inheritors.size()])).show();
PsiClass chosen = null;
PsiReference reference = editor != null ? TargetElementUtilBase.findReference(editor, editor.getCaretModel().getOffset()) : null;
if (reference != null) {
final PsiElement resolve = reference.resolve();
if (resolve == superClass) {
final PsiElement referenceElement = reference.getElement();
if (referenceElement != null) {
final PsiElement parent = referenceElement.getParent();
if (parent instanceof PsiReferenceList) {
final PsiElement gParent = parent.getParent();
if (gParent instanceof PsiClass && inheritors.contains(gParent)) {
chosen = (PsiClass)gParent;
}
}
}
}
}
new InlineSuperClassRefactoringDialog(project, superClass, chosen, inheritors.toArray(new PsiClass[inheritors.size()])).show();
}
}

View File

@@ -26,6 +26,7 @@ import com.intellij.openapi.util.Ref;
import com.intellij.psi.*;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiTypesUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.refactoring.inlineSuperClass.usageInfo.*;
@@ -39,11 +40,13 @@ import com.intellij.refactoring.util.classMembers.MemberInfo;
import com.intellij.refactoring.util.classMembers.MemberInfoStorage;
import com.intellij.usageView.UsageInfo;
import com.intellij.usageView.UsageViewDescriptor;
import com.intellij.util.ArrayUtilRt;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.Processor;
import com.intellij.util.containers.HashMap;
import com.intellij.util.containers.MultiMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Map;
@@ -51,16 +54,18 @@ import java.util.Map;
public class InlineSuperClassRefactoringProcessor extends FixableUsagesRefactoringProcessor {
public static final Logger LOG = Logger.getInstance("#" + InlineSuperClassRefactoringProcessor.class.getName());
private final PsiClass myCurrentInheritor;
private final PsiClass mySuperClass;
private final int myPolicy;
private final PsiClass[] myTargetClasses;
private final MemberInfo[] myMemberInfos;
public InlineSuperClassRefactoringProcessor(Project project, PsiClass superClass, int policy, final PsiClass... targetClasses) {
public InlineSuperClassRefactoringProcessor(Project project, PsiClass currentInheritor, PsiClass superClass, int policy, final PsiClass... targetClasses) {
super(project);
myCurrentInheritor = currentInheritor;
mySuperClass = superClass;
myPolicy = policy;
myTargetClasses = targetClasses;
myTargetClasses = currentInheritor != null ? new PsiClass[] {currentInheritor} : targetClasses;
MemberInfoStorage memberInfoStorage = new MemberInfoStorage(mySuperClass, new MemberInfo.Filter<PsiMember>() {
public boolean includeMember(PsiMember element) {
return !(element instanceof PsiClass) || PsiTreeUtil.isAncestor(mySuperClass, element, true);
@@ -88,6 +93,21 @@ public class InlineSuperClassRefactoringProcessor extends FixableUsagesRefactori
public boolean process(final PsiReference reference) {
final PsiElement element = reference.getElement();
if (element instanceof PsiJavaCodeReferenceElement) {
if (myCurrentInheritor != null) {
final PsiElement parent = element.getParent();
if (parent instanceof PsiReferenceList) {
final PsiElement pparent = parent.getParent();
if (pparent instanceof PsiClass) {
final PsiClass inheritor = (PsiClass)pparent;
if (parent.equals(inheritor.getExtendsList()) || parent.equals(inheritor.getImplementsList())) {
if (myCurrentInheritor.equals(inheritor)) {
usages.add(new ReplaceExtendsListUsageInfo((PsiJavaCodeReferenceElement)element, mySuperClass, inheritor));
}
}
}
}
return true;
}
final PsiImportStaticStatement staticImportStatement = PsiTreeUtil.getParentOfType(element, PsiImportStaticStatement.class);
if (staticImportStatement != null) {
usages.add(new ReplaceStaticImportUsageInfo(staticImportStatement, myTargetClasses));
@@ -204,10 +224,56 @@ public class InlineSuperClassRefactoringProcessor extends FixableUsagesRefactori
for (PsiElement element : conflictsMap.keySet()) {
conflicts.put(element, conflictsMap.get(element));
}
if (myCurrentInheritor != null) {
ReferencesSearch.search(myCurrentInheritor).forEach(new Processor<PsiReference>() {
@Override
public boolean process(PsiReference reference) {
final PsiElement element = reference.getElement();
if (element != null) {
final PsiElement parent = element.getParent();
if (parent instanceof PsiNewExpression) {
final PsiClass aClass = PsiUtil.resolveClassInType(getPlaceExpectedType(parent));
if (aClass == mySuperClass) {
conflicts.putValue(parent, "Instance of target type is passed to a place where super class is expected.");
return false;
}
}
}
return true;
}
});
}
checkConflicts(refUsages, conflicts);
return showConflicts(conflicts, refUsages.get());
}
@Nullable
private static PsiType getPlaceExpectedType(PsiElement parent) {
PsiType type = PsiTypesUtil.getExpectedTypeByParent((PsiExpression)parent);
if (type == null) {
final PsiElement arg = PsiUtil.skipParenthesizedExprUp(parent);
final PsiElement gParent = arg.getParent();
if (gParent instanceof PsiExpressionList) {
int i = ArrayUtilRt.find(((PsiExpressionList)gParent).getExpressions(), arg);
final PsiElement pParent = gParent.getParent();
if (pParent instanceof PsiCallExpression) {
final PsiMethod method = ((PsiCallExpression)pParent).resolveMethod();
if (method != null) {
final PsiParameter[] parameters = method.getParameterList().getParameters();
if (i >= parameters.length) {
if (method.isVarArgs()) {
return ((PsiEllipsisType)parameters[parameters.length - 1].getType()).getComponentType();
}
} else {
return parameters[i].getType();
}
}
}
}
}
return type;
}
protected void performRefactoring(final UsageInfo[] usages) {
new PushDownProcessor(mySuperClass.getProject(), myMemberInfos, mySuperClass, new DocCommentPolicy(myPolicy)) {
//push down conflicts are already collected
@@ -218,7 +284,12 @@ public class InlineSuperClassRefactoringProcessor extends FixableUsagesRefactori
@Override
protected void performRefactoring(UsageInfo[] pushDownUsages) {
super.performRefactoring(pushDownUsages);
if (myCurrentInheritor != null) {
encodeRefs();
pushDownToClass(myCurrentInheritor);
} else {
super.performRefactoring(pushDownUsages);
}
RefactoringUtil.sortDepthFirstRightLeftOrder(usages);
for (UsageInfo usageInfo : usages) {
if (!(usageInfo instanceof ReplaceExtendsListUsageInfo || usageInfo instanceof RemoveImportUsageInfo)) {
@@ -238,11 +309,13 @@ public class InlineSuperClassRefactoringProcessor extends FixableUsagesRefactori
((FixableUsageInfo)usage).fixUsage();
}
}
try {
mySuperClass.delete();
}
catch (IncorrectOperationException e) {
LOG.error(e);
if (myCurrentInheritor == null) {
try {
mySuperClass.delete();
}
catch (IncorrectOperationException e) {
LOG.error(e);
}
}
}
}.run();

View File

@@ -161,7 +161,7 @@ public class PushDownProcessor extends BaseRefactoringProcessor {
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");
private void encodeRefs() {
protected void encodeRefs() {
final Set<PsiMember> movedMembers = new HashSet<PsiMember>();
for (MemberInfo memberInfo : myMemberInfos) {
movedMembers.add(memberInfo.getMember());
@@ -328,7 +328,7 @@ public class PushDownProcessor extends BaseRefactoringProcessor {
}
}
private void pushDownToClass(PsiClass targetClass) throws IncorrectOperationException {
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) {

View File

@@ -0,0 +1,3 @@
abstract class Super {
public abstract void method() {}
}

View File

@@ -0,0 +1,16 @@
class Test {
public void context() {
method();
}
public void method() {}
}
class Test1 extends Super {
@Override
public void method() {}
}
class U {
Super t = new Test1();
}

View File

@@ -0,0 +1,3 @@
abstract class Super {
public abstract void method() {}
}

View File

@@ -0,0 +1,17 @@
class Test extends Super{
public void context() {
super.method();
}
@Override
public void method() {}
}
class Test1 extends Super {
@Override
public void method() {}
}
class U {
Super t = new Test1();
}

View File

@@ -0,0 +1,3 @@
abstract class Super {
public abstract void method() {}
}

View File

@@ -0,0 +1,15 @@
class Test {
public void context() {
method();
}
public void method() {}
}
class Test1 {
public void method() {}
}
class U {
Test t = new Test1();
}

View File

@@ -0,0 +1,3 @@
abstract class Super {
public abstract void method() {}
}

View File

@@ -0,0 +1,17 @@
class Test extends Super{
public void context() {
super.method();
}
@Override
public void method() {}
}
class Test1 extends Super {
@Override
public void method() {}
}
class U {
Super t = new Test();
}

View File

@@ -27,6 +27,10 @@ public class InlineSuperClassTest extends MultiFileTestCase {
}
private void doTest(final boolean fail) throws Exception {
doTest(fail, false);
}
private void doTest(final boolean fail, final boolean inlineOne) throws Exception {
try {
doTest(new PerformAction() {
@Override
@@ -41,7 +45,7 @@ public class InlineSuperClassTest extends MultiFileTestCase {
if (superClass == null) superClass = myJavaFacade.findClass("p1.Super", GlobalSearchScope.allScope(myProject));
assertNotNull("Class Super not found", superClass);
new InlineSuperClassRefactoringProcessor(getProject(), superClass, DocCommentPolicy.ASIS, aClass).run();
new InlineSuperClassRefactoringProcessor(getProject(), inlineOne ? aClass : null, superClass, DocCommentPolicy.ASIS, aClass).run();
//LocalFileSystem.getInstance().refresh(false);
//FileDocumentManager.getInstance().saveAllDocuments();
@@ -61,6 +65,14 @@ public class InlineSuperClassTest extends MultiFileTestCase {
}
}
public void testInlineOneClass() throws Exception {
doTest(false, true);
}
public void testInlineOneClassWithConflicts() throws Exception {
doTest(true, true);
}
public void testAbstractOverrides() throws Exception {
doTest();
}
@@ -184,7 +196,7 @@ public class InlineSuperClassTest extends MultiFileTestCase {
PsiClass superClass = myJavaFacade.findClass("Super", GlobalSearchScope.allScope(myProject));
if (superClass == null) superClass = myJavaFacade.findClass("p1.Super", GlobalSearchScope.allScope(myProject));
assertNotNull("Class Super not found", superClass);
new InlineSuperClassRefactoringProcessor(getProject(), superClass, DocCommentPolicy.ASIS,
new InlineSuperClassRefactoringProcessor(getProject(), null, superClass, DocCommentPolicy.ASIS,
myJavaFacade.findClass("Test", GlobalSearchScope.allScope(myProject)),
myJavaFacade.findClass("Test1", GlobalSearchScope.allScope(myProject))).run();
}

View File

@@ -426,6 +426,8 @@ inline.field.field.name.label=Field {0}
inline.field.border.title=Inline
all.references.and.remove.the.field=Inline &all references and remove the field
this.reference.only.and.keep.the.field=Inline this reference only and &keep the field
all.references.and.remove.super.class=Inline &all references and remove the class
this.reference.only.and.keep.super.class=Inline this reference only and &keep the super class
inline.variable.title=Inline Variable
variable.is.referenced.in.multiple.files=Variable {0} is referenced in multiple files
variable.is.never.used.before.modification=Variable {0} is never used before modification