| /* |
| * 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. |
| */ |
| |
| /* |
| * @author max |
| */ |
| package com.intellij.codeInsight.daemon.impl; |
| |
| import com.intellij.codeInsight.CodeInsightBundle; |
| import com.intellij.codeInsight.daemon.DaemonBundle; |
| import com.intellij.codeInsight.daemon.GutterIconNavigationHandler; |
| import com.intellij.codeInsight.navigation.ListBackgroundUpdaterTask; |
| import com.intellij.ide.util.*; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.progress.ProgressIndicator; |
| import com.intellij.openapi.progress.ProgressManager; |
| import com.intellij.openapi.project.DumbService; |
| import com.intellij.openapi.util.Computable; |
| import com.intellij.psi.*; |
| import com.intellij.psi.search.PsiElementProcessor; |
| import com.intellij.psi.search.PsiElementProcessorAdapter; |
| import com.intellij.psi.search.SearchScope; |
| import com.intellij.psi.search.searches.ClassInheritorsSearch; |
| import com.intellij.psi.search.searches.FunctionalExpressionSearch; |
| import com.intellij.psi.search.searches.OverridingMethodsSearch; |
| import com.intellij.psi.util.PsiUtil; |
| import com.intellij.util.*; |
| import gnu.trove.THashSet; |
| import org.jetbrains.annotations.NonNls; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import javax.swing.*; |
| import java.awt.event.MouseEvent; |
| import java.util.Arrays; |
| import java.util.Comparator; |
| |
| public class MarkerType { |
| public static final MarkerType OVERRIDING_METHOD = new MarkerType(new NullableFunction<PsiElement, String>() { |
| @Override |
| public String fun(PsiElement element) { |
| PsiElement parent = getParentMethod(element); |
| if (!(parent instanceof PsiMethod)) return null; |
| PsiMethod method = (PsiMethod)parent; |
| |
| return calculateOverridingMethodTooltip(method, method != element.getParent()); |
| } |
| }, new LineMarkerNavigator(){ |
| @Override |
| public void browse(MouseEvent e, PsiElement element) { |
| PsiElement parent = getParentMethod(element); |
| if (!(parent instanceof PsiMethod)) return; |
| PsiMethod method = (PsiMethod)parent; |
| navigateToOverridingMethod(e, method, method != element.getParent()); |
| |
| } |
| }); |
| |
| @Nullable |
| public static String calculateOverridingMethodTooltip(PsiMethod method, boolean acceptSelf) { |
| PsiMethod[] superMethods = composeSuperMethods(method, acceptSelf); |
| if (superMethods == null) return null; |
| |
| PsiMethod superMethod = superMethods[0]; |
| boolean isAbstract = method.hasModifierProperty(PsiModifier.ABSTRACT); |
| boolean isSuperAbstract = superMethod.hasModifierProperty(PsiModifier.ABSTRACT); |
| |
| final boolean sameSignature = superMethod.getSignature(PsiSubstitutor.EMPTY).equals(method.getSignature(PsiSubstitutor.EMPTY)); |
| @NonNls final String key; |
| if (isSuperAbstract && !isAbstract){ |
| key = sameSignature ? "method.implements" : "method.implements.in"; |
| } |
| else{ |
| key = sameSignature ? "method.overrides" : "method.overrides.in"; |
| } |
| return GutterIconTooltipHelper.composeText(superMethods, "", DaemonBundle.message(key)); |
| } |
| |
| public static void navigateToOverridingMethod(MouseEvent e, PsiMethod method, boolean acceptSelf) { |
| PsiMethod[] superMethods = composeSuperMethods(method, acceptSelf); |
| if (superMethods == null) return; |
| boolean showMethodNames = !PsiUtil.allMethodsHaveSameSignature(superMethods); |
| PsiElementListNavigator.openTargets(e, superMethods, |
| DaemonBundle.message("navigation.title.super.method", method.getName()), |
| DaemonBundle.message("navigation.findUsages.title.super.method", method.getName()), |
| new MethodCellRenderer(showMethodNames)); |
| } |
| |
| @Nullable |
| private static PsiMethod[] composeSuperMethods(PsiMethod method, boolean acceptSelf) { |
| PsiMethod[] superMethods = method.findSuperMethods(false); |
| if (acceptSelf) { |
| superMethods = ArrayUtil.prepend(method, superMethods); |
| } |
| if (superMethods.length == 0) return null; |
| return superMethods; |
| } |
| |
| private static PsiElement getParentMethod(PsiElement element) { |
| final PsiElement parent = element.getParent(); |
| final PsiMethod interfaceMethod = LambdaUtil.getFunctionalInterfaceMethod(parent); |
| return interfaceMethod != null ? interfaceMethod : parent; |
| } |
| |
| public static final String SEARCHING_FOR_OVERRIDING_METHODS = "Searching for overriding methods"; |
| public static final MarkerType OVERRIDEN_METHOD = new MarkerType(new NullableFunction<PsiElement, String>() { |
| @Override |
| public String fun(PsiElement element) { |
| PsiElement parent = element.getParent(); |
| if (!(parent instanceof PsiMethod)) return null; |
| PsiMethod method = (PsiMethod)parent; |
| |
| return getOverriddenMethodTooltip(method); |
| } |
| }, new LineMarkerNavigator(){ |
| @Override |
| public void browse(MouseEvent e, PsiElement element) { |
| PsiElement parent = element.getParent(); |
| if (!(parent instanceof PsiMethod)) return; |
| navigateToOverriddenMethod(e, (PsiMethod)parent); |
| |
| } |
| }); |
| |
| public static String getOverriddenMethodTooltip(final PsiMethod method) { |
| PsiElementProcessor.CollectElementsWithLimit<PsiMethod> processor = new PsiElementProcessor.CollectElementsWithLimit<PsiMethod>(5); |
| OverridingMethodsSearch.search(method, true).forEach(new PsiElementProcessorAdapter<PsiMethod>(processor)); |
| |
| boolean isAbstract = method.hasModifierProperty(PsiModifier.ABSTRACT); |
| |
| if (processor.isOverflow()){ |
| return isAbstract ? DaemonBundle.message("method.is.implemented.too.many") : DaemonBundle.message("method.is.overridden.too.many"); |
| } |
| |
| PsiMethod[] overridings = processor.toArray(PsiMethod.EMPTY_ARRAY); |
| if (overridings.length == 0) { |
| final PsiClass aClass = method.getContainingClass(); |
| if (aClass != null && FunctionalExpressionSearch.search(aClass).findFirst() != null) { |
| return "Has functional implementations"; |
| } |
| return null; |
| } |
| |
| Comparator<PsiMethod> comparator = new MethodCellRenderer(false).getComparator(); |
| Arrays.sort(overridings, comparator); |
| |
| String start = isAbstract ? DaemonBundle.message("method.is.implemented.header") : DaemonBundle.message("method.is.overriden.header"); |
| @NonNls String pattern = " {1}"; |
| return GutterIconTooltipHelper.composeText(overridings, start, pattern); |
| } |
| |
| public static void navigateToOverriddenMethod(MouseEvent e, final PsiMethod method) { |
| if (DumbService.isDumb(method.getProject())) { |
| DumbService.getInstance(method.getProject()).showDumbModeNotification( |
| "Navigation to overriding classes is not possible during index update"); |
| return; |
| } |
| |
| final PsiElementProcessor.CollectElementsWithLimit<PsiMethod> collectProcessor = |
| new PsiElementProcessor.CollectElementsWithLimit<PsiMethod>(2, new THashSet<PsiMethod>()); |
| final PsiElementProcessor.CollectElementsWithLimit<PsiFunctionalExpression> collectExprProcessor = |
| new PsiElementProcessor.CollectElementsWithLimit<PsiFunctionalExpression>(2, new THashSet<PsiFunctionalExpression>()); |
| if (!ProgressManager.getInstance().runProcessWithProgressSynchronously(new Runnable() { |
| @Override |
| public void run() { |
| OverridingMethodsSearch.search(method, true).forEach(new PsiElementProcessorAdapter<PsiMethod>(collectProcessor)); |
| if (collectProcessor.getCollection().isEmpty()) { |
| final PsiClass aClass = ApplicationManager.getApplication().runReadAction(new Computable<PsiClass>() { |
| @Override |
| public PsiClass compute() { |
| return method.getContainingClass(); |
| } |
| }); |
| if (aClass != null) { |
| FunctionalExpressionSearch.search(aClass).forEach(new PsiElementProcessorAdapter<PsiFunctionalExpression>(collectExprProcessor)); |
| } |
| } |
| } |
| }, SEARCHING_FOR_OVERRIDING_METHODS, true, method.getProject(), (JComponent)e.getComponent())) { |
| return; |
| } |
| |
| final PsiMethod[] methodOverriders = collectProcessor.toArray(PsiMethod.EMPTY_ARRAY); |
| NavigatablePsiElement[] overridings = ArrayUtil.mergeArrays(methodOverriders, collectExprProcessor.toArray(PsiFunctionalExpression.EMPTY_ARRAY)); |
| if (overridings.length == 0) return; |
| boolean showMethodNames = !PsiUtil.allMethodsHaveSameSignature(methodOverriders); |
| MethodOrFunctionalExpressionCellRenderer renderer = new MethodOrFunctionalExpressionCellRenderer(showMethodNames); |
| Arrays.sort(overridings, renderer.getComparator()); |
| final OverridingMethodsUpdater methodsUpdater = new OverridingMethodsUpdater(method, renderer); |
| PsiElementListNavigator.openTargets(e, overridings, methodsUpdater.getCaption(overridings.length), "Overriding methods of " + method.getName(), renderer, methodsUpdater); |
| } |
| |
| public static final String SEARCHING_FOR_OVERRIDDEN_METHODS = "Searching for overridden methods"; |
| public static final MarkerType SUBCLASSED_CLASS = new MarkerType(new NullableFunction<PsiElement, String>() { |
| @Override |
| public String fun(PsiElement element) { |
| PsiElement parent = element.getParent(); |
| if (!(parent instanceof PsiClass)) return null; |
| PsiClass aClass = (PsiClass)parent; |
| return getSubclassedClassTooltip(aClass); |
| } |
| }, new LineMarkerNavigator(){ |
| @Override |
| public void browse(MouseEvent e, PsiElement element) { |
| final PsiElement parent = element.getParent(); |
| if (!(parent instanceof PsiClass)) return; |
| final PsiClass aClass = (PsiClass)parent; |
| |
| navigateToSubclassedClass(e, aClass); |
| } |
| }); |
| |
| public static String getSubclassedClassTooltip(PsiClass aClass) { |
| PsiElementProcessor.CollectElementsWithLimit<PsiClass> processor = new PsiElementProcessor.CollectElementsWithLimit<PsiClass>(5, new THashSet<PsiClass>()); |
| ClassInheritorsSearch.search(aClass, true).forEach(new PsiElementProcessorAdapter<PsiClass>(processor)); |
| |
| if (processor.isOverflow()) { |
| return aClass.isInterface() |
| ? DaemonBundle.message("interface.is.implemented.too.many") |
| : DaemonBundle.message("class.is.subclassed.too.many"); |
| } |
| |
| PsiClass[] subclasses = processor.toArray(PsiClass.EMPTY_ARRAY); |
| if (subclasses.length == 0) { |
| final PsiElementProcessor.CollectElementsWithLimit<PsiFunctionalExpression> functionalImplementations = |
| new PsiElementProcessor.CollectElementsWithLimit<PsiFunctionalExpression>(2, new THashSet<PsiFunctionalExpression>()); |
| FunctionalExpressionSearch.search(aClass).forEach(new PsiElementProcessorAdapter<PsiFunctionalExpression>(functionalImplementations)); |
| if (!functionalImplementations.getCollection().isEmpty()) { |
| return "Has functional implementations"; |
| } |
| return null; |
| } |
| |
| Comparator<PsiClass> comparator = new PsiClassListCellRenderer().getComparator(); |
| Arrays.sort(subclasses, comparator); |
| |
| String start = aClass.isInterface() |
| ? DaemonBundle.message("interface.is.implemented.by.header") |
| : DaemonBundle.message("class.is.subclassed.by.header"); |
| @NonNls String pattern = " {0}"; |
| return GutterIconTooltipHelper.composeText(subclasses, start, pattern); |
| } |
| |
| public static void navigateToSubclassedClass(MouseEvent e, final PsiClass aClass) { |
| if (DumbService.isDumb(aClass.getProject())) { |
| DumbService.getInstance(aClass.getProject()).showDumbModeNotification("Navigation to overriding methods is not possible during index update"); |
| return; |
| } |
| |
| final PsiElementProcessor.CollectElementsWithLimit<PsiClass> collectProcessor = new PsiElementProcessor.CollectElementsWithLimit<PsiClass>(2, new THashSet<PsiClass>()); |
| final PsiElementProcessor.CollectElementsWithLimit<PsiFunctionalExpression> collectExprProcessor = new PsiElementProcessor.CollectElementsWithLimit<PsiFunctionalExpression>(2, new THashSet<PsiFunctionalExpression>()); |
| if (!ProgressManager.getInstance().runProcessWithProgressSynchronously(new Runnable() { |
| @Override |
| public void run() { |
| ClassInheritorsSearch.search(aClass, true).forEach(new PsiElementProcessorAdapter<PsiClass>(collectProcessor)); |
| if (collectProcessor.getCollection().isEmpty()) { |
| FunctionalExpressionSearch.search(aClass).forEach(new PsiElementProcessorAdapter<PsiFunctionalExpression>(collectExprProcessor)); |
| } |
| } |
| }, SEARCHING_FOR_OVERRIDDEN_METHODS, true, aClass.getProject(), (JComponent)e.getComponent())) { |
| return; |
| } |
| |
| NavigatablePsiElement[] inheritors = ArrayUtil.mergeArrays(collectProcessor.toArray(PsiClass.EMPTY_ARRAY), |
| collectExprProcessor.toArray(PsiFunctionalExpression.EMPTY_ARRAY)); |
| if (inheritors.length == 0) return; |
| final PsiClassOrFunctionalExpressionListCellRenderer renderer = new PsiClassOrFunctionalExpressionListCellRenderer(); |
| final SubclassUpdater subclassUpdater = new SubclassUpdater(aClass, renderer); |
| Arrays.sort(inheritors, renderer.getComparator()); |
| PsiElementListNavigator.openTargets(e, inheritors, subclassUpdater.getCaption(inheritors.length), CodeInsightBundle.message("goto.implementation.findUsages.title", aClass.getName()), renderer, subclassUpdater); |
| } |
| |
| private final GutterIconNavigationHandler<PsiElement> handler; |
| private final Function<PsiElement, String> myTooltip; |
| |
| public MarkerType(@NotNull Function<PsiElement, String> tooltip, @NotNull final LineMarkerNavigator navigator) { |
| myTooltip = tooltip; |
| handler = new GutterIconNavigationHandler<PsiElement>() { |
| @Override |
| public void navigate(MouseEvent e, PsiElement elt) { |
| navigator.browse(e, elt); |
| } |
| }; |
| } |
| |
| @NotNull |
| public GutterIconNavigationHandler<PsiElement> getNavigationHandler() { |
| return handler; |
| } |
| |
| @NotNull |
| public Function<PsiElement, String> getTooltip() { |
| return myTooltip; |
| } |
| |
| private static class SubclassUpdater extends ListBackgroundUpdaterTask { |
| private final PsiClass myClass; |
| private final PsiClassOrFunctionalExpressionListCellRenderer myRenderer; |
| |
| public SubclassUpdater(PsiClass aClass, PsiClassOrFunctionalExpressionListCellRenderer renderer) { |
| super(aClass.getProject(), SEARCHING_FOR_OVERRIDDEN_METHODS); |
| myClass = aClass; |
| myRenderer = renderer; |
| } |
| |
| @Override |
| public String getCaption(int size) { |
| return myClass.isInterface() |
| ? CodeInsightBundle.message("goto.implementation.chooserTitle", myClass.getName(), size) |
| : DaemonBundle.message("navigation.title.subclass", myClass.getName(), size); |
| } |
| |
| @Override |
| public void run(@NotNull final ProgressIndicator indicator) { |
| super.run(indicator); |
| ClassInheritorsSearch.search(myClass, ApplicationManager.getApplication().runReadAction(new Computable<SearchScope>() { |
| @Override |
| public SearchScope compute() { |
| return myClass.getUseScope(); |
| } |
| }), true).forEach(new CommonProcessors.CollectProcessor<PsiClass>() { |
| @Override |
| public boolean process(final PsiClass o) { |
| if (!updateComponent(o, myRenderer.getComparator())) { |
| indicator.cancel(); |
| } |
| indicator.checkCanceled(); |
| return super.process(o); |
| } |
| }); |
| |
| FunctionalExpressionSearch.search(myClass).forEach(new CommonProcessors.CollectProcessor<PsiFunctionalExpression>() { |
| @Override |
| public boolean process(final PsiFunctionalExpression expr) { |
| if (!updateComponent(expr, myRenderer.getComparator())) { |
| indicator.cancel(); |
| } |
| indicator.checkCanceled(); |
| return super.process(expr); |
| } |
| }); |
| } |
| |
| } |
| |
| private static class OverridingMethodsUpdater extends ListBackgroundUpdaterTask { |
| private final PsiMethod myMethod; |
| private final PsiElementListCellRenderer myRenderer; |
| |
| public OverridingMethodsUpdater(PsiMethod method, PsiElementListCellRenderer renderer) { |
| super(method.getProject(), SEARCHING_FOR_OVERRIDING_METHODS); |
| myMethod = method; |
| myRenderer = renderer; |
| } |
| |
| @Override |
| public String getCaption(int size) { |
| return myMethod.hasModifierProperty(PsiModifier.ABSTRACT) ? |
| DaemonBundle.message("navigation.title.implementation.method", myMethod.getName(), size) : |
| DaemonBundle.message("navigation.title.overrider.method", myMethod.getName(), size); |
| } |
| |
| @Override |
| public void run(@NotNull final ProgressIndicator indicator) { |
| super.run(indicator); |
| OverridingMethodsSearch.search(myMethod, true).forEach( |
| new CommonProcessors.CollectProcessor<PsiMethod>() { |
| @Override |
| public boolean process(PsiMethod psiMethod) { |
| if (!updateComponent(psiMethod, myRenderer.getComparator())) { |
| indicator.cancel(); |
| } |
| indicator.checkCanceled(); |
| return super.process(psiMethod); |
| } |
| }); |
| final PsiClass psiClass = ApplicationManager.getApplication().runReadAction(new Computable<PsiClass>() { |
| @Override |
| public PsiClass compute() { |
| return myMethod.getContainingClass(); |
| } |
| }); |
| FunctionalExpressionSearch.search(psiClass).forEach(new CommonProcessors.CollectProcessor<PsiFunctionalExpression>() { |
| @Override |
| public boolean process(final PsiFunctionalExpression expr) { |
| if (!updateComponent(expr, myRenderer.getComparator())) { |
| indicator.cancel(); |
| } |
| indicator.checkCanceled(); |
| return super.process(expr); |
| } |
| }); |
| } |
| } |
| } |