From 9d07ec2efab50ba6749e5f6d1cf645df3233736a Mon Sep 17 00:00:00 2001 From: Eugene Zhuravlev Date: Sun, 27 Oct 2013 00:06:58 +0200 Subject: [PATCH] lambda support in smart step into --- .../actions/JavaSmartStepIntoHandler.java | 13 ++- .../actions/JvmSmartStepIntoHandler.java | 86 ++++---------- .../actions/LambdaSmartStepTarget.java | 70 ++++++++++++ .../actions/MethodSmartStepTarget.java | 64 +++++++++++ .../actions/PsiMethodListPopupStep.java | 62 +++++++---- .../debugger/actions/SmartStepTarget.java | 49 ++++++++ .../engine/AnonymousClassMethodFilter.java | 54 +++++++++ .../engine/BasicStepMethodFilter.java | 74 ++++++++++++ .../engine/BreakpointStepMethodFilter.java | 28 +++++ .../debugger/engine/DebugProcessEvents.java | 4 +- .../debugger/engine/DebugProcessImpl.java | 4 +- .../debugger/engine/LambdaMethodFilter.java | 82 ++++++++++++++ .../debugger/engine/MethodFilter.java | 86 +------------- .../intellij/debugger/engine/RequestHint.java | 4 +- .../ui/breakpoints/BreakpointManager.java | 4 +- .../ui/breakpoints/StepIntoBreakpoint.java | 105 +++++++++++++++++- 16 files changed, 600 insertions(+), 189 deletions(-) create mode 100644 java/debugger/impl/src/com/intellij/debugger/actions/LambdaSmartStepTarget.java create mode 100644 java/debugger/impl/src/com/intellij/debugger/actions/MethodSmartStepTarget.java create mode 100644 java/debugger/impl/src/com/intellij/debugger/actions/SmartStepTarget.java create mode 100644 java/debugger/impl/src/com/intellij/debugger/engine/AnonymousClassMethodFilter.java create mode 100644 java/debugger/impl/src/com/intellij/debugger/engine/BasicStepMethodFilter.java create mode 100644 java/debugger/impl/src/com/intellij/debugger/engine/BreakpointStepMethodFilter.java create mode 100644 java/debugger/impl/src/com/intellij/debugger/engine/LambdaMethodFilter.java diff --git a/java/debugger/impl/src/com/intellij/debugger/actions/JavaSmartStepIntoHandler.java b/java/debugger/impl/src/com/intellij/debugger/actions/JavaSmartStepIntoHandler.java index 2fe2cc3508e2..a0a2146aca6f 100644 --- a/java/debugger/impl/src/com/intellij/debugger/actions/JavaSmartStepIntoHandler.java +++ b/java/debugger/impl/src/com/intellij/debugger/actions/JavaSmartStepIntoHandler.java @@ -44,7 +44,7 @@ public class JavaSmartStepIntoHandler extends JvmSmartStepIntoHandler { @Override @NotNull - public List findSmartStepTargets(final SourcePosition position) { + public List findSmartStepTargets(final SourcePosition position) { final int line = position.getLine(); if (line < 0) { return Collections.emptyList(); // the document has been changed @@ -77,11 +77,12 @@ public class JavaSmartStepIntoHandler extends JvmSmartStepIntoHandler { while(true); //noinspection unchecked - final List targets = new OrderedSet(); + final List targets = new OrderedSet(); final PsiElementVisitor methodCollector = new JavaRecursiveElementVisitor() { final Stack myContextStack = new Stack(); final Stack myParamNameStack = new Stack(); + private int myNextLambdaExpressionOrdinal = 0; @Nullable private String getCurrentParamName() { @@ -91,10 +92,14 @@ public class JavaSmartStepIntoHandler extends JvmSmartStepIntoHandler { @Override public void visitAnonymousClass(PsiAnonymousClass aClass) { for (PsiMethod psiMethod : aClass.getMethods()) { - targets.add(new StepTarget(psiMethod, getCurrentParamName(), psiMethod.getBody(), true)); + targets.add(new MethodSmartStepTarget(psiMethod, getCurrentParamName(), psiMethod.getBody(), true)); } } + public void visitLambdaExpression(PsiLambdaExpression expression) { + targets.add(new LambdaSmartStepTarget(expression, getCurrentParamName(), expression.getBody(), myNextLambdaExpressionOrdinal++)); + } + @Override public void visitStatement(PsiStatement statement) { if (lineRange.intersects(statement.getTextRange())) { @@ -130,7 +135,7 @@ public class JavaSmartStepIntoHandler extends JvmSmartStepIntoHandler { final PsiMethod psiMethod = expression.resolveMethod(); if (psiMethod != null) { myContextStack.push(psiMethod); - targets.add(new StepTarget( + targets.add(new MethodSmartStepTarget( psiMethod, null, expression instanceof PsiMethodCallExpression? diff --git a/java/debugger/impl/src/com/intellij/debugger/actions/JvmSmartStepIntoHandler.java b/java/debugger/impl/src/com/intellij/debugger/actions/JvmSmartStepIntoHandler.java index d50416536443..8f5b109c459e 100644 --- a/java/debugger/impl/src/com/intellij/debugger/actions/JvmSmartStepIntoHandler.java +++ b/java/debugger/impl/src/com/intellij/debugger/actions/JvmSmartStepIntoHandler.java @@ -16,6 +16,9 @@ package com.intellij.debugger.actions; import com.intellij.debugger.SourcePosition; +import com.intellij.debugger.engine.AnonymousClassMethodFilter; +import com.intellij.debugger.engine.BasicStepMethodFilter; +import com.intellij.debugger.engine.LambdaMethodFilter; import com.intellij.debugger.engine.MethodFilter; import com.intellij.debugger.impl.DebuggerSession; import com.intellij.openapi.editor.Editor; @@ -44,68 +47,10 @@ public abstract class JvmSmartStepIntoHandler { public static ExtensionPointName EP_NAME = ExtensionPointName.create("com.intellij.debugger.jvmSmartStepIntoHandler"); @NotNull - public abstract List findSmartStepTargets(SourcePosition position); + public abstract List findSmartStepTargets(SourcePosition position); public abstract boolean isAvailable(SourcePosition position); - public class StepTarget { - private final PsiMethod myMethod; - private final PsiElement myHighlightElement; - private final String myLabel; - private final boolean myNeedBreakpointRequest; - - public StepTarget(@NotNull PsiMethod method) { - this(method, null, null, false); - } - - public StepTarget(@NotNull PsiMethod method, @Nullable String additionalLabel, @Nullable PsiElement highlightElement, boolean needBreakpointRequest) { - myMethod = method; - myHighlightElement = highlightElement; - myLabel = additionalLabel; - myNeedBreakpointRequest = needBreakpointRequest; - } - - @Nullable - public PsiElement getHighlightElement() { - return myHighlightElement; - } - - @Nullable - public String getMethodLabel() { - return myLabel; - } - - @NotNull - public PsiMethod getMethod() { - return myMethod; - } - - public boolean needsBreakpointRequest() { - return myNeedBreakpointRequest; - } - - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - - final StepTarget that = (StepTarget)o; - - if (!myMethod.equals(that.myMethod)) { - return false; - } - - return true; - } - - public int hashCode() { - return myMethod.hashCode(); - } - } - /** * Override this if you haven't PsiMethod, like in Kotlin. * @param position @@ -114,16 +59,16 @@ public abstract class JvmSmartStepIntoHandler { * @return false to continue for another handler or for default action (step into) */ public boolean doSmartStep(SourcePosition position, final DebuggerSession session, TextEditor fileEditor) { - final List targets = findSmartStepTargets(position); + final List targets = findSmartStepTargets(position); if (!targets.isEmpty()) { - final StepTarget firstTarget = targets.get(0); + final SmartStepTarget firstTarget = targets.get(0); if (targets.size() == 1) { session.stepInto(true, createMethodFilter(firstTarget)); } else { final Editor editor = fileEditor.getEditor(); final PsiMethodListPopupStep popupStep = new PsiMethodListPopupStep(editor, targets, new PsiMethodListPopupStep.OnChooseRunnable() { - public void execute(StepTarget chosenTarget) { + public void execute(SmartStepTarget chosenTarget) { session.stepInto(true, createMethodFilter(chosenTarget)); } }); @@ -132,7 +77,7 @@ public abstract class JvmSmartStepIntoHandler { public void valueChanged(ListSelectionEvent e) { popupStep.getScopeHighlighter().dropHighlight(); if (!e.getValueIsAdjusting()) { - final StepTarget selectedTarget = (StepTarget)((JBList)e.getSource()).getSelectedValue(); + final SmartStepTarget selectedTarget = (SmartStepTarget)((JBList)e.getSource()).getSelectedValue(); if (selectedTarget != null) { highlightTarget(popupStep, selectedTarget); } @@ -148,7 +93,7 @@ public abstract class JvmSmartStepIntoHandler { return false; } - private static void highlightTarget(PsiMethodListPopupStep popupStep, StepTarget target) { + private static void highlightTarget(PsiMethodListPopupStep popupStep, SmartStepTarget target) { final PsiElement highlightElement = target.getHighlightElement(); if (highlightElement != null) { popupStep.getScopeHighlighter().highlight(highlightElement, Arrays.asList(highlightElement)); @@ -161,7 +106,16 @@ public abstract class JvmSmartStepIntoHandler { * @param stepTarget * @return SmartStepFilter */ - protected MethodFilter createMethodFilter(StepTarget stepTarget) { - return new MethodFilter(stepTarget.getMethod(), stepTarget.needsBreakpointRequest()); + @Nullable + protected MethodFilter createMethodFilter(SmartStepTarget stepTarget) { + if (stepTarget instanceof MethodSmartStepTarget) { + final PsiMethod method = ((MethodSmartStepTarget)stepTarget).getMethod(); + return stepTarget.needsBreakpointRequest()? new AnonymousClassMethodFilter(method) : new BasicStepMethodFilter(method); + } + if (stepTarget instanceof LambdaSmartStepTarget) { + final LambdaSmartStepTarget lambdaTarget = (LambdaSmartStepTarget)stepTarget; + return new LambdaMethodFilter(lambdaTarget.getLambda(), lambdaTarget.getOrdinal()); + } + return null; } } diff --git a/java/debugger/impl/src/com/intellij/debugger/actions/LambdaSmartStepTarget.java b/java/debugger/impl/src/com/intellij/debugger/actions/LambdaSmartStepTarget.java new file mode 100644 index 000000000000..da90bafd2a41 --- /dev/null +++ b/java/debugger/impl/src/com/intellij/debugger/actions/LambdaSmartStepTarget.java @@ -0,0 +1,70 @@ +/* + * 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. + */ +package com.intellij.debugger.actions; + +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiLambdaExpression; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * @author Eugene Zhuravlev + * Date: 10/25/13 + */ +public class LambdaSmartStepTarget extends SmartStepTarget{ + private final PsiLambdaExpression myLambda; + private final int myOrdinal; + + public LambdaSmartStepTarget(@NotNull PsiLambdaExpression lambda, @Nullable String label, @Nullable PsiElement highlightElement, int ordinal) { + super(label, highlightElement, true); + myLambda = lambda; + myOrdinal = ordinal; + } + + public PsiLambdaExpression getLambda() { + return myLambda; + } + + public int getOrdinal() { + return myOrdinal; + } + + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + final LambdaSmartStepTarget that = (LambdaSmartStepTarget)o; + + if (myOrdinal != that.myOrdinal) { + return false; + } + if (!myLambda.equals(that.myLambda)) { + return false; + } + + return true; + } + + public int hashCode() { + int result = myLambda.hashCode(); + result = 31 * result + myOrdinal; + return result; + } +} diff --git a/java/debugger/impl/src/com/intellij/debugger/actions/MethodSmartStepTarget.java b/java/debugger/impl/src/com/intellij/debugger/actions/MethodSmartStepTarget.java new file mode 100644 index 000000000000..461f89a6d594 --- /dev/null +++ b/java/debugger/impl/src/com/intellij/debugger/actions/MethodSmartStepTarget.java @@ -0,0 +1,64 @@ +/* + * 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. + */ +package com.intellij.debugger.actions; + +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiMethod; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * @author Eugene Zhuravlev + * Date: 10/25/13 + */ +public class MethodSmartStepTarget extends SmartStepTarget{ + private final PsiMethod myMethod; + + public MethodSmartStepTarget(@NotNull PsiMethod method) { + this(method, null, null, false); + } + + public MethodSmartStepTarget(@NotNull PsiMethod method, @Nullable String label, @Nullable PsiElement highlightElement, boolean needBreakpointRequest) { + super(label, highlightElement, needBreakpointRequest); + myMethod = method; + } + + @NotNull + public PsiMethod getMethod() { + return myMethod; + } + + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + final MethodSmartStepTarget that = (MethodSmartStepTarget)o; + + if (!myMethod.equals(that.myMethod)) { + return false; + } + + return true; + } + + public int hashCode() { + return myMethod.hashCode(); + } +} diff --git a/java/debugger/impl/src/com/intellij/debugger/actions/PsiMethodListPopupStep.java b/java/debugger/impl/src/com/intellij/debugger/actions/PsiMethodListPopupStep.java index 3e0676a7f0fe..335a188cbe3b 100644 --- a/java/debugger/impl/src/com/intellij/debugger/actions/PsiMethodListPopupStep.java +++ b/java/debugger/impl/src/com/intellij/debugger/actions/PsiMethodListPopupStep.java @@ -17,8 +17,10 @@ package com.intellij.debugger.actions; import com.intellij.codeInsight.unwrap.ScopeHighlighter; import com.intellij.debugger.DebuggerBundle; +import com.intellij.icons.AllIcons; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.ui.popup.*; +import com.intellij.psi.PsiLambdaExpression; import com.intellij.psi.PsiMethod; import com.intellij.psi.PsiSubstitutor; import com.intellij.psi.util.PsiFormatUtil; @@ -31,16 +33,16 @@ import java.util.List; * @author Eugene Zhuravlev * Date: Nov 21, 2006 */ -class PsiMethodListPopupStep implements ListPopupStep { - private final List myTargets; +class PsiMethodListPopupStep implements ListPopupStep { + private final List myTargets; private final OnChooseRunnable myStepRunnable; private final ScopeHighlighter myScopeHighlighter; public interface OnChooseRunnable { - void execute(JvmSmartStepIntoHandler.StepTarget stepTarget); + void execute(SmartStepTarget stepTarget); } - public PsiMethodListPopupStep(Editor editor, final List targets, final OnChooseRunnable stepRunnable) { + public PsiMethodListPopupStep(Editor editor, final List targets, final OnChooseRunnable stepRunnable) { myTargets = targets; myScopeHighlighter = new ScopeHighlighter(editor); myStepRunnable = stepRunnable; @@ -52,33 +54,49 @@ class PsiMethodListPopupStep implements ListPopupStep getValues() { + public List getValues() { return myTargets; } - public boolean isSelectable(JvmSmartStepIntoHandler.StepTarget value) { + public boolean isSelectable(SmartStepTarget value) { return true; } - public Icon getIconFor(JvmSmartStepIntoHandler.StepTarget aValue) { - return aValue.getMethod().getIcon(0); + public Icon getIconFor(SmartStepTarget aValue) { + if (aValue instanceof MethodSmartStepTarget) { + return ((MethodSmartStepTarget)aValue).getMethod().getIcon(0); + } + if (aValue instanceof LambdaSmartStepTarget) { + return AllIcons.Nodes.Function; + } + return null; } @NotNull - public String getTextFor(JvmSmartStepIntoHandler.StepTarget value) { - final PsiMethod method = value.getMethod(); - final String methodLabel = value.getMethodLabel(); - final String methodRender = PsiFormatUtil.formatMethod( - method, - PsiSubstitutor.EMPTY, - PsiFormatUtil.SHOW_NAME | PsiFormatUtil.SHOW_PARAMETERS, - PsiFormatUtil.SHOW_TYPE, - 999 - ); - return methodLabel != null? methodLabel + methodRender : methodRender; + public String getTextFor(SmartStepTarget value) { + final String label = value.getLabel(); + final String formatted; + if (value instanceof MethodSmartStepTarget) { + final PsiMethod method = ((MethodSmartStepTarget)value).getMethod(); + formatted = PsiFormatUtil.formatMethod( + method, + PsiSubstitutor.EMPTY, + PsiFormatUtil.SHOW_NAME | PsiFormatUtil.SHOW_PARAMETERS, + PsiFormatUtil.SHOW_TYPE, + 999 + ); + } + else if (value instanceof LambdaSmartStepTarget){ + final PsiLambdaExpression lambda = ((LambdaSmartStepTarget)value).getLambda(); + formatted = PsiFormatUtil.formatType(lambda.getType(), 0, PsiSubstitutor.EMPTY); + } + else { + formatted = ""; + } + return label != null? label + formatted : formatted; } - public ListSeparator getSeparatorAbove(JvmSmartStepIntoHandler.StepTarget value) { + public ListSeparator getSeparatorAbove(SmartStepTarget value) { return null; } @@ -90,7 +108,7 @@ class PsiMethodListPopupStep implements ListPopupStep" : psiMethod.getName(); + myTargetMethodSignature = JVMNameUtil.getJVMSignature(psiMethod); + } + + @NotNull + public String getMethodName() { + return myTargetMethodName; + } + + public boolean locationMatches(DebugProcessImpl process, Location location) throws EvaluateException { + final Method method = location.method(); + if (!myTargetMethodName.equals(method.name())) { + return false; + } + if (myTargetMethodSignature != null) { + if (!signatureMatches(method, myTargetMethodSignature.getName(process))) { + return false; + } + } + return DebuggerUtilsEx.isAssignableFrom(myDeclaringClassName.getName(process), location.declaringType()); + } + + private static boolean signatureMatches(Method method, final String expectedSignature) throws EvaluateException { + if (expectedSignature.equals(method.signature())) { + return true; + } + // check if there are any bridge methods that match + for (Method candidate : method.declaringType().methodsByName(method.name())) { + if (candidate != method && candidate.isBridge() && expectedSignature.equals(candidate.signature())) { + return true; + } + } + return false; + } +} diff --git a/java/debugger/impl/src/com/intellij/debugger/engine/BreakpointStepMethodFilter.java b/java/debugger/impl/src/com/intellij/debugger/engine/BreakpointStepMethodFilter.java new file mode 100644 index 000000000000..adfe975b8398 --- /dev/null +++ b/java/debugger/impl/src/com/intellij/debugger/engine/BreakpointStepMethodFilter.java @@ -0,0 +1,28 @@ +/* + * 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. + */ +package com.intellij.debugger.engine; + +import com.intellij.debugger.SourcePosition; +import org.jetbrains.annotations.Nullable; + +/** + * @author Eugene Zhuravlev + * Date: 10/26/13 + */ +public interface BreakpointStepMethodFilter extends MethodFilter{ + @Nullable + SourcePosition getBreakpointPosition(); +} diff --git a/java/debugger/impl/src/com/intellij/debugger/engine/DebugProcessEvents.java b/java/debugger/impl/src/com/intellij/debugger/engine/DebugProcessEvents.java index 58e54df33a56..58f4c7b70bc4 100644 --- a/java/debugger/impl/src/com/intellij/debugger/engine/DebugProcessEvents.java +++ b/java/debugger/impl/src/com/intellij/debugger/engine/DebugProcessEvents.java @@ -394,8 +394,8 @@ public class DebugProcessEvents extends DebugProcessImpl { getSuspendManager().voteSuspend(suspendContext); if (hint != null) { final MethodFilter methodFilter = hint.getMethodFilter(); - if (methodFilter != null && methodFilter.getBreakpointPosition() == null && !hint.wasStepTargetMethodMatched()) { - final String message = "Method " + methodFilter.getMethodName() + "() has not been called"; + if (methodFilter instanceof BasicStepMethodFilter && !hint.wasStepTargetMethodMatched()) { + final String message = "Method " + ((BasicStepMethodFilter)methodFilter).getMethodName() + "() has not been called"; XDebugSessionImpl.NOTIFICATION_GROUP.createNotification(message, MessageType.INFO).notify(project); } } diff --git a/java/debugger/impl/src/com/intellij/debugger/engine/DebugProcessImpl.java b/java/debugger/impl/src/com/intellij/debugger/engine/DebugProcessImpl.java index 7416d1f8d630..53c826a5eb5c 100644 --- a/java/debugger/impl/src/com/intellij/debugger/engine/DebugProcessImpl.java +++ b/java/debugger/impl/src/com/intellij/debugger/engine/DebugProcessImpl.java @@ -1378,7 +1378,9 @@ public abstract class DebugProcessImpl implements DebugProcess { super(suspendContext); myForcedIgnoreFilters = ignoreFilters || methodFilter != null; mySmartStepFilter = methodFilter; - myBreakpoint = methodFilter == null ? null : DebuggerManagerEx.getInstanceEx(myProject).getBreakpointManager().addStepIntoBreakpoint(methodFilter); + myBreakpoint = methodFilter instanceof BreakpointStepMethodFilter ? + DebuggerManagerEx.getInstanceEx(myProject).getBreakpointManager().addStepIntoBreakpoint(((BreakpointStepMethodFilter)methodFilter)) : + null; } public void contextAction() { diff --git a/java/debugger/impl/src/com/intellij/debugger/engine/LambdaMethodFilter.java b/java/debugger/impl/src/com/intellij/debugger/engine/LambdaMethodFilter.java new file mode 100644 index 000000000000..7e4fd940c772 --- /dev/null +++ b/java/debugger/impl/src/com/intellij/debugger/engine/LambdaMethodFilter.java @@ -0,0 +1,82 @@ +/* + * 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. + */ +package com.intellij.debugger.engine; + +import com.intellij.debugger.SourcePosition; +import com.intellij.debugger.engine.evaluation.EvaluateException; +import com.intellij.psi.PsiCodeBlock; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiLambdaExpression; +import com.intellij.psi.PsiStatement; +import com.sun.jdi.Location; +import com.sun.jdi.Method; +import org.jetbrains.annotations.Nullable; + +/** + * @author Eugene Zhuravlev + * Date: 10/26/13 + */ +public class LambdaMethodFilter implements BreakpointStepMethodFilter{ + private static final String LAMBDA_METHOD_PREFIX = "lambda$"; + private final int myLambdaOrdinal; + @Nullable + private final SourcePosition myFirstStatementPosition; + private final int myLastStatementLine; + + public LambdaMethodFilter(PsiLambdaExpression lambda, int expressionOrdinal) { + myLambdaOrdinal = expressionOrdinal; + + SourcePosition firstStatementPosition = null; + SourcePosition lastStatementPosition = null; + final PsiElement body = lambda.getBody(); + if (body instanceof PsiCodeBlock) { + final PsiStatement[] statements = ((PsiCodeBlock)body).getStatements(); + final int statementCount = statements.length; + if (statementCount > 0) { + firstStatementPosition = SourcePosition.createFromElement(statements[0]); + if (statementCount > 1) { + lastStatementPosition = SourcePosition.createFromElement(statements[statementCount - 1]); + } + } + } + else if (body != null){ + firstStatementPosition = SourcePosition.createFromElement(body); + } + myFirstStatementPosition = firstStatementPosition; + myLastStatementLine = lastStatementPosition != null? lastStatementPosition.getLine() : -1; + } + + public int getLambdaOrdinal() { + return myLambdaOrdinal; + } + + @Nullable + public SourcePosition getBreakpointPosition() { + return myFirstStatementPosition; + } + + /** + * @return a zero-based line number of the last lambda statement, or -1 if not available + */ + public int getLastStatementLine() { + return myLastStatementLine; + } + + public boolean locationMatches(DebugProcessImpl process, Location location) throws EvaluateException { + final Method method = location.method(); + return method.name().startsWith(LAMBDA_METHOD_PREFIX) && method.isSynthetic(); + } +} diff --git a/java/debugger/impl/src/com/intellij/debugger/engine/MethodFilter.java b/java/debugger/impl/src/com/intellij/debugger/engine/MethodFilter.java index 9d882da91337..665d52fb81f8 100644 --- a/java/debugger/impl/src/com/intellij/debugger/engine/MethodFilter.java +++ b/java/debugger/impl/src/com/intellij/debugger/engine/MethodFilter.java @@ -15,91 +15,9 @@ */ package com.intellij.debugger.engine; -import com.intellij.debugger.SourcePosition; import com.intellij.debugger.engine.evaluation.EvaluateException; -import com.intellij.debugger.impl.DebuggerUtilsEx; -import com.intellij.psi.PsiCodeBlock; -import com.intellij.psi.PsiMethod; -import com.intellij.psi.PsiStatement; import com.sun.jdi.Location; -import com.sun.jdi.Method; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -/** -* @author Eugene Zhuravlev -* Date: 10/23/13 -*/ -public final class MethodFilter { - @NotNull - private final JVMName myDeclaringClassName; - @NotNull - private final String myTargetMethodName; - @NotNull - private final JVMName myTargetMethodSignature; - @Nullable - private final SourcePosition myBreakpointPosition; - - public MethodFilter(PsiMethod psiMethod, boolean createBreakpointRequest) { - this( - JVMNameUtil.getJVMQualifiedName(psiMethod.getContainingClass()), - psiMethod.isConstructor()? "" : psiMethod.getName(), - JVMNameUtil.getJVMSignature(psiMethod), - createBreakpointRequest? calcBreakpointPosition(psiMethod) : null - ); - } - - public MethodFilter(@NotNull JVMName declaringClassName, @NotNull String targetMethodName, @NotNull JVMName targetMethodSignature, @Nullable SourcePosition breakpointPosition) { - myDeclaringClassName = declaringClassName; - myTargetMethodName = targetMethodName; - myTargetMethodSignature = targetMethodSignature; - myBreakpointPosition = breakpointPosition; - } - - private static SourcePosition calcBreakpointPosition(PsiMethod method) { - final PsiCodeBlock body = method.getBody(); - if (body == null) { - return null; - } - final PsiStatement[] statements = body.getStatements(); - if (statements.length == 0) { - return null; - } - final PsiStatement firstStatement = statements[0]; - return SourcePosition.createFromElement(firstStatement); - } - - @NotNull - public String getMethodName() { - return myTargetMethodName; - } - - @Nullable - public SourcePosition getBreakpointPosition() { - return myBreakpointPosition; - } - - public boolean locationMatches(DebugProcessImpl process, Location location) throws EvaluateException { - final Method method = location.method(); - if (!myTargetMethodName.equals(method.name())) { - return false; - } - if (!signatureMatches(method, myTargetMethodSignature.getName(process))) { - return false; - } - return DebuggerUtilsEx.isAssignableFrom(myDeclaringClassName.getName(process), location.declaringType()); - } - - private static boolean signatureMatches(Method method, final String expectedSignature) throws EvaluateException { - if (expectedSignature.equals(method.signature())) { - return true; - } - // check if there are any bridge methods that match - for (Method candidate : method.declaringType().methodsByName(method.name())) { - if (candidate != method && candidate.isBridge() && expectedSignature.equals(candidate.signature())) { - return true; - } - } - return false; - } +public interface MethodFilter { + boolean locationMatches(DebugProcessImpl process, Location location) throws EvaluateException; } diff --git a/java/debugger/impl/src/com/intellij/debugger/engine/RequestHint.java b/java/debugger/impl/src/com/intellij/debugger/engine/RequestHint.java index 40dce090fa2e..e8bb6ccb09f3 100644 --- a/java/debugger/impl/src/com/intellij/debugger/engine/RequestHint.java +++ b/java/debugger/impl/src/com/intellij/debugger/engine/RequestHint.java @@ -134,7 +134,7 @@ public class RequestHint { } public boolean wasStepTargetMethodMatched() { - return myTargetMethodMatched; + return myMethodFilter instanceof BreakpointStepMethodFilter || myTargetMethodMatched; } public int getNextStepDepth(final SuspendContextImpl context) { @@ -227,7 +227,7 @@ public class RequestHint { } // smart step feature if (myMethodFilter != null) { - if (myMethodFilter.getBreakpointPosition() != null) { + if (myMethodFilter instanceof BreakpointStepMethodFilter) { // continue stepping if stop criterion is implemented as breakpoint request return StepRequest.STEP_OUT; } diff --git a/java/debugger/impl/src/com/intellij/debugger/ui/breakpoints/BreakpointManager.java b/java/debugger/impl/src/com/intellij/debugger/ui/breakpoints/BreakpointManager.java index fd6134a66c6d..1923092097db 100644 --- a/java/debugger/impl/src/com/intellij/debugger/ui/breakpoints/BreakpointManager.java +++ b/java/debugger/impl/src/com/intellij/debugger/ui/breakpoints/BreakpointManager.java @@ -24,8 +24,8 @@ import com.intellij.codeInsight.folding.impl.actions.ExpandRegionAction; import com.intellij.debugger.DebuggerBundle; import com.intellij.debugger.DebuggerInvocationUtil; import com.intellij.debugger.SourcePosition; +import com.intellij.debugger.engine.BreakpointStepMethodFilter; import com.intellij.debugger.engine.DebugProcessImpl; -import com.intellij.debugger.engine.MethodFilter; import com.intellij.debugger.engine.evaluation.CodeFragmentKind; import com.intellij.debugger.engine.evaluation.TextWithImportsImpl; import com.intellij.debugger.engine.requests.RequestManagerImpl; @@ -400,7 +400,7 @@ public class BreakpointManager { } @Nullable - public StepIntoBreakpoint addStepIntoBreakpoint(@NotNull MethodFilter filter) { + public StepIntoBreakpoint addStepIntoBreakpoint(@NotNull BreakpointStepMethodFilter filter) { return StepIntoBreakpoint.create(myProject, filter); } diff --git a/java/debugger/impl/src/com/intellij/debugger/ui/breakpoints/StepIntoBreakpoint.java b/java/debugger/impl/src/com/intellij/debugger/ui/breakpoints/StepIntoBreakpoint.java index 064a3f2d3ee6..afb5a684ffdd 100644 --- a/java/debugger/impl/src/com/intellij/debugger/ui/breakpoints/StepIntoBreakpoint.java +++ b/java/debugger/impl/src/com/intellij/debugger/ui/breakpoints/StepIntoBreakpoint.java @@ -16,16 +16,21 @@ package com.intellij.debugger.ui.breakpoints; import com.intellij.debugger.SourcePosition; +import com.intellij.debugger.engine.BreakpointStepMethodFilter; +import com.intellij.debugger.engine.CompoundPositionManager; import com.intellij.debugger.engine.DebugProcessImpl; -import com.intellij.debugger.engine.MethodFilter; +import com.intellij.debugger.engine.LambdaMethodFilter; import com.intellij.debugger.engine.evaluation.EvaluateException; +import com.intellij.debugger.engine.requests.RequestManagerImpl; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; -import com.sun.jdi.Location; -import com.sun.jdi.ReferenceType; +import com.sun.jdi.*; +import com.sun.jdi.request.BreakpointRequest; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.*; + /** * @author Eugene Zhuravlev * Date: Sep 13, 2006 @@ -33,9 +38,9 @@ import org.jetbrains.annotations.Nullable; public class StepIntoBreakpoint extends RunToCursorBreakpoint { private static final Logger LOG = Logger.getInstance("#com.intellij.debugger.ui.breakpoints.StepIntoBreakpoint"); @NotNull - private final MethodFilter myFilter; + private final BreakpointStepMethodFilter myFilter; - StepIntoBreakpoint(@NotNull Project project, @NotNull SourcePosition pos, @NotNull MethodFilter filter) { + StepIntoBreakpoint(@NotNull Project project, @NotNull SourcePosition pos, @NotNull BreakpointStepMethodFilter filter) { super(project, pos, false); myFilter = filter; } @@ -44,6 +49,94 @@ public class StepIntoBreakpoint extends RunToCursorBreakpoint { super.createOrWaitPrepare(debugProcess, classPosition); } + protected void createRequestForPreparedClass(DebugProcessImpl debugProcess, ReferenceType classType) { + try { + final CompoundPositionManager positionManager = debugProcess.getPositionManager(); + final SourcePosition startPosition = getSourcePosition(); + List locations = positionManager.locationsOfLine(classType, startPosition); + + if (locations.isEmpty() && myFilter instanceof LambdaMethodFilter) { + // sometimes first statements are mapped to some weird line number, + // so if lambda spans for more than one lines, try get some locations from these lines + final int lastLine = ((LambdaMethodFilter)myFilter).getLastStatementLine(); + if (lastLine >= 0) { + int nextLine = startPosition.getLine() + 1; + while (nextLine <= lastLine && locations.isEmpty()) { + locations = positionManager.locationsOfLine(classType, SourcePosition.createFromLine(startPosition.getFile(), nextLine++)); + } + } + } + + if (!locations.isEmpty()) { + final Set methods = new HashSet(); + for (Location loc : locations) { + if (acceptLocation(debugProcess, classType, loc)) { + methods.add(loc.method()); + } + } + Location location = null; + final int methodsFound = methods.size(); + if (methodsFound == 1) { + location = methods.iterator().next().location(); + } + else { + if (myFilter instanceof LambdaMethodFilter) { + final LambdaMethodFilter lambdaFilter = (LambdaMethodFilter)myFilter; + if (lambdaFilter.getLambdaOrdinal() < methodsFound) { + final Method[] candidates = methods.toArray(new Method[methodsFound]); + Arrays.sort(candidates, new Comparator() { + public int compare(Method m1, Method m2) { + return getMethodOrdinal(m1) - getMethodOrdinal(m2); + } + }); + location = candidates[lambdaFilter.getLambdaOrdinal()].location(); + } + } + else { + if (methodsFound > 0) { + location = methods.iterator().next().location(); + } + } + } + if (location != null) { + final RequestManagerImpl requestsManager = debugProcess.getRequestsManager(); + final BreakpointRequest request = requestsManager.createBreakpointRequest(this, location); + requestsManager.enableRequest(request); + } + } + } + catch (ClassNotPreparedException ex) { + if (LOG.isDebugEnabled()) { + LOG.debug("ClassNotPreparedException: " + ex.getMessage()); + } + } + catch (ObjectCollectedException ex) { + if (LOG.isDebugEnabled()) { + LOG.debug("ObjectCollectedException: " + ex.getMessage()); + } + } + catch (InternalException ex) { + LOG.info(ex); + } + catch(Exception ex) { + LOG.info(ex); + } + } + + private static int getMethodOrdinal(Method m) { + final String name = m.name(); + final int dollarIndex = name.lastIndexOf("$"); + if (dollarIndex < 0) { + return 0; + } + try { + return Integer.parseInt(name.substring(dollarIndex + 1)); + } + catch (NumberFormatException e) { + return 0; + } + } + protected boolean acceptLocation(DebugProcessImpl debugProcess, ReferenceType classType, Location loc) { try { return myFilter.locationMatches(debugProcess, loc); @@ -55,7 +148,7 @@ public class StepIntoBreakpoint extends RunToCursorBreakpoint { } @Nullable - protected static StepIntoBreakpoint create(@NotNull Project project, @NotNull MethodFilter filter) { + protected static StepIntoBreakpoint create(@NotNull Project project, @NotNull BreakpointStepMethodFilter filter) { final SourcePosition pos = filter.getBreakpointPosition(); if (pos != null) { final StepIntoBreakpoint breakpoint = new StepIntoBreakpoint(project, pos, filter);