lambda support in smart step into

This commit is contained in:
Eugene Zhuravlev
2013-10-27 00:11:18 +02:00
parent 48613331d6
commit 9d07ec2efa
16 changed files with 600 additions and 189 deletions
@@ -44,7 +44,7 @@ public class JavaSmartStepIntoHandler extends JvmSmartStepIntoHandler {
@Override
@NotNull
public List<StepTarget> findSmartStepTargets(final SourcePosition position) {
public List<SmartStepTarget> 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<StepTarget> targets = new OrderedSet<StepTarget>();
final List<SmartStepTarget> targets = new OrderedSet<SmartStepTarget>();
final PsiElementVisitor methodCollector = new JavaRecursiveElementVisitor() {
final Stack<PsiMethod> myContextStack = new Stack<PsiMethod>();
final Stack<String> myParamNameStack = new Stack<String>();
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?
@@ -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<JvmSmartStepIntoHandler> EP_NAME = ExtensionPointName.create("com.intellij.debugger.jvmSmartStepIntoHandler");
@NotNull
public abstract List<StepTarget> findSmartStepTargets(SourcePosition position);
public abstract List<SmartStepTarget> 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<StepTarget> targets = findSmartStepTargets(position);
final List<SmartStepTarget> 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;
}
}
@@ -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;
}
}
@@ -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();
}
}
@@ -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<JvmSmartStepIntoHandler.StepTarget> {
private final List<JvmSmartStepIntoHandler.StepTarget> myTargets;
class PsiMethodListPopupStep implements ListPopupStep<SmartStepTarget> {
private final List<SmartStepTarget> 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<JvmSmartStepIntoHandler.StepTarget> targets, final OnChooseRunnable stepRunnable) {
public PsiMethodListPopupStep(Editor editor, final List<SmartStepTarget> targets, final OnChooseRunnable stepRunnable) {
myTargets = targets;
myScopeHighlighter = new ScopeHighlighter(editor);
myStepRunnable = stepRunnable;
@@ -52,33 +54,49 @@ class PsiMethodListPopupStep implements ListPopupStep<JvmSmartStepIntoHandler.St
}
@NotNull
public List<JvmSmartStepIntoHandler.StepTarget> getValues() {
public List<SmartStepTarget> 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<JvmSmartStepIntoHandler.St
return DebuggerBundle.message("title.smart.step.popup");
}
public PopupStep onChosen(JvmSmartStepIntoHandler.StepTarget selectedValue, final boolean finalChoice) {
public PopupStep onChosen(SmartStepTarget selectedValue, final boolean finalChoice) {
if (finalChoice) {
myScopeHighlighter.dropHighlight();
myStepRunnable.execute(selectedValue);
@@ -102,7 +120,7 @@ class PsiMethodListPopupStep implements ListPopupStep<JvmSmartStepIntoHandler.St
return null;
}
public boolean hasSubstep(JvmSmartStepIntoHandler.StepTarget selectedValue) {
public boolean hasSubstep(SmartStepTarget selectedValue) {
return false;
}
@@ -0,0 +1,49 @@
/*
* 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 org.jetbrains.annotations.Nullable;
/**
* @author Eugene Zhuravlev
* Date: 10/25/13
*/
public abstract class SmartStepTarget {
private final PsiElement myHighlightElement;
private final String myLabel;
private final boolean myNeedBreakpointRequest;
protected SmartStepTarget(@Nullable String label, @Nullable PsiElement highlightElement, boolean needBreakpointRequest) {
myHighlightElement = highlightElement;
myLabel = label;
myNeedBreakpointRequest = needBreakpointRequest;
}
@Nullable
public PsiElement getHighlightElement() {
return myHighlightElement;
}
@Nullable
public String getLabel() {
return myLabel;
}
public boolean needsBreakpointRequest() {
return myNeedBreakpointRequest;
}
}
@@ -0,0 +1,54 @@
/*
* 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.psi.PsiCodeBlock;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.PsiStatement;
import org.jetbrains.annotations.Nullable;
/**
* @author Eugene Zhuravlev
* Date: 10/26/13
*/
public class AnonymousClassMethodFilter extends BasicStepMethodFilter implements BreakpointStepMethodFilter{
@Nullable
private final SourcePosition myBreakpointPosition;
public AnonymousClassMethodFilter(PsiMethod psiMethod) {
super(psiMethod);
myBreakpointPosition = calcBreakpointPosition(psiMethod.getBody());
}
@Nullable
public SourcePosition getBreakpointPosition() {
return myBreakpointPosition;
}
private static SourcePosition calcBreakpointPosition(final PsiCodeBlock body) {
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);
}
}
@@ -0,0 +1,74 @@
/*
* 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.engine.evaluation.EvaluateException;
import com.intellij.debugger.impl.DebuggerUtilsEx;
import com.intellij.psi.PsiMethod;
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/26/13
*/
public class BasicStepMethodFilter implements MethodFilter{
@NotNull
protected final JVMName myDeclaringClassName;
@NotNull
private final String myTargetMethodName;
@Nullable
protected final JVMName myTargetMethodSignature;
public BasicStepMethodFilter(PsiMethod psiMethod) {
myDeclaringClassName = JVMNameUtil.getJVMQualifiedName(psiMethod.getContainingClass());
myTargetMethodName = psiMethod.isConstructor() ? "<init>" : 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;
}
}
@@ -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();
}
@@ -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 <b>" + methodFilter.getMethodName() + "()</b> has not been called";
if (methodFilter instanceof BasicStepMethodFilter && !hint.wasStepTargetMethodMatched()) {
final String message = "Method <b>" + ((BasicStepMethodFilter)methodFilter).getMethodName() + "()</b> has not been called";
XDebugSessionImpl.NOTIFICATION_GROUP.createNotification(message, MessageType.INFO).notify(project);
}
}
@@ -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() {
@@ -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();
}
}
@@ -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()? "<init>" : 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;
}
@@ -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;
}
@@ -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);
}
@@ -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<Location> 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<Method> methods = new HashSet<Method>();
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<Method>() {
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);