From 3ea4e3a5b541e0e0e16dca5bf78613349a99a308 Mon Sep 17 00:00:00 2001 From: Artemiy Sartakov Date: Tue, 4 Aug 2020 12:26:46 +0700 Subject: [PATCH] InlineSuperClass: do not inline class with sealed parent as anonymous, fix permits list in case when sealed parent and child are both sealed (IDEA-243902) GitOrigin-RevId: 52ac1c93c8d9091b18172ec27a95c0eccc672915 --- .../inline/InlineToAnonymousClassHandler.java | 10 ++++++- .../InlineToAnonymousClassProcessor.java | 10 +++++++ .../InlineSuperClassRefactoringProcessor.java | 6 ++++- .../usageInfo/RemovePermitsListUsageInfo.java | 24 +++++++++++++++++ .../safeDelete/JavaSafeDeleteProcessor.java | 2 +- .../SafeDeletePermitsClassUsageInfo.java | 26 ++++++++++++++----- .../inlineToAnonymousClass/Sealed.java | 9 +++++++ .../inlineToAnonymousClass/Sealed.java.after | 7 +++++ .../SealedParentChildWithMembers.java | 11 ++++++++ .../inline/InlineToAnonymousClassTest.java | 10 +++++++ 10 files changed, 105 insertions(+), 10 deletions(-) create mode 100644 java/java-impl/src/com/intellij/refactoring/inlineSuperClass/usageInfo/RemovePermitsListUsageInfo.java create mode 100644 java/java-tests/testData/refactoring/inlineToAnonymousClass/Sealed.java create mode 100644 java/java-tests/testData/refactoring/inlineToAnonymousClass/Sealed.java.after create mode 100644 java/java-tests/testData/refactoring/inlineToAnonymousClass/SealedParentChildWithMembers.java diff --git a/java/java-impl/src/com/intellij/refactoring/inline/InlineToAnonymousClassHandler.java b/java/java-impl/src/com/intellij/refactoring/inline/InlineToAnonymousClassHandler.java index 71029fda0c7f..1160400698b9 100644 --- a/java/java-impl/src/com/intellij/refactoring/inline/InlineToAnonymousClassHandler.java +++ b/java/java-impl/src/com/intellij/refactoring/inline/InlineToAnonymousClassHandler.java @@ -39,6 +39,7 @@ import com.intellij.refactoring.util.InlineUtil; import com.intellij.refactoring.util.RefactoringUtil; import com.intellij.util.ArrayUtil; import com.intellij.util.Processor; +import com.siyeh.ig.psiutils.ClassUtils; import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -73,7 +74,9 @@ public class InlineToAnonymousClassHandler extends JavaInlineActionHandler { } if (!(element instanceof PsiClass)) return false; if (element instanceof PsiAnonymousClass) return false; - return findClassInheritors((PsiClass)element); + PsiClass psiClass = (PsiClass)element; + if (!findClassInheritors(psiClass)) return false; + return !isParentSealed(psiClass); } private static boolean findClassInheritors(final PsiClass element) { @@ -92,6 +95,11 @@ public class InlineToAnonymousClassHandler extends JavaInlineActionHandler { return inheritors.isEmpty(); } + private static boolean isParentSealed(@NotNull PsiClass psiClass) { + if (PsiTreeUtil.findChildOfType(psiClass, PsiMember.class) == null) return false; + return ClassUtils.hasSealedParent(psiClass); + } + @Override public boolean canInlineElementInEditor(PsiElement element, Editor editor) { if (canInlineElement(element)) { diff --git a/java/java-impl/src/com/intellij/refactoring/inline/InlineToAnonymousClassProcessor.java b/java/java-impl/src/com/intellij/refactoring/inline/InlineToAnonymousClassProcessor.java index c7fa206ff524..73871120f5cd 100644 --- a/java/java-impl/src/com/intellij/refactoring/inline/InlineToAnonymousClassProcessor.java +++ b/java/java-impl/src/com/intellij/refactoring/inline/InlineToAnonymousClassProcessor.java @@ -20,11 +20,14 @@ import com.intellij.usageView.UsageInfo; import com.intellij.usageView.UsageViewDescriptor; import com.intellij.util.IncorrectOperationException; import com.intellij.util.containers.MultiMap; +import com.siyeh.ig.psiutils.ClassUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.*; +import static com.intellij.util.ObjectUtils.tryCast; + /** * @author yole */ @@ -216,6 +219,13 @@ public class InlineToAnonymousClassProcessor extends BaseRefactoringProcessor { else if (element != null && element.getParent() instanceof PsiNewExpression) { newExpressions.add((PsiNewExpression) element.getParent()); } + else if (element instanceof PsiJavaCodeReferenceElement && element.getParent() instanceof PsiReferenceList) { + PsiReferenceList refList = (PsiReferenceList) element.getParent(); + PsiClass parentClass = tryCast(refList.getParent(), PsiClass.class); + if (parentClass != null && refList == parentClass.getPermitsList()) { + ClassUtils.removeFromPermitsList(parentClass, myClass); + } + } else { PsiImportStatement statement = PsiTreeUtil.getParentOfType(element, PsiImportStatement.class); if (statement != null && !myInlineThisOnly) { diff --git a/java/java-impl/src/com/intellij/refactoring/inlineSuperClass/InlineSuperClassRefactoringProcessor.java b/java/java-impl/src/com/intellij/refactoring/inlineSuperClass/InlineSuperClassRefactoringProcessor.java index 939810bf851e..a2670896b466 100644 --- a/java/java-impl/src/com/intellij/refactoring/inlineSuperClass/InlineSuperClassRefactoringProcessor.java +++ b/java/java-impl/src/com/intellij/refactoring/inlineSuperClass/InlineSuperClassRefactoringProcessor.java @@ -119,9 +119,13 @@ public class InlineSuperClassRefactoringProcessor extends FixableUsagesRefactori if (parent instanceof PsiReferenceList) { final PsiElement pparent = parent.getParent(); if (pparent instanceof PsiClass) { + final PsiJavaCodeReferenceElement classRef = (PsiJavaCodeReferenceElement)element; final PsiClass inheritor = (PsiClass)pparent; if (parent.equals(inheritor.getExtendsList()) || parent.equals(inheritor.getImplementsList())) { - usages.add(new ReplaceExtendsListUsageInfo((PsiJavaCodeReferenceElement)element, mySuperClass, inheritor)); + usages.add(new ReplaceExtendsListUsageInfo(classRef, mySuperClass, inheritor)); + } + else if (parent.equals(inheritor.getPermitsList())) { + usages.add(new RemovePermitsListUsageInfo(classRef, mySuperClass, inheritor)); } } } else { diff --git a/java/java-impl/src/com/intellij/refactoring/inlineSuperClass/usageInfo/RemovePermitsListUsageInfo.java b/java/java-impl/src/com/intellij/refactoring/inlineSuperClass/usageInfo/RemovePermitsListUsageInfo.java new file mode 100644 index 000000000000..fd470a5c63c7 --- /dev/null +++ b/java/java-impl/src/com/intellij/refactoring/inlineSuperClass/usageInfo/RemovePermitsListUsageInfo.java @@ -0,0 +1,24 @@ +// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.refactoring.inlineSuperClass.usageInfo; + +import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiJavaCodeReferenceElement; +import com.intellij.refactoring.safeDelete.usageInfo.SafeDeletePermitsClassUsageInfo; +import com.intellij.refactoring.util.FixableUsageInfo; +import com.intellij.util.IncorrectOperationException; + +public class RemovePermitsListUsageInfo extends FixableUsageInfo { + private final SafeDeletePermitsClassUsageInfo mySafeDeletePermitsClassUsageInfo; + + public RemovePermitsListUsageInfo(PsiJavaCodeReferenceElement reference, PsiClass refClass, PsiClass parentClass) { + super(reference); + mySafeDeletePermitsClassUsageInfo = new SafeDeletePermitsClassUsageInfo(reference, refClass, parentClass, false); + } + + @Override + public void fixUsage() throws IncorrectOperationException { + if (mySafeDeletePermitsClassUsageInfo.isSafeDelete()) { + mySafeDeletePermitsClassUsageInfo.deleteElement(); + } + } +} diff --git a/java/java-impl/src/com/intellij/refactoring/safeDelete/JavaSafeDeleteProcessor.java b/java/java-impl/src/com/intellij/refactoring/safeDelete/JavaSafeDeleteProcessor.java index 673bdba3c1d0..302f850c7a4c 100644 --- a/java/java-impl/src/com/intellij/refactoring/safeDelete/JavaSafeDeleteProcessor.java +++ b/java/java-impl/src/com/intellij/refactoring/safeDelete/JavaSafeDeleteProcessor.java @@ -575,7 +575,7 @@ public class JavaSafeDeleteProcessor extends SafeDeleteProcessorDelegateBase { PsiJavaCodeReferenceElement classRef = (PsiJavaCodeReferenceElement) element; final PsiClass inheritor = (PsiClass) pparent; if (parent.equals(inheritor.getPermitsList())) { - usages.add(new SafeDeletePermitsClassUsageInfo(classRef, psiClass, inheritor)); + usages.add(new SafeDeletePermitsClassUsageInfo(classRef, psiClass, inheritor, true)); return true; } //If psiClass contains only private members, then it is safe to remove it and change inheritor's extends/implements accordingly diff --git a/java/java-impl/src/com/intellij/refactoring/safeDelete/usageInfo/SafeDeletePermitsClassUsageInfo.java b/java/java-impl/src/com/intellij/refactoring/safeDelete/usageInfo/SafeDeletePermitsClassUsageInfo.java index 17fa2aa277a4..07a89adf0d8b 100644 --- a/java/java-impl/src/com/intellij/refactoring/safeDelete/usageInfo/SafeDeletePermitsClassUsageInfo.java +++ b/java/java-impl/src/com/intellij/refactoring/safeDelete/usageInfo/SafeDeletePermitsClassUsageInfo.java @@ -2,21 +2,24 @@ package com.intellij.refactoring.safeDelete.usageInfo; import com.intellij.psi.PsiClass; -import com.intellij.psi.PsiClassType; import com.intellij.psi.PsiJavaCodeReferenceElement; import com.intellij.psi.PsiReferenceList; import com.intellij.util.IncorrectOperationException; import com.intellij.util.containers.ContainerUtil; import com.siyeh.ig.psiutils.ClassUtils; +import org.jetbrains.annotations.Nullable; import java.util.Objects; public class SafeDeletePermitsClassUsageInfo extends SafeDeleteReferenceUsageInfo { private final PsiClass myParentClass; + private final boolean myChangeParentModifier; - public SafeDeletePermitsClassUsageInfo(final PsiJavaCodeReferenceElement reference, PsiClass refClass, PsiClass parentClass) { + public SafeDeletePermitsClassUsageInfo(PsiJavaCodeReferenceElement reference, PsiClass refClass, + PsiClass parentClass, boolean changeParentModifier) { super(reference, refClass, true); myParentClass = parentClass; + myChangeParentModifier = changeParentModifier; } @Override @@ -27,17 +30,26 @@ public class SafeDeletePermitsClassUsageInfo extends SafeDeleteReferenceUsageInf @Override public void deleteElement() throws IncorrectOperationException { final PsiClass refClass = getReferencedElement(); - ClassUtils.removeFromPermitsList(myParentClass, refClass); + if (myChangeParentModifier) { + ClassUtils.removeFromPermitsList(myParentClass, refClass); + } + else { + PsiJavaCodeReferenceElement exChildRef = findReference(); + if (exChildRef != null) exChildRef.delete(); + } } @Override public boolean isSafeDelete() { - if (getElement() != null) return true; + return getElement() != null && findReference() != null; + } + + private @Nullable PsiJavaCodeReferenceElement findReference() { PsiReferenceList permitsList = myParentClass.getPermitsList(); - if (permitsList == null) return false; + if (permitsList == null) return null; PsiJavaCodeReferenceElement[] childRefs = permitsList.getReferenceElements(); - if (childRefs.length < 1) return false; - return ContainerUtil.exists(childRefs, ref -> ref.resolve() == getReferencedElement()); + if (childRefs.length < 1) return null; + return ContainerUtil.find(childRefs, ref -> ref.resolve() == getReferencedElement()); } @Override diff --git a/java/java-tests/testData/refactoring/inlineToAnonymousClass/Sealed.java b/java/java-tests/testData/refactoring/inlineToAnonymousClass/Sealed.java new file mode 100644 index 000000000000..d5bc39d84be4 --- /dev/null +++ b/java/java-tests/testData/refactoring/inlineToAnonymousClass/Sealed.java @@ -0,0 +1,9 @@ +sealed class Parent permits Parent.A { + + void test() { + Object obj = new A(); + } + + static final class A extends Parent {} + +} \ No newline at end of file diff --git a/java/java-tests/testData/refactoring/inlineToAnonymousClass/Sealed.java.after b/java/java-tests/testData/refactoring/inlineToAnonymousClass/Sealed.java.after new file mode 100644 index 000000000000..0c1c5f6497e6 --- /dev/null +++ b/java/java-tests/testData/refactoring/inlineToAnonymousClass/Sealed.java.after @@ -0,0 +1,7 @@ +final class Parent { + + void test() { + Object obj = new Parent(); + } + +} \ No newline at end of file diff --git a/java/java-tests/testData/refactoring/inlineToAnonymousClass/SealedParentChildWithMembers.java b/java/java-tests/testData/refactoring/inlineToAnonymousClass/SealedParentChildWithMembers.java new file mode 100644 index 000000000000..2fc76d1295dd --- /dev/null +++ b/java/java-tests/testData/refactoring/inlineToAnonymousClass/SealedParentChildWithMembers.java @@ -0,0 +1,11 @@ +sealed class Parent { + + void test() { + Object obj = new A(); + } + + static final class A extends Parent { + void foo() {} + } + +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/java/refactoring/inline/InlineToAnonymousClassTest.java b/java/java-tests/testSrc/com/intellij/java/refactoring/inline/InlineToAnonymousClassTest.java index e7bca6986b40..dc239c7ec95c 100644 --- a/java/java-tests/testSrc/com/intellij/java/refactoring/inline/InlineToAnonymousClassTest.java +++ b/java/java-tests/testSrc/com/intellij/java/refactoring/inline/InlineToAnonymousClassTest.java @@ -271,6 +271,16 @@ public class InlineToAnonymousClassTest extends LightRefactoringTestCase { doTest(true, false); } + public void testSealed() { + setLanguageLevel(LanguageLevel.JDK_15_PREVIEW); + doTest(false, false); + } + + public void testSealedParentChildWithMembers() { + setLanguageLevel(LanguageLevel.JDK_15_PREVIEW); + doTestCanBeInvokedOnReference(false); + } + public void testNoInlineAbstract() { doTestNoInline("Abstract classes cannot be inlined"); }