diff --git a/java/java-impl/src/com/intellij/codeInsight/RecursionUtil.java b/java/java-impl/src/com/intellij/codeInsight/RecursionUtil.java new file mode 100644 index 000000000000..00e0bf788c60 --- /dev/null +++ b/java/java-impl/src/com/intellij/codeInsight/RecursionUtil.java @@ -0,0 +1,44 @@ +/* + * Copyright 2000-2012 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.codeInsight; + +import com.intellij.openapi.util.Comparing; +import com.intellij.psi.PsiExpression; +import com.intellij.psi.PsiMethod; +import com.intellij.psi.PsiMethodCallExpression; +import com.intellij.psi.PsiThisExpression; +import com.intellij.psi.util.PsiTreeUtil; +import org.jetbrains.annotations.NotNull; + +/** + * @author Danila Ponomarenko + */ +public class RecursionUtil { + public static boolean isRecursiveMethodCall(@NotNull PsiMethodCallExpression methodCall) { + final PsiMethod method = PsiTreeUtil.getParentOfType(methodCall, PsiMethod.class); + if (method == null) { + return false; + } + + final PsiMethod resolvedMethod = methodCall.resolveMethod(); + + if (!Comparing.equal(method, resolvedMethod)) { + return false; + } + final PsiExpression qualifier = methodCall.getMethodExpression().getQualifierExpression(); + return qualifier == null || qualifier instanceof PsiThisExpression; + } +} diff --git a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/RecursiveCallLineMarkerProvider.java b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/RecursiveCallLineMarkerProvider.java new file mode 100644 index 000000000000..85f27d544401 --- /dev/null +++ b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/RecursiveCallLineMarkerProvider.java @@ -0,0 +1,196 @@ +/* + * Copyright 2000-2012 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.codeInsight.daemon.impl; + +import com.intellij.codeHighlighting.Pass; +import com.intellij.codeInsight.RecursionUtil; +import com.intellij.codeInsight.daemon.GutterIconNavigationHandler; +import com.intellij.codeInsight.daemon.LineMarkerInfo; +import com.intellij.codeInsight.daemon.LineMarkerProvider; +import com.intellij.icons.AllIcons; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.ScrollType; +import com.intellij.openapi.editor.markup.GutterIconRenderer; +import com.intellij.openapi.project.DumbAware; +import com.intellij.openapi.ui.popup.JBPopupFactory; +import com.intellij.openapi.util.TextRange; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.psi.*; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.psi.util.PsiUtilBase; +import com.intellij.ui.awt.RelativePoint; +import com.intellij.ui.components.JBLabel; +import com.intellij.ui.components.JBList; +import com.intellij.util.Function; +import com.intellij.util.FunctionUtil; +import com.intellij.util.NotNullFunction; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; +import java.awt.event.MouseEvent; +import java.util.*; + +/** + * @author Danila Ponomarenko + */ +public class RecursiveCallLineMarkerProvider implements LineMarkerProvider, DumbAware { + private static final Icon RECURSIVE_METHOD_ICON = AllIcons.Gutter.RecursiveMethod; + + @Override + public LineMarkerInfo getLineMarkerInfo(@NotNull PsiElement element) { + return null; //do nothing + } + + @Override + public void collectSlowLineMarkers(@NotNull List elements, @NotNull Collection result) { + final Set recMethods = new HashSet(); + + for (PsiElement element : elements) { + if (element instanceof PsiMethodCallExpression) { + final PsiMethodCallExpression methodCall = (PsiMethodCallExpression)element; + final PsiMethod method = methodCall.resolveMethod(); + if (method != null && !recMethods.contains(method) && RecursionUtil.isRecursiveMethodCall(methodCall)) { + recMethods.add(method); + result.add(RecursiveMethodMarkerInfo.create(method)); + } + } + } + } + + @NotNull + private static List getRecursiveMethodCalls(final @NotNull PsiMethod method) { + final List result = new ArrayList(); + ApplicationManager.getApplication().runReadAction(new Runnable() { + @Override + public void run() { + method.accept(new JavaRecursiveElementWalkingVisitor() { + @Override + public void visitMethodCallExpression(PsiMethodCallExpression expression) { + if (RecursionUtil.isRecursiveMethodCall(expression)) { + result.add(expression); + } + super.visitMethodCallExpression(expression); + } + }); + } + }); + return result; + } + + private static class RecursiveMethodMarkerInfo extends LineMarkerInfo { + public RecursiveMethodMarkerInfo(PsiMethod method, + TextRange range, + Icon icon, + int markers, + Function constant, + GutterIconNavigationHandler handler, + GutterIconRenderer.Alignment left) { + super(method, range, icon, markers, constant, handler, left); + } + + @NotNull + public static RecursiveMethodMarkerInfo create(@NotNull PsiMethod method){ + final PsiIdentifier identifier = method.getNameIdentifier(); + return new RecursiveMethodMarkerInfo(method, + identifier != null ? identifier.getTextRange() : method.getTextRange(), + RECURSIVE_METHOD_ICON, + Pass.UPDATE_OVERRIDEN_MARKERS, + FunctionUtil.constant("Potentially recursive method"), + getNavigationHandler(method), + GutterIconRenderer.Alignment.LEFT + ); + } + + + private static GutterIconNavigationHandler getNavigationHandler(final @NotNull PsiMethod method) { + return new GutterIconNavigationHandler() { + @Override + public void navigate(@NotNull MouseEvent e, PsiMethod elt) { + final List calls = getRecursiveMethodCalls(method); + if (calls.size() == 1) { + navigateToMethodCall(calls.get(0)); + } + else { + showPopup(e, calls); + } + } + }; + } + + private static void showPopup(@NotNull MouseEvent e, @NotNull List calls) { + final JBList list = new JBList(calls); + list.setFixedCellHeight(20); + list.installCellRenderer(createCellRenderer()); + JBPopupFactory.getInstance(). + createListPopupBuilder(list). + setItemChoosenCallback(createItemChosenCallback(e, list)). + createPopup().show(new RelativePoint(e)); + } + + + @NotNull + private static Runnable createItemChosenCallback(final MouseEvent e, final @NotNull JBList list) { + return new Runnable() { + @Override + public void run() { + final Object value = list.getSelectedValue(); + if (!(value instanceof PsiMethodCallExpression)) { + return; + } + navigateToMethodCall((PsiMethodCallExpression)value); + } + }; + } + + private static void navigateToMethodCall(@NotNull PsiMethodCallExpression methodCall) { + final PsiIdentifier identifier = PsiTreeUtil.getChildOfType(methodCall.getMethodExpression(), PsiIdentifier.class); + if (identifier != null) { + navigateTo(identifier); + }else { + navigateTo(methodCall); + } + } + + private static void navigateTo(@NotNull PsiElement element) { + final Editor editor = PsiUtilBase.findEditor(element); + if (editor == null) return; + + editor.getCaretModel().moveToOffset(element.getTextRange().getStartOffset()); + editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); + } + + + @NotNull + private static NotNullFunction createCellRenderer() { + return new NotNullFunction() { + @NotNull + @Override + public JComponent fun(Object o) { + if (!(o instanceof PsiMethodCallExpression)) { + return new JBLabel(); + } + + final PsiMethodCallExpression methodCall = (PsiMethodCallExpression)o; + String text = StringUtil.first(methodCall.getText(), 100, true).replace('\n', ' '); + + return new JBLabel(text, RECURSIVE_METHOD_ICON, SwingConstants.LEFT); + } + }; + } + } +} + diff --git a/java/java-impl/src/com/intellij/codeInsight/folding/impl/RecursiveMethodCallFoldingBuilder.java b/java/java-impl/src/com/intellij/codeInsight/folding/impl/RecursiveMethodCallFoldingBuilder.java index 5cf8424aebc1..126c1b7f6378 100644 --- a/java/java-impl/src/com/intellij/codeInsight/folding/impl/RecursiveMethodCallFoldingBuilder.java +++ b/java/java-impl/src/com/intellij/codeInsight/folding/impl/RecursiveMethodCallFoldingBuilder.java @@ -15,12 +15,12 @@ */ package com.intellij.codeInsight.folding.impl; +import com.intellij.codeInsight.RecursionUtil; import com.intellij.codeInsight.folding.JavaCodeFoldingSettings; import com.intellij.lang.ASTNode; import com.intellij.lang.folding.FoldingBuilderEx; import com.intellij.lang.folding.FoldingDescriptor; import com.intellij.openapi.editor.Document; -import com.intellij.openapi.util.TextRange; import com.intellij.psi.*; import com.intellij.psi.util.PsiTreeUtil; import org.jetbrains.annotations.NotNull; @@ -43,7 +43,7 @@ public class RecursiveMethodCallFoldingBuilder extends FoldingBuilderEx { new JavaRecursiveElementWalkingVisitor() { @Override public void visitMethodCallExpression(PsiMethodCallExpression expression) { - if (isRecursiveMethodCall(expression)) { + if (RecursionUtil.isRecursiveMethodCall(expression)) { final PsiIdentifier identifier = PsiTreeUtil.getChildOfType(expression.getMethodExpression(), PsiIdentifier.class); if (identifier != null) { result.add(new FoldingDescriptor(identifier, identifier.getTextRange())); @@ -75,23 +75,4 @@ public class RecursiveMethodCallFoldingBuilder extends FoldingBuilderEx { return element.getText(); } - - private static boolean isRecursiveMethodCall(@NotNull PsiMethodCallExpression methodCall) { - final PsiMethod referencedMethod = methodCall.resolveMethod(); - - if (referencedMethod == null || !referencedMethod.isValid() || !methodCall.isValid()) { - return false; - } - - final PsiFile methodCallFile = methodCall.getContainingFile(); - final PsiFile methodFile = referencedMethod.getContainingFile(); - - if (methodCallFile == null || methodFile == null || !methodCallFile.equals(methodFile)) { - return false; - } - - final TextRange rmRange = referencedMethod.getTextRange(); - final int mcOffset = methodCall.getTextRange().getStartOffset(); - return rmRange != null && rmRange.contains(mcOffset); - } } diff --git a/resources/src/META-INF/IdeaPlugin.xml b/resources/src/META-INF/IdeaPlugin.xml index 6d54f6c7e979..60af77201bfc 100644 --- a/resources/src/META-INF/IdeaPlugin.xml +++ b/resources/src/META-INF/IdeaPlugin.xml @@ -762,6 +762,7 @@ +