IDEA-184196 No "Implements via subclass" icon when a method is final

This commit is contained in:
Alexey Kudravtsev
2018-01-09 17:59:00 +03:00
parent 45b7d50bf6
commit 1021f8e0c1
4 changed files with 83 additions and 30 deletions
@@ -20,8 +20,8 @@ import com.intellij.psi.*;
import com.intellij.psi.search.searches.ClassInheritorsSearch;
import com.intellij.psi.util.MethodSignatureUtil;
import com.intellij.psi.util.PsiSuperMethodUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.util.ObjectUtils;
import com.intellij.util.Processor;
import com.intellij.util.containers.MultiMap;
import com.intellij.util.containers.hash.HashMap;
@@ -37,10 +37,7 @@ public class FindSuperElementsHelper {
if (element instanceof PsiClass) {
PsiClass aClass = (PsiClass) element;
List<PsiClass> allSupers = new ArrayList<>(Arrays.asList(aClass.getSupers()));
for (Iterator<PsiClass> iterator = allSupers.iterator(); iterator.hasNext();) {
PsiClass superClass = iterator.next();
if (CommonClassNames.JAVA_LANG_OBJECT.equals(superClass.getQualifiedName())) iterator.remove();
}
allSupers.removeIf(superClass -> CommonClassNames.JAVA_LANG_OBJECT.equals(superClass.getQualifiedName()));
return allSupers.toArray(new PsiClass[allSupers.size()]);
}
if (element instanceof PsiMethod) {
@@ -88,22 +85,31 @@ public class FindSuperElementsHelper {
}
}
Map<PsiMethod, SiblingInfo> result = new HashMap<>();
Map<PsiMethod, SiblingInfo> result = null;
for (PsiClass psiClass : byClass.keySet()) {
SiblingInheritorSearcher searcher = new SiblingInheritorSearcher(byClass.get(psiClass), psiClass);
ClassInheritorsSearch.search(psiClass, psiClass.getUseScope(), true, true, false).forEach(searcher);
result.putAll(searcher.getResult());
Map<PsiMethod, SiblingInfo> searcherResult = searcher.getResult();
if (!searcherResult.isEmpty()) {
if (result == null) result = new HashMap<>();
result.putAll(searcherResult);
}
}
return result;
return result == null ? Collections.emptyMap() : result;
}
private static boolean canHaveSiblingSuper(PsiMethod method, PsiClass containingClass) {
public static boolean canHaveSiblingSuper(@NotNull PsiMethod method, PsiClass containingClass) {
return containingClass != null &&
PsiUtil.canBeOverridden(method) &&
!method.isConstructor() &&
// NB: method CAN be final
!method.hasModifierProperty(PsiModifier.STATIC) &&
!method.hasModifierProperty(PsiModifier.PRIVATE) &&
!method.hasModifierProperty(PsiModifier.ABSTRACT) &&
!method.hasModifierProperty(PsiModifier.NATIVE) &&
method.hasModifierProperty(PsiModifier.PUBLIC) &&
!containingClass.isInterface() &&
!(containingClass instanceof PsiAnonymousClass) &&
!containingClass.hasModifierProperty(PsiModifier.FINAL) &&
!CommonClassNames.JAVA_LANG_OBJECT.equals(containingClass.getQualifiedName());
}
@@ -120,10 +126,10 @@ public class FindSuperElementsHelper {
private static class SiblingInheritorSearcher implements Processor<PsiClass> {
private final PsiClass myContainingClass;
private final Set<PsiMethod> myRemainingMethods;
private final Map<PsiMethod, SiblingInfo> myResult = new HashMap<>();
private Map<PsiMethod, SiblingInfo> myResult;
private final Collection<PsiAnchor> myCheckedInterfaces = new THashSet<>();
SiblingInheritorSearcher(Collection<PsiMethod> methods, PsiClass containingClass) {
SiblingInheritorSearcher(@NotNull Collection<PsiMethod> methods, @NotNull PsiClass containingClass) {
myContainingClass = containingClass;
myRemainingMethods = new HashSet<>(methods);
myCheckedInterfaces.add(PsiAnchor.create(containingClass));
@@ -142,19 +148,25 @@ public class FindSuperElementsHelper {
return !myRemainingMethods.isEmpty();
}
private void processInterface(PsiClass inheritor, PsiClass anInterface) {
private void processInterface(@NotNull PsiClass inheritor, @NotNull PsiClass anInterface) {
for (Iterator<PsiMethod> methodIterator = myRemainingMethods.iterator(); methodIterator.hasNext(); ) {
ProgressManager.checkCanceled();
PsiMethod method = methodIterator.next();
SiblingInfo info = findSibling(inheritor, anInterface, method);
if (info != null) {
myResult.put(method, info);
Map<PsiMethod, SiblingInfo> result;
if ((result = myResult) == null) {
myResult = result = new HashMap<>();
}
result.put(method, info);
methodIterator.remove();
}
}
}
@Nullable
private SiblingInfo findSibling(PsiClass inheritor, PsiClass anInterface, PsiMethod method) {
private SiblingInfo findSibling(@NotNull PsiClass inheritor, @NotNull PsiClass anInterface, @NotNull PsiMethod method) {
for (PsiMethod superMethod : anInterface.findMethodsByName(method.getName(), true)) {
PsiElement navigationElement = superMethod.getNavigationElement();
if (!(navigationElement instanceof PsiMethod)) continue; // Kotlin
@@ -173,7 +185,7 @@ public class FindSuperElementsHelper {
return null;
}
private boolean isOverridden(PsiClass inheritor, PsiMethod method, PsiMethod superMethod, PsiClass superInterface) {
private boolean isOverridden(@NotNull PsiClass inheritor, @NotNull PsiMethod method, @NotNull PsiMethod superMethod, @NotNull PsiClass superInterface) {
// calculate substitutor of containingClass --> inheritor
PsiSubstitutor substitutor = TypeConversionUtil.getSuperClassSubstitutor(myContainingClass, inheritor, PsiSubstitutor.EMPTY);
// calculate substitutor of inheritor --> superInterface
@@ -182,8 +194,9 @@ public class FindSuperElementsHelper {
return MethodSignatureUtil.isSubsignature(superMethod.getSignature(superInterfaceSubstitutor), method.getSignature(substitutor));
}
@NotNull
Map<PsiMethod, SiblingInfo> getResult() {
return myResult;
return ObjectUtils.notNull(myResult, Collections.emptyMap());
}
}
}
@@ -185,7 +185,8 @@ public class JavaLineMarkerProvider extends LineMarkerProviderDescriptor {
List<Computable<List<LineMarkerInfo>>> tasks = new ArrayList<>();
MultiMap<PsiClass, PsiMethod> byClass = MultiMap.create();
MultiMap<PsiClass, PsiMethod> canbeOverridden = MultiMap.create();
MultiMap<PsiClass, PsiMethod> canHaveSiblings = MultiMap.create();
//noinspection ForLoopReplaceableByForEach
for (int i = 0; i < elements.size(); i++) {
PsiElement element = elements.get(i);
@@ -194,9 +195,12 @@ public class JavaLineMarkerProvider extends LineMarkerProviderDescriptor {
PsiElement parent = element.getParent();
if (parent instanceof PsiMethod) {
final PsiMethod method = (PsiMethod)parent;
PsiClass psiClass = method.getContainingClass();
if (PsiUtil.canBeOverridden(method) && psiClass != null) {
byClass.putValue(psiClass, method);
PsiClass containingClass = method.getContainingClass();
if (containingClass != null && PsiUtil.canBeOverridden(method)) {
canbeOverridden.putValue(containingClass, method);
}
if (FindSuperElementsHelper.canHaveSiblingSuper(method, containingClass)) {
canHaveSiblings.putValue(containingClass, method);
}
if (isServiceProviderMethod(method)) {
tasks.add(() -> collectServiceProviderMethod(method));
@@ -213,11 +217,14 @@ public class JavaLineMarkerProvider extends LineMarkerProviderDescriptor {
}
}
}
for (PsiClass psiClass : byClass.keySet()) {
Collection<PsiMethod> methods = byClass.get(psiClass);
tasks.add(() -> collectSiblingInheritedMethods(methods));
for (PsiClass psiClass : canbeOverridden.keySet()) {
Collection<PsiMethod> methods = canbeOverridden.get(psiClass);
tasks.add(() -> collectOverridingMethods(methods, psiClass));
}
for (PsiClass psiClass : canHaveSiblings.keySet()) {
Collection<PsiMethod> methods = canHaveSiblings.get(psiClass);
tasks.add(() -> collectSiblingInheritedMethods(methods));
}
Object lock = new Object();
ProgressIndicator indicator = ProgressIndicatorProvider.getGlobalProgressIndicator();
@@ -484,10 +491,10 @@ public class JavaLineMarkerProvider extends LineMarkerProviderDescriptor {
}
public static abstract class ServiceNavigationHandler implements GutterIconNavigationHandler<PsiElement> {
protected final String myInterfaceClassName;
public abstract static class ServiceNavigationHandler implements GutterIconNavigationHandler<PsiElement> {
final String myInterfaceClassName;
protected ServiceNavigationHandler(@NotNull String interfaceClassName) {myInterfaceClassName = interfaceClassName;}
ServiceNavigationHandler(@NotNull String interfaceClassName) {myInterfaceClassName = interfaceClassName;}
@Override
public void navigate(MouseEvent e, PsiElement element) {
@@ -505,16 +512,17 @@ public class JavaLineMarkerProvider extends LineMarkerProviderDescriptor {
return myInterfaceClassName;
}
protected boolean isTargetReference(PsiJavaCodeReferenceElement reference) {
boolean isTargetReference(PsiJavaCodeReferenceElement reference) {
return reference != null && getTargetFQN().equals(reference.getQualifiedName());
}
}
private static class ServiceUsesNavigationHandler extends ServiceNavigationHandler {
public ServiceUsesNavigationHandler(String interfaceClassName) {
ServiceUsesNavigationHandler(String interfaceClassName) {
super(interfaceClassName);
}
@Override
public PsiJavaCodeReferenceElement findTargetReference(@NotNull PsiJavaModule module) {
return StreamEx.of(module.getUses().iterator())
.map(PsiUsesStatement::getClassReference)
@@ -526,11 +534,12 @@ public class JavaLineMarkerProvider extends LineMarkerProviderDescriptor {
private static class ServiceProvidesNavigationHandler extends ServiceNavigationHandler {
private final String myImplementerClassName;
public ServiceProvidesNavigationHandler(@NotNull String interfaceClassName, @NotNull String implementerClassName) {
ServiceProvidesNavigationHandler(@NotNull String interfaceClassName, @NotNull String implementerClassName) {
super(interfaceClassName);
myImplementerClassName = implementerClassName;
}
@Override
public PsiJavaCodeReferenceElement findTargetReference(@NotNull PsiJavaModule module) {
PsiJavaCodeReferenceElement[] references =
StreamEx.of(module.getProvides().iterator())
@@ -542,6 +551,7 @@ public class JavaLineMarkerProvider extends LineMarkerProviderDescriptor {
return ContainerUtil.find(references, this::isTargetReference);
}
@Override
@NotNull
protected String getTargetFQN() {
return myImplementerClassName;
@@ -0,0 +1,11 @@
package z;
interface I {
void run();
}
abstract class A {
public final void <caret>run() {}
}
class Foo extends A implements I {
}
@@ -97,6 +97,25 @@ public class JavaGotoSuperTest extends LightDaemonAnalyzerTestCase {
LineMarkerInfo aMarker = findMarkerWithElement(markers, aRun.getNameIdentifier());
assertSame(MarkerType.SIBLING_OVERRIDING_METHOD.getNavigationHandler(), aMarker.getNavigationHandler());
}
public void testSiblingInheritanceLineMarkersEvenIfMethodIsFinal() {
configureByFile(getBasePath() + "SiblingInheritanceFinal.java");
PsiJavaFile file = (PsiJavaFile)getFile();
PsiClass i = JavaPsiFacade.getInstance(getProject()).findClass("z.I", GlobalSearchScope.fileScope(file));
PsiClass a = JavaPsiFacade.getInstance(getProject()).findClass("z.A", GlobalSearchScope.fileScope(file));
PsiMethod iRun = i.getMethods()[0];
assertEquals("run", iRun.getName());
PsiMethod aRun = a.getMethods()[0];
assertEquals("run", aRun.getName());
doHighlighting();
Document document = getEditor().getDocument();
List<LineMarkerInfo> markers = DaemonCodeAnalyzerImpl.getLineMarkers(document, getProject());
assertTrue(markers.size() >= 2);
LineMarkerInfo iMarker = findMarkerWithElement(markers, iRun.getNameIdentifier());
assertSame(MarkerType.OVERRIDDEN_METHOD.getNavigationHandler(), iMarker.getNavigationHandler());
LineMarkerInfo aMarker = findMarkerWithElement(markers, aRun.getNameIdentifier());
assertSame(MarkerType.SIBLING_OVERRIDING_METHOD.getNavigationHandler(), aMarker.getNavigationHandler());
}
private static LineMarkerInfo findMarkerWithElement(List<LineMarkerInfo> markers, PsiElement psiMethod) {
LineMarkerInfo marker = ContainerUtil.find(markers, info -> info.getElement().equals(psiMethod));