frequently used super class inspection: delete selected methods in quick fix; update description; set enabled by default

This commit is contained in:
Dmitry Batkovich
2017-05-03 14:19:23 +03:00
parent 5fdacce050
commit 3b9d7f32a6
11 changed files with 420 additions and 35 deletions
@@ -15,18 +15,32 @@
*/
package com.intellij.compiler.inspection;
import com.intellij.codeInsight.FileModificationService;
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.application.WriteAction;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.DialogBuilder;
import com.intellij.openapi.util.Pair;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.impl.source.PsiExtensibleClass;
import com.intellij.refactoring.ui.MemberSelectionPanel;
import com.intellij.refactoring.util.classMembers.MemberInfo;
import com.intellij.util.ArrayUtil;
import com.intellij.util.ObjectUtils;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.TestOnly;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class ChangeSuperClassFix implements LocalQuickFix, HighPriorityAction {
@NotNull
private final SmartPsiElementPointer<PsiClass> myNewSuperClass;
@@ -35,15 +49,18 @@ public class ChangeSuperClassFix implements LocalQuickFix, HighPriorityAction {
private final int myInheritorCount;
@NotNull
private final String myNewSuperName;
private final boolean myNewSuperIsInterface;
private final boolean myImplements;
public ChangeSuperClassFix(@NotNull final PsiClass newSuperClass, final int percent, @NotNull final PsiClass oldSuperClass) {
public ChangeSuperClassFix(@NotNull final PsiClass newSuperClass,
@NotNull final PsiClass oldSuperClass,
final int percent,
final boolean isImplements) {
final SmartPointerManager smartPointerManager = SmartPointerManager.getInstance(newSuperClass.getProject());
myNewSuperName = ObjectUtils.notNull(newSuperClass.getQualifiedName());
myNewSuperIsInterface = newSuperClass.isInterface();
myNewSuperClass = smartPointerManager.createSmartPsiElementPointer(newSuperClass);
myOldSuperClass = smartPointerManager.createSmartPsiElementPointer(oldSuperClass);
myInheritorCount = percent;
myImplements = isImplements;
}
@NotNull
@@ -60,7 +77,7 @@ public class ChangeSuperClassFix implements LocalQuickFix, HighPriorityAction {
@NotNull
@Override
public String getName() {
return String.format("Make " + (myNewSuperIsInterface ? "implements" : "extends") + " '%s'", myNewSuperName);
return String.format("Make " + (myImplements ? "implements" : "extends") + " '%s'", myNewSuperName);
}
@NotNull
@@ -69,12 +86,20 @@ public class ChangeSuperClassFix implements LocalQuickFix, HighPriorityAction {
return GroupNames.INHERITANCE_GROUP_NAME;
}
@Override
public boolean startInWriteAction() {
return false;
}
@Override
public void applyFix(@NotNull final Project project, @NotNull final ProblemDescriptor problemDescriptor) {
final PsiClass oldSuperClass = myOldSuperClass.getElement();
final PsiClass newSuperClass = myNewSuperClass.getElement();
if (oldSuperClass == null || newSuperClass == null) return;
changeSuperClass((PsiClass)problemDescriptor.getPsiElement(), oldSuperClass, newSuperClass);
PsiElement element = problemDescriptor.getPsiElement();
if (!(element instanceof PsiClass) || !FileModificationService.getInstance().preparePsiElementsForWrite(element)) return;
PsiClass aClass = (PsiClass)element;
changeSuperClass(aClass, oldSuperClass, newSuperClass);
}
/**
@@ -86,38 +111,85 @@ public class ChangeSuperClassFix implements LocalQuickFix, HighPriorityAction {
private static void changeSuperClass(@NotNull final PsiClass aClass,
@NotNull final PsiClass oldSuperClass,
@NotNull final PsiClass newSuperClass) {
List<PsiMethod> ownMethods = ((PsiExtensibleClass)aClass).getOwnMethods();
// first is own method, second is parent
List<Pair<PsiMethod, Set<PsiMethod>>> oldOverridenMethods =
ownMethods.stream().map(m -> {
if (m.isConstructor()) return null;
PsiMethod[] supers = m.findSuperMethods(oldSuperClass);
if (supers.length == 0) return null;
return Pair.create(m, ContainerUtil.set(supers));
}).collect(Collectors.toList());
JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(aClass.getProject());
PsiElementFactory factory = psiFacade.getElementFactory();
if (aClass instanceof PsiAnonymousClass) {
((PsiAnonymousClass)aClass).getBaseClassReference().replace(factory.createClassReferenceElement(newSuperClass));
WriteAction.run(() -> {
PsiElement ref;
if (aClass instanceof PsiAnonymousClass) {
ref = ((PsiAnonymousClass)aClass).getBaseClassReference().replace(factory.createClassReferenceElement(newSuperClass));
} else {
PsiReferenceList extendsList = ObjectUtils.notNull(aClass.getExtendsList());
PsiJavaCodeReferenceElement[] refElements =
ArrayUtil.mergeArrays(getReferences(extendsList), getReferences(aClass.getImplementsList()));
for (PsiJavaCodeReferenceElement refElement : refElements) {
if (refElement.isReferenceTo(oldSuperClass)) {
refElement.delete();
}
}
PsiReferenceList list;
if (newSuperClass.isInterface() && !aClass.isInterface()) {
list = aClass.getImplementsList();
}
else {
list = extendsList;
PsiJavaCodeReferenceElement[] elements = list.getReferenceElements();
if (elements.length == 1 &&
elements[0].isReferenceTo(psiFacade.findClass(CommonClassNames.JAVA_LANG_OBJECT, aClass.getResolveScope()))) {
elements[0].delete();
}
}
assert list != null;
ref = list.add(factory.createClassReferenceElement(newSuperClass));
}
JavaCodeStyleManager.getInstance(aClass.getProject()).shortenClassReferences(ref);
});
if (ownMethods.isEmpty()) {
// should not override methods from a new super class
return;
}
PsiReferenceList extendsList = ObjectUtils.notNull(aClass.getExtendsList());
PsiJavaCodeReferenceElement[] refElements =
ArrayUtil.mergeArrays(getReferences(extendsList), getReferences(aClass.getImplementsList()));
for (PsiJavaCodeReferenceElement refElement : refElements) {
if (refElement.isReferenceTo(oldSuperClass)) {
refElement.delete();
}
}
Stream<PsiMethod> memberInfos = oldOverridenMethods.stream().filter(m -> {
Set<PsiMethod> newSupers = ContainerUtil.set(m.getFirst().findSuperMethods(newSuperClass));
return !newSupers.equals(m.getSecond());
}).map(m -> m.getFirst());
PsiReferenceList list;
if (newSuperClass.isInterface()) {
list = aClass.getImplementsList();
}
else {
list = extendsList;
PsiJavaCodeReferenceElement[] elements = list.getReferenceElements();
if (elements.length == 1 &&
elements[0].isReferenceTo(psiFacade.findClass(CommonClassNames.JAVA_LANG_OBJECT, aClass.getResolveScope()))) {
elements[0].delete();
List<PsiMethod> toDelete = getOverridenMethodsToDelete(memberInfos, newSuperClass.getName());
WriteAction.run(() -> {
for (PsiMethod method : toDelete) {
method.delete();
}
}
PsiElement ref = list.add(factory.createClassReferenceElement(newSuperClass));
JavaCodeStyleManager.getInstance(aClass.getProject()).shortenClassReferences(ref);
});
}
@NotNull
private static PsiJavaCodeReferenceElement[] getReferences(PsiReferenceList list) {
return list == null ? PsiJavaCodeReferenceElement.EMPTY_ARRAY : list.getReferenceElements();
}
@NotNull
private static List<PsiMethod> getOverridenMethodsToDelete(Stream<PsiMethod> candidates, String newClassName) {
DialogBuilder dlg = new DialogBuilder();
MemberSelectionPanel panel = new MemberSelectionPanel("<html>Choose members to delete since they are already defined in <b>" + newClassName + "</b>",
candidates.map(m -> {
MemberInfo info = new MemberInfo(m);
info.setChecked(true);
return info;
}).collect(Collectors.toList()), null);
dlg.setCenterPanel(panel);
dlg.setTitle("Choose Members");
return dlg.showAndGet()
? panel.getTable().getSelectedMemberInfos().stream().map(info -> (PsiMethod)info.getMember()).collect(Collectors.toList())
: Collections.emptyList();
}
}
@@ -27,6 +27,7 @@ import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.impl.java.stubs.index.JavaFullClassNameIndex;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.util.SystemProperties;
import one.util.streamex.MoreCollectors;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -40,14 +41,14 @@ public class FrequentlyUsedInheritorInspection extends BaseJavaLocalInspectionTo
private static final Logger LOG = Logger.getInstance(FrequentlyUsedInheritorInspection.class);
public static final byte MAX_RESULT = 3;
private static final int PERCENT_THRESHOLD = 20;
private static final int PERCENT_THRESHOLD = SystemProperties.getIntProperty("FrequentlyUsedInheritorInspection.percent.threshold", 20);
@Nullable
@Override
public ProblemDescriptor[] checkClass(@NotNull final PsiClass aClass,
@NotNull final InspectionManager manager,
final boolean isOnTheFly) {
if (aClass.isInterface() || aClass instanceof PsiTypeParameter) {
if (aClass instanceof PsiTypeParameter) {
return null;
}
@@ -63,7 +64,8 @@ public class FrequentlyUsedInheritorInspection extends BaseJavaLocalInspectionTo
final Collection<LocalQuickFix> topInheritorsQuickFix = new ArrayList<>(topInheritors.size());
for (final ClassAndInheritorCount searchResult : topInheritors) {
final LocalQuickFix quickFix = new ChangeSuperClassFix(searchResult.psi, searchResult.number, superClass);
final LocalQuickFix quickFix = new ChangeSuperClassFix(searchResult.psi, superClass, searchResult.number,
searchResult.psi.isInterface() && !aClass.isInterface());
topInheritorsQuickFix.add(quickFix);
if (topInheritorsQuickFix.size() >= MAX_RESULT) {
break;
@@ -128,7 +130,7 @@ public class FrequentlyUsedInheritorInspection extends BaseJavaLocalInspectionTo
.filter(inheritor -> !(inheritor instanceof LightRef.LightAnonymousClassDef))
.map(inheritor -> {
int count = compilerRefService.getInheritorCount(inheritor);
if (count * 100 > finalHierarchyCardinality * PERCENT_THRESHOLD) {
if (count != 1 && count * 100 > finalHierarchyCardinality * PERCENT_THRESHOLD) {
return new Object() {
final LightRef.LightClassHierarchyElementDef myDef = inheritor;
final int inheritorCount = count;