From c96285a01ceb9f58b5e2bf9857d05a21a0834e00 Mon Sep 17 00:00:00 2001 From: batya239 Date: Thu, 25 Apr 2013 14:03:43 +0400 Subject: [PATCH] quick fix for find super that has frequently used inheritors --- .../inheritance/ChangeSuperClassFix.java | 157 ++++++++++++++++++ ...HasFrequentlyUsedInheritorsInspection.java | 113 +++++++++++++ .../search/InheritorsCountData.java | 54 ++++++ .../InheritorsStatisticalDataSearch.java | 133 +++++++++++++++ .../InheritorsStatisticsSearchResult.java | 28 ++++ .../smartInheritance/AbstractClass.java | 12 ++ .../smartInheritance/AnonymousClasses.java | 17 ++ .../AnonymousClassesInStats.java | 22 +++ .../smartInheritance/InterfacesNotShowed.java | 22 +++ .../InterfacesNotShowed2.java | 25 +++ .../NoCompletionForAbstractClasses.java | 25 +++ .../NoMoreThanMaxCountIntentions.java | 37 +++++ .../smartInheritance/RelevantClassShowed.java | 33 ++++ ...requentlyUsedInheritorsInspectionTest.java | 116 +++++++++++++ ...SuperClassHasFrequentlyUsedInheritors.html | 20 +++ resources/src/META-INF/IdeaPlugin.xml | 5 +- 16 files changed, 818 insertions(+), 1 deletion(-) create mode 100644 java/java-impl/src/com/intellij/codeInspection/inheritance/ChangeSuperClassFix.java create mode 100644 java/java-impl/src/com/intellij/codeInspection/inheritance/SuperClassHasFrequentlyUsedInheritorsInspection.java create mode 100644 java/java-impl/src/com/intellij/codeInspection/inheritance/search/InheritorsCountData.java create mode 100644 java/java-impl/src/com/intellij/codeInspection/inheritance/search/InheritorsStatisticalDataSearch.java create mode 100644 java/java-impl/src/com/intellij/codeInspection/inheritance/search/InheritorsStatisticsSearchResult.java create mode 100644 java/java-tests/testData/inspection/smartInheritance/AbstractClass.java create mode 100644 java/java-tests/testData/inspection/smartInheritance/AnonymousClasses.java create mode 100644 java/java-tests/testData/inspection/smartInheritance/AnonymousClassesInStats.java create mode 100644 java/java-tests/testData/inspection/smartInheritance/InterfacesNotShowed.java create mode 100644 java/java-tests/testData/inspection/smartInheritance/InterfacesNotShowed2.java create mode 100644 java/java-tests/testData/inspection/smartInheritance/NoCompletionForAbstractClasses.java create mode 100644 java/java-tests/testData/inspection/smartInheritance/NoMoreThanMaxCountIntentions.java create mode 100644 java/java-tests/testData/inspection/smartInheritance/RelevantClassShowed.java create mode 100644 java/java-tests/testSrc/com/intellij/codeInspection/SuperClassHasFrequentlyUsedInheritorsInspectionTest.java create mode 100644 resources-en/src/inspectionDescriptions/SuperClassHasFrequentlyUsedInheritors.html diff --git a/java/java-impl/src/com/intellij/codeInspection/inheritance/ChangeSuperClassFix.java b/java/java-impl/src/com/intellij/codeInspection/inheritance/ChangeSuperClassFix.java new file mode 100644 index 000000000000..f4c56ec98a66 --- /dev/null +++ b/java/java-impl/src/com/intellij/codeInspection/inheritance/ChangeSuperClassFix.java @@ -0,0 +1,157 @@ +/* + * Copyright 2000-2013 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.codeInspection.inheritance; + +import com.intellij.codeInsight.CodeInsightUtilBase; +import com.intellij.codeInsight.daemon.GroupNames; +import com.intellij.codeInsight.intention.HighPriorityAction; +import com.intellij.codeInspection.LocalQuickFix; +import com.intellij.codeInspection.ProblemDescriptor; +import com.intellij.openapi.command.WriteCommandAction; +import com.intellij.openapi.project.Project; +import com.intellij.psi.*; +import com.intellij.psi.codeStyle.JavaCodeStyleManager; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.TestOnly; + +/** + * @author Dmitry Batkovich + */ +public class ChangeSuperClassFix implements LocalQuickFix { + @NotNull + private final PsiClass myNewSuperClass; + @NotNull + private final PsiClass myOldSuperClass; + private final int myPercent; + + public ChangeSuperClassFix(final @NotNull PsiClass newSuperClass, final int percent, final @NotNull PsiClass oldSuperClass) { + this.myNewSuperClass = newSuperClass; + this.myOldSuperClass = oldSuperClass; + this.myPercent = percent; + } + + @NotNull + @TestOnly + public PsiClass getNewSuperClass() { + return myNewSuperClass; + } + + @TestOnly + public int getPercent() { + return myPercent; + } + + @NotNull + @Override + public String getName() { + return String.format("%s%% extends %s", myPercent, myNewSuperClass.getQualifiedName()); + } + + @NotNull + @Override + public String getFamilyName() { + return GroupNames.INHERITANCE_GROUP_NAME; + } + + @Override + public void applyFix(@NotNull final Project project, @NotNull final ProblemDescriptor problemDescriptor) { + changeSuperClass((PsiClass)problemDescriptor.getPsiElement(), myOldSuperClass, myNewSuperClass); + } + + /** + * myOldSuperClass and myNewSuperClass can be interfaces or classes in any combination + *

+ * 1. not checks that myOldSuperClass is really super of aClass + * 2. not checks that myNewSuperClass not exists in currently existed supers + */ + private static void changeSuperClass(final @NotNull PsiClass aClass, + final @NotNull PsiClass oldSuperClass, + final @NotNull PsiClass newSuperClass) { + if (!CodeInsightUtilBase.preparePsiElementForWrite(aClass)) return; + + new WriteCommandAction.Simple(newSuperClass.getProject(), aClass.getContainingFile()) { + @Override + protected void run() throws Throwable { + PsiElementFactory factory = JavaPsiFacade.getInstance(aClass.getProject()).getElementFactory(); + if (aClass instanceof PsiAnonymousClass) { + ((PsiAnonymousClass)aClass).getBaseClassReference().replace(factory.createClassReferenceElement(newSuperClass)); + } + else if (oldSuperClass.isInterface()) { + final PsiReferenceList interfaceList = aClass.getImplementsList(); + if (interfaceList != null) { + for (final PsiJavaCodeReferenceElement interfaceRef : interfaceList.getReferenceElements()) { + final PsiElement aInterface = interfaceRef.resolve(); + if (aInterface != null && aInterface.isEquivalentTo(oldSuperClass)) { + interfaceRef.delete(); + } + } + } + + final PsiReferenceList extendsList = aClass.getExtendsList(); + if (extendsList != null) { + final PsiJavaCodeReferenceElement newClassReference = factory.createClassReferenceElement(newSuperClass); + if (extendsList.getReferenceElements().length == 0) { + extendsList.add(newClassReference); + } + } + } + else { + final PsiReferenceList extendsList = aClass.getExtendsList(); + if (extendsList != null && extendsList.getReferenceElements().length == 1) { + extendsList.getReferenceElements()[0].delete(); + PsiElement ref = extendsList.add(factory.createClassReferenceElement(newSuperClass)); + JavaCodeStyleManager.getInstance(aClass.getProject()).shortenClassReferences(ref); + } + } + } + }.execute(); + } + + public static LocalQuickFix highPriority(final LocalQuickFix quickFix) { + return new HighPriorityQuickFixWrapper(quickFix); + } + + public static class HighPriorityQuickFixWrapper implements LocalQuickFix, HighPriorityAction { + + private final LocalQuickFix myUnderlying; + + private HighPriorityQuickFixWrapper(final LocalQuickFix underlying) { + myUnderlying = underlying; + } + + @TestOnly + public LocalQuickFix getUnderlying() { + return myUnderlying; + } + + @NotNull + @Override + public String getName() { + return myUnderlying.getName(); + } + + @NotNull + @Override + public String getFamilyName() { + return myUnderlying.getFamilyName(); + } + + @Override + public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { + myUnderlying.applyFix(project, descriptor); + } + } +} diff --git a/java/java-impl/src/com/intellij/codeInspection/inheritance/SuperClassHasFrequentlyUsedInheritorsInspection.java b/java/java-impl/src/com/intellij/codeInspection/inheritance/SuperClassHasFrequentlyUsedInheritorsInspection.java new file mode 100644 index 000000000000..1d56aa2011e0 --- /dev/null +++ b/java/java-impl/src/com/intellij/codeInspection/inheritance/SuperClassHasFrequentlyUsedInheritorsInspection.java @@ -0,0 +1,113 @@ +package com.intellij.codeInspection.inheritance; + +import com.intellij.codeInsight.daemon.GroupNames; +import com.intellij.codeInspection.*; +import com.intellij.codeInspection.inheritance.search.InheritorsStatisticalDataSearch; +import com.intellij.codeInspection.inheritance.search.InheritorsStatisticsSearchResult; +import com.intellij.openapi.project.Project; +import com.intellij.psi.*; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * @author Dmitry Batkovich + */ +public class SuperClassHasFrequentlyUsedInheritorsInspection extends BaseJavaLocalInspectionTool { + private final static int MIN_PERCENT_RATIO = 5; + public final static int MAX_QUICK_FIX_COUNTS = 4; + + @Nls + @NotNull + @Override + public String getGroupDisplayName() { + return GroupNames.INHERITANCE_GROUP_NAME; + } + + @Nls + @NotNull + @Override + public String getDisplayName() { + return "Direct inheritance on super class not frequently used"; + } + + @Override + public boolean isEnabledByDefault() { + return true; + } + + @Nullable + @Override + public ProblemDescriptor[] checkClass(@NotNull final PsiClass aClass, + @NotNull final InspectionManager manager, + final boolean isOnTheFly) { + if (aClass.isInterface() || aClass instanceof PsiTypeParameter || aClass.getMethods().length != 0) return null; + + if (aClass.isInterface() || aClass.hasModifierProperty(PsiModifier.ABSTRACT)) return null; + + final PsiClass superClass = getSuperIfUnique(aClass); + if (superClass == null) return null; + + final List topInheritors = + InheritorsStatisticalDataSearch.search(superClass, aClass, aClass.getResolveScope(), MIN_PERCENT_RATIO); + + if (topInheritors.isEmpty()) { + return null; + } + + final Collection topInheritorsQuickFix = new ArrayList(topInheritors.size()); + + boolean isFirst = true; + for (final InheritorsStatisticsSearchResult searchResult : topInheritors) { + LocalQuickFix quickFix = + new ChangeSuperClassFix(searchResult.getPsiClass(), searchResult.getPercent(), superClass); + if (isFirst) { + quickFix = ChangeSuperClassFix.highPriority(quickFix); + isFirst = false; + } + topInheritorsQuickFix.add(quickFix); + if (topInheritorsQuickFix.size() >= MAX_QUICK_FIX_COUNTS) { + break; + } + } + return new ProblemDescriptor[]{manager + .createProblemDescriptor(aClass, String.format("Most of inheritors of this %s are not direct", superClass.getQualifiedName()), false, + topInheritorsQuickFix.toArray(new LocalQuickFix[topInheritorsQuickFix.size()]), + ProblemHighlightType.INFORMATION)}; + } + + @Nullable + private static PsiClass getSuperIfUnique(final @NotNull PsiClass aClass) { + if (aClass instanceof PsiAnonymousClass) { + return (PsiClass)((PsiAnonymousClass)aClass).getBaseClassReference().resolve(); + } + final PsiReferenceList extendsList = aClass.getExtendsList(); + if (extendsList != null) { + final PsiJavaCodeReferenceElement[] referenceElements = extendsList.getReferenceElements(); + if (referenceElements.length == 1) { + PsiClass returnClass = (PsiClass)referenceElements[0].resolve(); + if (returnClass != null && + !CommonClassNames.JAVA_LANG_OBJECT.equals(returnClass.getQualifiedName()) && + !returnClass.isInterface()) { + return returnClass; + } + } + } + + final PsiReferenceList implementsList = aClass.getImplementsList(); + if (implementsList != null) { + final PsiJavaCodeReferenceElement[] referenceElements = implementsList.getReferenceElements(); + if (referenceElements.length == 1) { + PsiClass returnClass = (PsiClass)referenceElements[0].resolve(); + if (returnClass != null && returnClass.isInterface()) { + return returnClass; + } + } + } + return null; + } +} diff --git a/java/java-impl/src/com/intellij/codeInspection/inheritance/search/InheritorsCountData.java b/java/java-impl/src/com/intellij/codeInspection/inheritance/search/InheritorsCountData.java new file mode 100644 index 000000000000..18f65878eacd --- /dev/null +++ b/java/java-impl/src/com/intellij/codeInspection/inheritance/search/InheritorsCountData.java @@ -0,0 +1,54 @@ +package com.intellij.codeInspection.inheritance.search; + +import com.intellij.psi.PsiClass; +import org.jetbrains.annotations.NotNull; + +/** + * @author Dmitry Batkovich + */ +class InheritorsCountData implements Comparable { + @NotNull + private final PsiClass myPsiClass; + private final int myInheritorsCount; + + public InheritorsCountData(@NotNull final PsiClass psiClass, final int inheritorsCount) { + myPsiClass = psiClass; + myInheritorsCount = inheritorsCount; + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || !(o instanceof InheritorsCountData)) return false; + + final InheritorsCountData data = (InheritorsCountData)o; + return myInheritorsCount == data.myInheritorsCount && myPsiClass.equals(data.myPsiClass); + } + + @NotNull + public PsiClass getPsiClass() { + return myPsiClass; + } + + public int getInheritorsCount() { + return myInheritorsCount; + } + + @Override + public int hashCode() { + final String name = myPsiClass.getName(); + int result = name != null ? name.hashCode() : 0; + return 31 * result + myInheritorsCount; + } + + @Override + public int compareTo(@NotNull final InheritorsCountData that) { + final int sub = -this.myInheritorsCount + that.myInheritorsCount; + if (sub != 0) return sub; + return String.CASE_INSENSITIVE_ORDER.compare(this.myPsiClass.getName(), that.myPsiClass.getName()); + } + + public String toString() { + return String.format("%s:%d", myPsiClass, myInheritorsCount); + } +} \ No newline at end of file diff --git a/java/java-impl/src/com/intellij/codeInspection/inheritance/search/InheritorsStatisticalDataSearch.java b/java/java-impl/src/com/intellij/codeInspection/inheritance/search/InheritorsStatisticalDataSearch.java new file mode 100644 index 000000000000..8691d6b65f3e --- /dev/null +++ b/java/java-impl/src/com/intellij/codeInspection/inheritance/search/InheritorsStatisticalDataSearch.java @@ -0,0 +1,133 @@ +package com.intellij.codeInspection.inheritance.search; + +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiClass; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.psi.search.searches.DirectClassInheritorsSearch; +import com.intellij.util.Processor; +import org.jetbrains.annotations.NotNull; + +import java.util.*; + +/** + * @author Dmitry Batkovich + */ +public class InheritorsStatisticalDataSearch { + + /** + * search for most used inheritors of superClass in scope + * + * @param aClass - class that excluded from inheritors of superClass + * @param minPercentRatio - head volume + * @return - search results in relevant ordering (frequency descent) + */ + public static List search(final @NotNull PsiClass superClass, + final @NotNull PsiClass aClass, + final @NotNull GlobalSearchScope scope, + final int minPercentRatio) { + final String superClassName = superClass.getName(); + final String aClassName = aClass.getName(); + final Set disabledNames = new HashSet(); + disabledNames.add(aClassName); + disabledNames.add(superClassName); + final Set collector = new TreeSet(); + final Pair collectingResult = collectInheritorsInfo(superClass, collector, disabledNames); + final int allAnonymousInheritors = collectingResult.getSecond(); + final int allInheritors = collectingResult.getFirst() + allAnonymousInheritors - 1; + + final List result = new ArrayList(); + + Integer firstPercent = null; + for (final InheritorsCountData data : collector) { + final int inheritorsCount = data.getInheritorsCount(); + if (inheritorsCount < allAnonymousInheritors) { + break; + } + final int percent = (inheritorsCount * 100) / allInheritors; + if (percent < 1) { + break; + } + if (firstPercent == null) { + firstPercent = percent; + } + else if (percent * minPercentRatio < firstPercent) { + break; + } + + final PsiClass psiClass = data.getPsiClass(); + final VirtualFile file = psiClass.getContainingFile().getVirtualFile(); + if (file != null && scope.contains(file)) { + result.add(new InheritorsStatisticsSearchResult(psiClass, percent)); + } + } + return result; + } + + private static Pair collectInheritorsInfo(final PsiClass superClass, + final Set collector, + final Set disabledNames) { + return collectInheritorsInfo(superClass, collector, disabledNames, new HashSet(), new HashSet()); + } + + private static Pair collectInheritorsInfo(final PsiClass aClass, + final Set collector, + final Set disabledNames, + final Set processedElements, + final Set allNotAnonymousInheritors) { + final String className = aClass.getName(); + if (!processedElements.add(className)) return Pair.create(0, 0); + + final MyInheritorsInfoProcessor processor = new MyInheritorsInfoProcessor(collector, disabledNames, processedElements); + DirectClassInheritorsSearch.search(aClass).forEach(processor); + + allNotAnonymousInheritors.addAll(processor.getAllNotAnonymousInheritors()); + + final int allInheritorsCount = processor.getAllNotAnonymousInheritors().size() + processor.getAnonymousInheritorsCount(); + if (!aClass.isInterface() && allInheritorsCount != 0 && !disabledNames.contains(className)) { + collector.add(new InheritorsCountData(aClass, allInheritorsCount)); + } + return Pair.create(allNotAnonymousInheritors.size(), processor.getAnonymousInheritorsCount()); + } + + private static class MyInheritorsInfoProcessor implements Processor { + private final Set myCollector; + private final Set myDisabledNames; + private final Set myProcessedElements; + private final Set myAllNotAnonymousInheritors; + + private MyInheritorsInfoProcessor(Set collector, Set disabledNames, Set processedElements) { + myCollector = collector; + myDisabledNames = disabledNames; + myProcessedElements = processedElements; + myAllNotAnonymousInheritors = new HashSet(); + } + + private int myAnonymousInheritorsCount = 0; + + private Set getAllNotAnonymousInheritors() { + return myAllNotAnonymousInheritors; + } + + private int getAnonymousInheritorsCount() { + return myAnonymousInheritorsCount; + } + + @Override + public boolean process(final PsiClass psiClass) { + final String inheritorName = psiClass.getName(); + if (inheritorName == null) { + myAnonymousInheritorsCount++; + } + else { + final Pair res = + collectInheritorsInfo(psiClass, myCollector, myDisabledNames, myProcessedElements, myAllNotAnonymousInheritors); + myAnonymousInheritorsCount += res.getSecond(); + if (!psiClass.isInterface()) { + myAllNotAnonymousInheritors.add(inheritorName); + } + } + return true; + } + } +} diff --git a/java/java-impl/src/com/intellij/codeInspection/inheritance/search/InheritorsStatisticsSearchResult.java b/java/java-impl/src/com/intellij/codeInspection/inheritance/search/InheritorsStatisticsSearchResult.java new file mode 100644 index 000000000000..2d832bd91c92 --- /dev/null +++ b/java/java-impl/src/com/intellij/codeInspection/inheritance/search/InheritorsStatisticsSearchResult.java @@ -0,0 +1,28 @@ +package com.intellij.codeInspection.inheritance.search; + +import com.intellij.psi.PsiClass; +import org.jetbrains.annotations.NotNull; + +/** + * @author Dmitry Batkovich + */ +public class InheritorsStatisticsSearchResult { + + @NotNull + private final PsiClass myClass; + private final int myPercent; + + InheritorsStatisticsSearchResult(final @NotNull PsiClass aClass, final int percent) { + myClass = aClass; + myPercent = percent; + } + + public PsiClass getPsiClass() { + return myClass; + } + + public int getPercent() { + return myPercent; + } + +} diff --git a/java/java-tests/testData/inspection/smartInheritance/AbstractClass.java b/java/java-tests/testData/inspection/smartInheritance/AbstractClass.java new file mode 100644 index 000000000000..6f3507029a7b --- /dev/null +++ b/java/java-tests/testData/inspection/smartInheritance/AbstractClass.java @@ -0,0 +1,12 @@ +class MyInheritor implements A {} + +interface A {} + +abstract class B implements A {} + +abstract class C extends B {} +abstract class C1 extends B {} +abstract class C2 extends B {} +abstract class C3 extends B {} +abstract class C4 extends B {} +abstract class C5 extends B {} diff --git a/java/java-tests/testData/inspection/smartInheritance/AnonymousClasses.java b/java/java-tests/testData/inspection/smartInheritance/AnonymousClasses.java new file mode 100644 index 000000000000..789e79150a90 --- /dev/null +++ b/java/java-tests/testData/inspection/smartInheritance/AnonymousClasses.java @@ -0,0 +1,17 @@ +class Some { + void m() { + A someA = new A () {} + } +} + +class A {} + +class B extends A {} + +class B1 extends B {} +class B3 extends B {} +class B4 extends B {} +class B5 extends B {} +class B3 extends B {} +class B6 extends B {} + diff --git a/java/java-tests/testData/inspection/smartInheritance/AnonymousClassesInStats.java b/java/java-tests/testData/inspection/smartInheritance/AnonymousClassesInStats.java new file mode 100644 index 000000000000..4ff927db43b6 --- /dev/null +++ b/java/java-tests/testData/inspection/smartInheritance/AnonymousClassesInStats.java @@ -0,0 +1,22 @@ +class MyInheritor implements A0 { + +} + +interface A0 { + +} + +class A implements A0 {} + +class C implements A0 {} +class D extends C {} + +class Some { + void m() { + A a1 = new A() {}; + A a3 = new A() {}; + A a4 = new A() {}; + A a5 = new A() {}; + A a2 = new A() {}; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/smartInheritance/InterfacesNotShowed.java b/java/java-tests/testData/inspection/smartInheritance/InterfacesNotShowed.java new file mode 100644 index 000000000000..d5ca8af6e15b --- /dev/null +++ b/java/java-tests/testData/inspection/smartInheritance/InterfacesNotShowed.java @@ -0,0 +1,22 @@ +class MyInheritor implements A { + +} + +interface A { +} + +interface B extends A {} +interface B1 extends A {} +interface B6 extends A {} +interface B2 extends A {} +interface B3 extends A {} +interface B4 extends A {} +interface B5 extends A {} + +interface C extends B {} +interface C1 extends B {} +interface C2 extends B {} +interface C3 extends B {} +interface C4 extends B {} + +class D extends C {} diff --git a/java/java-tests/testData/inspection/smartInheritance/InterfacesNotShowed2.java b/java/java-tests/testData/inspection/smartInheritance/InterfacesNotShowed2.java new file mode 100644 index 000000000000..72e94b550f6d --- /dev/null +++ b/java/java-tests/testData/inspection/smartInheritance/InterfacesNotShowed2.java @@ -0,0 +1,25 @@ +class MyInheritor implements A {} + +interface A {} + +interface B extends A {} +interface B1 extends A {} +interface B6 extends A {} +interface B2 extends A {} +interface B3 extends A {} +interface B4 extends A {} +interface B5 extends A {} + +interface C extends B {} +interface C1 extends B {} +interface C2 extends B {} +interface C3 extends B {} +interface C4 extends B {} + +class D implements C {} + +class E1 extends D {} +class E2 extends D {} +class E3 extends D {} +class E4 extends D {} +class E5 extends D {} diff --git a/java/java-tests/testData/inspection/smartInheritance/NoCompletionForAbstractClasses.java b/java/java-tests/testData/inspection/smartInheritance/NoCompletionForAbstractClasses.java new file mode 100644 index 000000000000..397185cb6efa --- /dev/null +++ b/java/java-tests/testData/inspection/smartInheritance/NoCompletionForAbstractClasses.java @@ -0,0 +1,25 @@ +abstract class MyInheritor implements A {} + +interface A {} + +interface B extends A {} +interface B1 extends A {} +interface B6 extends A {} +interface B2 extends A {} +interface B3 extends A {} +interface B4 extends A {} +interface B5 extends A {} + +interface C extends B {} +interface C1 extends B {} +interface C2 extends B {} +interface C3 extends B {} +interface C4 extends B {} + +class D implements C {} + +class E1 extends D {} +class E2 extends D {} +class E3 extends D {} +class E4 extends D {} +class E5 extends D {} diff --git a/java/java-tests/testData/inspection/smartInheritance/NoMoreThanMaxCountIntentions.java b/java/java-tests/testData/inspection/smartInheritance/NoMoreThanMaxCountIntentions.java new file mode 100644 index 000000000000..7a6f476bfc79 --- /dev/null +++ b/java/java-tests/testData/inspection/smartInheritance/NoMoreThanMaxCountIntentions.java @@ -0,0 +1,37 @@ +class MyInheritor extends A { + +} + +abstract class A {} + +class B1 extends A {} +class C11 extends B1 {} +class C12 extends B1 {} + +class B2 extends A {} +class C21 extends B2 {} +class C22 extends B2 {} + +class B3 extends A {} +class C31 extends B3 {} +class C32 extends B3 {} + +class B4 extends A {} +class C41 extends B4 {} +class C42 extends B4 {} + +class B5 extends A {} +class C51 extends B5 {} +class C52 extends B5 {} + +class B6 extends A {} +class C61 extends B6 {} +class C62 extends B6 {} + +class B7 extends A {} +class C71 extends B7 {} +class C72 extends B7 {} + +class B8 extends A {} +class C81 extends B8 {} +class C82 extends B8 {} diff --git a/java/java-tests/testData/inspection/smartInheritance/RelevantClassShowed.java b/java/java-tests/testData/inspection/smartInheritance/RelevantClassShowed.java new file mode 100644 index 000000000000..2919f193b1b6 --- /dev/null +++ b/java/java-tests/testData/inspection/smartInheritance/RelevantClassShowed.java @@ -0,0 +1,33 @@ +class MyInheritor implements A { + +} + +//first level + +interface A { +} + +//2 level + +class B implements A { +} + +//3 level + +class C extends B { +} + +class C1 extends B { +} + +//... + +class D extends C{} +class D1 extends C{} +class D2 extends C{} +class D3 extends C{} +class D4 extends C{} +class D5 extends C{} +class D6 extends C{} +class D7 extends C{} +class D8 extends C{} diff --git a/java/java-tests/testSrc/com/intellij/codeInspection/SuperClassHasFrequentlyUsedInheritorsInspectionTest.java b/java/java-tests/testSrc/com/intellij/codeInspection/SuperClassHasFrequentlyUsedInheritorsInspectionTest.java new file mode 100644 index 000000000000..f0dd9ad75475 --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/codeInspection/SuperClassHasFrequentlyUsedInheritorsInspectionTest.java @@ -0,0 +1,116 @@ +package com.intellij.codeInspection; + +import com.intellij.JavaTestUtil; +import com.intellij.codeInsight.intention.IntentionAction; +import com.intellij.codeInspection.ex.QuickFixWrapper; +import com.intellij.codeInspection.inheritance.ChangeSuperClassFix; +import com.intellij.codeInspection.inheritance.SuperClassHasFrequentlyUsedInheritorsInspection; +import com.intellij.openapi.roots.ModuleRootManager; +import com.intellij.openapi.util.Pair; +import com.intellij.testFramework.fixtures.JavaCodeInsightFixtureTestCase; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.containers.HashSet; +import org.jetbrains.annotations.Nullable; + +import java.util.Set; + +/** + * @author Dmitry Batkovich + */ +@SuppressWarnings("ALL") +public class SuperClassHasFrequentlyUsedInheritorsInspectionTest extends JavaCodeInsightFixtureTestCase { + + @Override + protected String getTestDataPath() { + return JavaTestUtil.getJavaTestDataPath() + "/inspection/smartInheritance/"; + } + + //search tests + + public void testRelevantClassShowed() { + doTest(Pair.create("C", 75), Pair.create("B", 91)); + } + + public void testInterfacesNotShowed() { + assertEmptyResult(); + } + + public void testInterfacesNotShowed2() { + doTest(Pair.create("D", 83)); + } + + public void testAnonymousClasses() { + doTest(Pair.create("B", 83)); + } + + public void testAnonymousClassesInStats() { + doTest(Pair.create("A", 62)); + } + + public void testAbstractClass() { + doTest(Pair.create("B", 85)); + } + + public void testNoCompletionForAbstractClasses() { + assertEmptyResult(); + } + + public void testNoMoreThanMaxCountIntentions() { + doTest(SuperClassHasFrequentlyUsedInheritorsInspection.MAX_QUICK_FIX_COUNTS); + } + + // completion tests + + private void assertEmptyResult() { + doTest(); + } + + private void doTest(final Pair... expectedResults) { + myFixture.configureByFile(getTestName(false) + ".java"); + myFixture.enableInspections(SuperClassHasFrequentlyUsedInheritorsInspection.class); + + final Set> actualSet = new HashSet>(); + for (IntentionAction intentionAction : myFixture.getAvailableIntentions()) { + if (intentionAction instanceof QuickFixWrapper) { + LocalQuickFix localQuickFix = ((QuickFixWrapper)intentionAction).getFix(); + ChangeSuperClassFix changeSuperClassFix = getQuickFixFromWrapper((QuickFixWrapper)intentionAction); + if (changeSuperClassFix != null) { + actualSet.add(Pair.create(changeSuperClassFix.getNewSuperClass().getQualifiedName(), changeSuperClassFix.getPercent())); + } + } + } + + final Set> expectedSet = ContainerUtil.newHashSet(expectedResults); + assertEquals(expectedSet, actualSet); + } + + private void doTest(final int expectedSize) { + myFixture.configureByFile(getTestName(false) + ".java"); + myFixture.enableInspections(SuperClassHasFrequentlyUsedInheritorsInspection.class); + + + final Set> actualSet = new HashSet>(); + for (IntentionAction intentionAction : myFixture.getAvailableIntentions()) { + if (intentionAction instanceof QuickFixWrapper) { + ChangeSuperClassFix changeSuperClassFix = getQuickFixFromWrapper((QuickFixWrapper)intentionAction); + if (changeSuperClassFix != null) { + actualSet.add(Pair.create(changeSuperClassFix.getNewSuperClass().getQualifiedName(), changeSuperClassFix.getPercent())); + } + } + } + + assertSize(expectedSize, actualSet); + } + + @Nullable + private final static ChangeSuperClassFix getQuickFixFromWrapper(final QuickFixWrapper quickFixWrapper) { + final LocalQuickFix quickFix = quickFixWrapper.getFix(); + if (quickFix instanceof ChangeSuperClassFix) { + return (ChangeSuperClassFix)quickFix; + } + if (quickFix instanceof ChangeSuperClassFix.HighPriorityQuickFixWrapper) { + return (ChangeSuperClassFix)((ChangeSuperClassFix.HighPriorityQuickFixWrapper)quickFix).getUnderlying(); + } + return null; + } +} diff --git a/resources-en/src/inspectionDescriptions/SuperClassHasFrequentlyUsedInheritors.html b/resources-en/src/inspectionDescriptions/SuperClassHasFrequentlyUsedInheritors.html new file mode 100644 index 000000000000..87210e46438c --- /dev/null +++ b/resources-en/src/inspectionDescriptions/SuperClassHasFrequentlyUsedInheritors.html @@ -0,0 +1,20 @@ + + + +This inspection find super classes that can be replaced by another frequently used super + + \ No newline at end of file diff --git a/resources/src/META-INF/IdeaPlugin.xml b/resources/src/META-INF/IdeaPlugin.xml index e5a32554f5ab..3c5c4f24c076 100644 --- a/resources/src/META-INF/IdeaPlugin.xml +++ b/resources/src/META-INF/IdeaPlugin.xml @@ -624,7 +624,10 @@ - + com.intellij.codeInsight.daemon.quickFix.RedundantLambdaParameterTypeIntention