diff --git a/java/java-analysis-impl/src/com/intellij/psi/impl/FindSuperElementsHelper.java b/java/java-analysis-impl/src/com/intellij/psi/impl/FindSuperElementsHelper.java index 4a83c62846bd..1befd3632cc0 100644 --- a/java/java-analysis-impl/src/com/intellij/psi/impl/FindSuperElementsHelper.java +++ b/java/java-analysis-impl/src/com/intellij/psi/impl/FindSuperElementsHelper.java @@ -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 allSupers = new ArrayList<>(Arrays.asList(aClass.getSupers())); - for (Iterator 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 result = new HashMap<>(); + Map 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 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 { private final PsiClass myContainingClass; private final Set myRemainingMethods; - private final Map myResult = new HashMap<>(); + private Map myResult; private final Collection myCheckedInterfaces = new THashSet<>(); - SiblingInheritorSearcher(Collection methods, PsiClass containingClass) { + SiblingInheritorSearcher(@NotNull Collection 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 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 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 getResult() { - return myResult; + return ObjectUtils.notNull(myResult, Collections.emptyMap()); } } } diff --git a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/JavaLineMarkerProvider.java b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/JavaLineMarkerProvider.java index 3d5a0c614961..dc8b9815729e 100644 --- a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/JavaLineMarkerProvider.java +++ b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/JavaLineMarkerProvider.java @@ -185,7 +185,8 @@ public class JavaLineMarkerProvider extends LineMarkerProviderDescriptor { List>> tasks = new ArrayList<>(); - MultiMap byClass = MultiMap.create(); + MultiMap canbeOverridden = MultiMap.create(); + MultiMap 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 methods = byClass.get(psiClass); - tasks.add(() -> collectSiblingInheritedMethods(methods)); + for (PsiClass psiClass : canbeOverridden.keySet()) { + Collection methods = canbeOverridden.get(psiClass); tasks.add(() -> collectOverridingMethods(methods, psiClass)); } + for (PsiClass psiClass : canHaveSiblings.keySet()) { + Collection 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 { - protected final String myInterfaceClassName; + public abstract static class ServiceNavigationHandler implements GutterIconNavigationHandler { + 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; diff --git a/java/java-tests/testData/codeInsight/gotosuper/SiblingInheritanceFinal.java b/java/java-tests/testData/codeInsight/gotosuper/SiblingInheritanceFinal.java new file mode 100644 index 000000000000..2b2fa80744c7 --- /dev/null +++ b/java/java-tests/testData/codeInsight/gotosuper/SiblingInheritanceFinal.java @@ -0,0 +1,11 @@ +package z; + +interface I { + void run(); +} +abstract class A { + public final void run() {} +} + +class Foo extends A implements I { +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/impl/JavaGotoSuperTest.java b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/impl/JavaGotoSuperTest.java index c42385e9c348..fd6850bfdb49 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/impl/JavaGotoSuperTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/impl/JavaGotoSuperTest.java @@ -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 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 markers, PsiElement psiMethod) { LineMarkerInfo marker = ContainerUtil.find(markers, info -> info.getElement().equals(psiMethod));