From beac4780d2285479de38825af91c3682b7b0ba88 Mon Sep 17 00:00:00 2001 From: Jan Lahoda Date: Wed, 18 Mar 2026 16:13:55 +0100 Subject: [PATCH 1/3] Breakpoints for lambdas. --- .../api/debugger/jpda/LineBreakpoint.java | 25 +- .../nbproject/project.xml | 8 + .../BreakpointAnnotationProvider.java | 22 +- .../jpda/projectsui/Bundle.properties | 2 + .../DebuggerBreakpointAnnotation.java | 12 +- .../projectsui/LambdaBreakpointManager.java | 279 ++++++++++++++++++ .../resources/DisabledLambdaBreakpoint.xml | 33 +++ .../jpda/resources/LambdaBreakpoint.xml | 36 +++ .../debugger/jpda/resources/mf-layer.xml | 2 + .../jpda/ui/breakpoints/Bundle.properties | 5 + .../ui/breakpoints/LineBreakpointPanel.form | 40 ++- .../ui/breakpoints/LineBreakpointPanel.java | 30 ++ .../jpda/ui/models/BreakpointsNodeModel.java | 21 +- .../debugger/jpda/ui/models/Bundle.properties | 1 + java/debugger.jpda/nbproject/project.xml | 3 +- .../jpda/breakpoints/BreakpointsReader.java | 5 + .../jpda/breakpoints/LineBreakpointImpl.java | 32 ++ java/java.lsp.server/nbproject/project.xml | 15 + .../server/debugging/NbProtocolServer.java | 81 +++++ .../breakpoints/BreakpointsManager.java | 99 +++++-- .../debugging/breakpoints/NbBreakpoint.java | 50 +++- .../NbBreakpointsRequestHandler.java | 4 +- 22 files changed, 758 insertions(+), 47 deletions(-) create mode 100644 java/debugger.jpda.projectsui/src/org/netbeans/modules/debugger/jpda/projectsui/LambdaBreakpointManager.java create mode 100644 java/debugger.jpda.ui/src/org/netbeans/modules/debugger/jpda/resources/DisabledLambdaBreakpoint.xml create mode 100644 java/debugger.jpda.ui/src/org/netbeans/modules/debugger/jpda/resources/LambdaBreakpoint.xml diff --git a/java/api.debugger.jpda/src/org/netbeans/api/debugger/jpda/LineBreakpoint.java b/java/api.debugger.jpda/src/org/netbeans/api/debugger/jpda/LineBreakpoint.java index 78726be196d7..d073ac961258 100644 --- a/java/api.debugger.jpda/src/org/netbeans/api/debugger/jpda/LineBreakpoint.java +++ b/java/api.debugger.jpda/src/org/netbeans/api/debugger/jpda/LineBreakpoint.java @@ -67,6 +67,8 @@ public class LineBreakpoint extends JPDABreakpoint { /** Property name constant */ public static final String PROP_LINE_NUMBER = "lineNumber"; // NOI18N /** Property name constant */ + public static final String PROP_LAMBDA_INDEX = "lambdaIndex"; // NOI18N + /** Property name constant */ public static final String PROP_URL = "url"; // NOI18N /** Property name constant. */ public static final String PROP_CONDITION = "condition"; // NOI18N @@ -94,6 +96,7 @@ public class LineBreakpoint extends JPDABreakpoint { private String className = null; private Map instanceFilters; private Map threadFilters; + private int lambdaIndex = -1; //line breakpoint by default private LineBreakpoint (String url) { @@ -182,6 +185,26 @@ public void setLineNumber (int ln) { Integer.valueOf(ln) ); } + + public int getLambdaIndex() { + return lambdaIndex; + } + + public void setLambdaIndex(int li) { + int old; + synchronized (this) { + if (li == lambdaIndex) { + return; + } + old = lambdaIndex; + lambdaIndex = li; + } + firePropertyChange ( + PROP_LAMBDA_INDEX, + Integer.valueOf(old), + Integer.valueOf(li) + ); + } /** * Get the instance filter for a specific debugger session. @@ -423,7 +446,7 @@ public String toString () { if (fileName == null) { fileName = url; } - return "LineBreakpoint " + fileName + " : " + lineNumber; + return "LineBreakpoint " + fileName + " : " + lineNumber + (lambdaIndex >= 0 ? " lambda index: " + lambdaIndex : ""); } private static class LineBreakpointImpl extends LineBreakpoint diff --git a/java/debugger.jpda.projectsui/nbproject/project.xml b/java/debugger.jpda.projectsui/nbproject/project.xml index 561252c26711..6790eca564da 100644 --- a/java/debugger.jpda.projectsui/nbproject/project.xml +++ b/java/debugger.jpda.projectsui/nbproject/project.xml @@ -146,6 +146,14 @@ 1.62 + + org.netbeans.modules.java.source + + + + 0.191 + + org.netbeans.modules.java.source.base diff --git a/java/debugger.jpda.projectsui/src/org/netbeans/modules/debugger/jpda/projectsui/BreakpointAnnotationProvider.java b/java/debugger.jpda.projectsui/src/org/netbeans/modules/debugger/jpda/projectsui/BreakpointAnnotationProvider.java index 2ca1022f3580..6f6eb0f76eac 100644 --- a/java/debugger.jpda.projectsui/src/org/netbeans/modules/debugger/jpda/projectsui/BreakpointAnnotationProvider.java +++ b/java/debugger.jpda.projectsui/src/org/netbeans/modules/debugger/jpda/projectsui/BreakpointAnnotationProvider.java @@ -139,6 +139,7 @@ private void annotate (final FileObject fo) { } annotatedFiles.add(fo); } + LambdaBreakpointManager.FactoryImpl.doRefresh(fo); //ensure spans of lambda breakpoints are updated } void setBreakpointsActive(boolean active) { @@ -189,6 +190,7 @@ private void refreshAnnotation(JPDABreakpoint b) { breakpointToAnnotations.put(b, Collections.newSetFromMap(new WeakHashMap<>())); for (FileObject fo : annotatedFiles) { addAnnotationTo(b, fo); + LambdaBreakpointManager.FactoryImpl.doRefresh(fo); } } } @@ -207,12 +209,17 @@ private static String getAnnotationType(JPDABreakpoint b, boolean isConditional, boolean active) { boolean isInvalid = b.getValidity() == VALIDITY.INVALID; String annotationType; - if (b instanceof LineBreakpoint) { - annotationType = b.isEnabled () ? - (isConditional ? EditorContext.CONDITIONAL_BREAKPOINT_ANNOTATION_TYPE : - EditorContext.BREAKPOINT_ANNOTATION_TYPE) : - (isConditional ? EditorContext.DISABLED_CONDITIONAL_BREAKPOINT_ANNOTATION_TYPE : - EditorContext.DISABLED_BREAKPOINT_ANNOTATION_TYPE); + if (b instanceof LineBreakpoint lb) { + if (lb.getLambdaIndex() >= 0) { + annotationType = b.isEnabled() ? DebuggerBreakpointAnnotation.LAMBDA_BREAKPOINT + : DebuggerBreakpointAnnotation.DISABLED_LAMBDA_BREAKPOINT; + } else { + annotationType = b.isEnabled () ? + (isConditional ? EditorContext.CONDITIONAL_BREAKPOINT_ANNOTATION_TYPE : + EditorContext.BREAKPOINT_ANNOTATION_TYPE) : + (isConditional ? EditorContext.DISABLED_CONDITIONAL_BREAKPOINT_ANNOTATION_TYPE : + EditorContext.DISABLED_BREAKPOINT_ANNOTATION_TYPE); + } } else if (b instanceof FieldBreakpoint) { annotationType = b.isEnabled () ? EditorContext.FIELD_BREAKPOINT_ANNOTATION_TYPE : @@ -475,6 +482,9 @@ static String getCondition(Breakpoint b) { } } + Set getAnnotationsForBreakpoint(JPDABreakpoint b) { + return breakpointToAnnotations.getOrDefault(b, Set.of()); + } /* // Not used @Override diff --git a/java/debugger.jpda.projectsui/src/org/netbeans/modules/debugger/jpda/projectsui/Bundle.properties b/java/debugger.jpda.projectsui/src/org/netbeans/modules/debugger/jpda/projectsui/Bundle.properties index 102e8b85d3a4..f9220ce54e27 100644 --- a/java/debugger.jpda.projectsui/src/org/netbeans/modules/debugger/jpda/projectsui/Bundle.properties +++ b/java/debugger.jpda.projectsui/src/org/netbeans/modules/debugger/jpda/projectsui/Bundle.properties @@ -41,6 +41,8 @@ TOOLTIP_DISABLED_METHOD_BREAKPOINT=Disabled Method Breakpoint TOOLTIP_CLASS_BREAKPOINT=Class Breakpoint TOOLTIP_DISABLED_CLASS_BREAKPOINT=Disabled Class Breakpoint TOOLTIP_BREAKPOINT_STROKE=Deactivated breakpoint +TOOLTIP_LAMBDA_BREAKPOINT=Lambda Breakpoint +TOOLTIP_DISABLED_LAMBDA_BREAKPOINT=Lambda Breakpoint # A condition expression follows this message on a next line TOOLTIP_CONDITION=Hits when: # {0} - a number diff --git a/java/debugger.jpda.projectsui/src/org/netbeans/modules/debugger/jpda/projectsui/DebuggerBreakpointAnnotation.java b/java/debugger.jpda.projectsui/src/org/netbeans/modules/debugger/jpda/projectsui/DebuggerBreakpointAnnotation.java index 5e0abec4496d..6159bb69bae0 100644 --- a/java/debugger.jpda.projectsui/src/org/netbeans/modules/debugger/jpda/projectsui/DebuggerBreakpointAnnotation.java +++ b/java/debugger.jpda.projectsui/src/org/netbeans/modules/debugger/jpda/projectsui/DebuggerBreakpointAnnotation.java @@ -23,7 +23,6 @@ import java.util.List; import org.netbeans.api.debugger.Breakpoint; import org.netbeans.api.debugger.Breakpoint.HIT_COUNT_FILTERING_STYLE; -import org.netbeans.api.debugger.jpda.JPDABreakpoint; import org.netbeans.spi.debugger.jpda.EditorContext; import org.netbeans.spi.debugger.ui.BreakpointAnnotation; @@ -39,6 +38,9 @@ */ public class DebuggerBreakpointAnnotation extends BreakpointAnnotation { + public static final String LAMBDA_BREAKPOINT = "LambdaBreakpoint"; + public static final String DISABLED_LAMBDA_BREAKPOINT = "DisabledLambdaBreakpoint"; + private final Line line; private final String type; private final Breakpoint breakpoint; @@ -163,6 +165,14 @@ private String getShortDescriptionIntern () { if (type == EditorContext.DISABLED_CLASS_BREAKPOINT_ANNOTATION_TYPE) { return NbBundle.getMessage (DebuggerBreakpointAnnotation.class, "TOOLTIP_DISABLED_CLASS_BREAKPOINT"); // NOI18N + } else + if (type == LAMBDA_BREAKPOINT) { + return NbBundle.getMessage + (DebuggerBreakpointAnnotation.class, "TOOLTIP_LAMBDA_BREAKPOINT"); // NOI18N + } else + if (type == DISABLED_LAMBDA_BREAKPOINT) { + return NbBundle.getMessage + (DebuggerBreakpointAnnotation.class, "TOOLTIP_DISABLED_LAMBDA_BREAKPOINT"); // NOI18N } ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, new IllegalStateException("Unknown breakpoint type '"+type+"'.")); return null; diff --git a/java/debugger.jpda.projectsui/src/org/netbeans/modules/debugger/jpda/projectsui/LambdaBreakpointManager.java b/java/debugger.jpda.projectsui/src/org/netbeans/modules/debugger/jpda/projectsui/LambdaBreakpointManager.java new file mode 100644 index 000000000000..d394253b8c46 --- /dev/null +++ b/java/debugger.jpda.projectsui/src/org/netbeans/modules/debugger/jpda/projectsui/LambdaBreakpointManager.java @@ -0,0 +1,279 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.netbeans.modules.debugger.jpda.projectsui; + +import com.sun.source.tree.LambdaExpressionTree; +import com.sun.source.tree.LineMap; +import com.sun.source.tree.Tree; +import java.beans.PropertyChangeEvent; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; +import org.netbeans.api.debugger.Breakpoint; +import org.netbeans.api.debugger.DebuggerEngine; +import org.netbeans.api.debugger.DebuggerManager; +import org.netbeans.api.debugger.DebuggerManagerAdapter; +import org.netbeans.api.debugger.LazyDebuggerManagerListener; +import org.netbeans.api.debugger.jpda.JPDABreakpoint; +import org.netbeans.api.debugger.jpda.JPDADebugger; +import org.netbeans.api.debugger.jpda.LineBreakpoint; +import org.netbeans.api.java.source.CancellableTask; +import org.netbeans.api.java.source.CompilationInfo; +import org.netbeans.api.java.source.JavaSource.Phase; +import org.netbeans.api.java.source.JavaSource.Priority; +import org.netbeans.api.java.source.JavaSourceTaskFactory; +import org.netbeans.api.java.source.support.CancellableTreePathScanner; +import org.netbeans.api.java.source.support.EditorAwareJavaSourceTaskFactory; +import org.netbeans.spi.debugger.DebuggerServiceRegistration; +import org.openide.cookies.LineCookie; +import org.openide.filesystems.FileObject; +import org.openide.filesystems.URLMapper; +import org.openide.text.Annotation; +import org.openide.text.Line; +import org.openide.text.Line.Part; +import org.openide.util.Exceptions; +import org.openide.util.Lookup; +import org.openide.util.lookup.ServiceProvider; + +@DebuggerServiceRegistration(types=LazyDebuggerManagerListener.class) +public class LambdaBreakpointManager extends DebuggerManagerAdapter { + + private static volatile JPDADebugger currentDebugger = null; //XXX: static??? + + @Override + public String[] getProperties() { + return new String[] { DebuggerManager.PROP_BREAKPOINTS, DebuggerManager.PROP_DEBUGGER_ENGINES }; + } + + @Override + public void breakpointAdded(Breakpoint breakpoint) { + refreshAfterChange(breakpoint); + } + + @Override + public void breakpointRemoved(Breakpoint breakpoint) { + refreshAfterChange(breakpoint); + } + + private void refreshAfterChange(Breakpoint breakpoint) { + if (breakpoint instanceof LineBreakpoint lb) { + try { + URL currentURL = new URL(lb.getURL()); + FileObject fo = URLMapper.findFileObject(currentURL); + + if (fo != null) { + FactoryImpl.doRefresh(fo); + } + } catch (MalformedURLException ex) { + Exceptions.printStackTrace(ex); + } + } + } + + @Override + public void propertyChange(PropertyChangeEvent evt) { + String propertyName = evt.getPropertyName (); + if (propertyName == null) { + return; + } + if (DebuggerManager.PROP_CURRENT_ENGINE.equals(propertyName)) { + setCurrentDebugger(DebuggerManager.getDebuggerManager().getCurrentEngine()); + } + if ( (!LineBreakpoint.PROP_URL.equals (propertyName)) && + (!LineBreakpoint.PROP_LINE_NUMBER.equals (propertyName)) + ) { + return; + } + JPDABreakpoint b = (JPDABreakpoint) evt.getSource (); + DebuggerManager manager = DebuggerManager.getDebuggerManager(); + Breakpoint[] bkpts = manager.getBreakpoints(); + boolean found = false; + for (int x = 0; x < bkpts.length; x++) { + if (b == bkpts[x]) { + found = true; + break; + } + } + if (!found) { + // breakpoint has been removed + return; + } + } + + private void setCurrentDebugger(DebuggerEngine engine) { + JPDADebugger oldDebugger = currentDebugger; + if (oldDebugger != null) { + oldDebugger.removePropertyChangeListener(JPDADebugger.PROP_BREAKPOINTS_ACTIVE, this); + } + boolean active = true; + JPDADebugger debugger = null; + if (engine != null) { + debugger = engine.lookupFirst(null, JPDADebugger.class); + if (debugger != null) { + debugger.addPropertyChangeListener(JPDADebugger.PROP_BREAKPOINTS_ACTIVE, this); + active = debugger.getBreakpointsActive(); + } + } + currentDebugger = debugger; + } + + //TODO: requires dependency on java.source - maybe try to rewrite to Schedulers! + @ServiceProvider(service=JavaSourceTaskFactory.class) + public static final class FactoryImpl extends EditorAwareJavaSourceTaskFactory { + + private BreakpointAnnotationProvider bap; + + public FactoryImpl() { + super(Phase.PARSED, Priority.BELOW_NORMAL); + } + + private BreakpointAnnotationProvider getAnnotationProvider() { + if (bap == null) { + bap = BreakpointAnnotationProvider.getInstance(); + } + return bap; + } + + @Override + protected CancellableTask createTask(FileObject file) { + return new CancellableTask() { + private final AtomicBoolean canceled = new AtomicBoolean(); + + @Override + public void cancel() { + canceled.set(true); + } + + @Override + public void run(CompilationInfo info) throws Exception { + canceled.set(false); + + String currentFile = info.getFileObject().toURL().toString(); + Map> lines2LambdaIndexes = new HashMap<>(); + + for (Breakpoint b : DebuggerManager.getDebuggerManager().getBreakpoints()) { + if (b instanceof LineBreakpoint lb && currentFile.equals(lb.getURL())) { + lines2LambdaIndexes.computeIfAbsent(lb.getLineNumber(), x -> new HashMap<>()) + .put(lb.getLambdaIndex(), lb); + } + } + + LineMap lineMap = info.getCompilationUnit().getLineMap(); + + new CancellableTreePathScanner(canceled) { + int currentLine = -1; + int currentLambdaIndex = -1; + public Void scan(Tree tree, Void v) { + if (tree != null && tree.getKind() != Tree.Kind.COMPILATION_UNIT) { + long startPos = info.getTrees().getSourcePositions().getStartPosition(getCurrentPath().getCompilationUnit(), tree); + if (startPos != (-1)) { + int line = (int) lineMap.getLineNumber(startPos); + + if (line != currentLine) { + currentLine = line; + currentLambdaIndex = 0; + } + } + } + return super.scan(tree, v); + } + public Void visitLambdaExpression(LambdaExpressionTree tree, Void v) { + long startPos = info.getTrees().getSourcePositions().getStartPosition(getCurrentPath().getCompilationUnit(), tree); + int startLine = (int) lineMap.getLineNumber(startPos); + Map existingLineBreakpoints = lines2LambdaIndexes.get(startLine); + LineBreakpoint existingLineBreakpoint = existingLineBreakpoints != null ? existingLineBreakpoints.remove(currentLambdaIndex) : null; + + if (existingLineBreakpoints != null && existingLineBreakpoints.containsKey(-1)) { + //the line breakpoint exists, ensure the lambda breakpoint exists and is updated + if (existingLineBreakpoint == null) { + LineBreakpoint lb = LineBreakpoint.create(currentFile, startLine); + lb.setLambdaIndex(currentLambdaIndex); + lb.disable(); + DebuggerManager.getDebuggerManager().addBreakpoint(lb); + } else { + long endPos = info.getTrees().getSourcePositions().getEndPosition(getCurrentPath().getCompilationUnit(), tree); + int endLine = (int) lineMap.getLineNumber(endPos); + int startColumn = (int) lineMap.getColumnNumber(startPos) - 1; + int endColumn; + if (startLine == endLine) { + endColumn = (int) lineMap.getColumnNumber(endPos) - 1; + } else { + endColumn = (int) lineMap.getStartPosition(startLine + 1) - 1; + } + + int length = endColumn - startColumn; + + Set annotations = getAnnotationProvider().getAnnotationsForBreakpoint(existingLineBreakpoint); + + for (Annotation ann : annotations) { + if (!(ann.getAttachedAnnotatable() instanceof Part p) || p.getLine().getLineNumber() != startLine || p.getColumn() != startColumn || p.getLength() != length) { + LineCookie lc = info.getFileObject().getLookup().lookup(LineCookie.class); + + if (lc == null) { + continue; + } + + Line line = lc.getLineSet().getCurrent(startLine - 1); + Part part = line.createPart(startColumn, length); + + if (canceled.get()) { + //don't set the part if cancelled - the positions may be wrong + return null; + } + + ann.detach(); + ann.attach(part); + } + } + } + } + + currentLambdaIndex++; + + return super.visitLambdaExpression(tree, v); + } + }.scan(info.getCompilationUnit(), null); + + //remove any stale lambda breakpoints: + for (Breakpoint b : DebuggerManager.getDebuggerManager().getBreakpoints()) { + if (b instanceof LineBreakpoint lb && currentFile.equals(lb.getURL()) && lb.getLambdaIndex() >=0) { + Map staleLambdaBreakpoints = lines2LambdaIndexes.getOrDefault(lb.getLineNumber(), Map.of()); + if (staleLambdaBreakpoints.containsKey(lb.getLambdaIndex()) || !staleLambdaBreakpoints.containsKey(-1)) { + DebuggerManager.getDebuggerManager().removeBreakpoint(lb); + } + } + } + } + }; + } + + public static void doRefresh(FileObject file) { + for (JavaSourceTaskFactory f : Lookup.getDefault().lookupAll(JavaSourceTaskFactory.class)) { + if (f instanceof FactoryImpl impl) { + impl.reschedule(file); + } + } + } + } + +} diff --git a/java/debugger.jpda.ui/src/org/netbeans/modules/debugger/jpda/resources/DisabledLambdaBreakpoint.xml b/java/debugger.jpda.ui/src/org/netbeans/modules/debugger/jpda/resources/DisabledLambdaBreakpoint.xml new file mode 100644 index 000000000000..e841d70ba90b --- /dev/null +++ b/java/debugger.jpda.ui/src/org/netbeans/modules/debugger/jpda/resources/DisabledLambdaBreakpoint.xml @@ -0,0 +1,33 @@ + + + + + diff --git a/java/debugger.jpda.ui/src/org/netbeans/modules/debugger/jpda/resources/LambdaBreakpoint.xml b/java/debugger.jpda.ui/src/org/netbeans/modules/debugger/jpda/resources/LambdaBreakpoint.xml new file mode 100644 index 000000000000..6a83877f77ba --- /dev/null +++ b/java/debugger.jpda.ui/src/org/netbeans/modules/debugger/jpda/resources/LambdaBreakpoint.xml @@ -0,0 +1,36 @@ + + + + + diff --git a/java/debugger.jpda.ui/src/org/netbeans/modules/debugger/jpda/resources/mf-layer.xml b/java/debugger.jpda.ui/src/org/netbeans/modules/debugger/jpda/resources/mf-layer.xml index df823ccc2467..4f05e7f2004a 100644 --- a/java/debugger.jpda.ui/src/org/netbeans/modules/debugger/jpda/resources/mf-layer.xml +++ b/java/debugger.jpda.ui/src/org/netbeans/modules/debugger/jpda/resources/mf-layer.xml @@ -91,9 +91,11 @@ + + diff --git a/java/debugger.jpda.ui/src/org/netbeans/modules/debugger/jpda/ui/breakpoints/Bundle.properties b/java/debugger.jpda.ui/src/org/netbeans/modules/debugger/jpda/ui/breakpoints/Bundle.properties index fe41db22e26f..d21a239259c2 100644 --- a/java/debugger.jpda.ui/src/org/netbeans/modules/debugger/jpda/ui/breakpoints/Bundle.properties +++ b/java/debugger.jpda.ui/src/org/netbeans/modules/debugger/jpda/ui/breakpoints/Bundle.properties @@ -229,6 +229,11 @@ ACSD_TF_Line_Breakpoint_Line_Number=Line number TTT_TF_Line_Breakpoint_Line_Number=Line number to stop at + L_Line_Breakpoint_Lambda_Index=Lambda &Index\: + ACSD_L_Line_Breakpoint_Lambda_Index=Lambda Index + ACSD_TF_Line_Breakpoint_Lambda_Index=Lambda Index + TTT_TF_Line_Breakpoint_Lambda_Index=Lambda Index to stop at + L_Line_Breakpoint_Condition=Co&ndition: ACSD_L_Line_Breakpoint_Condition=Condition ACSD_TF_Line_Breakpoint_Condition=Condition diff --git a/java/debugger.jpda.ui/src/org/netbeans/modules/debugger/jpda/ui/breakpoints/LineBreakpointPanel.form b/java/debugger.jpda.ui/src/org/netbeans/modules/debugger/jpda/ui/breakpoints/LineBreakpointPanel.form index 5ea1b1c1ff72..9eb4f7f3a2d4 100644 --- a/java/debugger.jpda.ui/src/org/netbeans/modules/debugger/jpda/ui/breakpoints/LineBreakpointPanel.form +++ b/java/debugger.jpda.ui/src/org/netbeans/modules/debugger/jpda/ui/breakpoints/LineBreakpointPanel.form @@ -1,4 +1,4 @@ - + - - - diff --git a/java/debugger.jpda.ui/src/org/netbeans/modules/debugger/jpda/resources/LambdaBreakpoint.xml b/java/debugger.jpda.ui/src/org/netbeans/modules/debugger/jpda/resources/LambdaBreakpoint.xml deleted file mode 100644 index 6a83877f77ba..000000000000 --- a/java/debugger.jpda.ui/src/org/netbeans/modules/debugger/jpda/resources/LambdaBreakpoint.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - - - diff --git a/java/debugger.jpda.ui/src/org/netbeans/modules/debugger/jpda/resources/mf-layer.xml b/java/debugger.jpda.ui/src/org/netbeans/modules/debugger/jpda/resources/mf-layer.xml index 4f05e7f2004a..df823ccc2467 100644 --- a/java/debugger.jpda.ui/src/org/netbeans/modules/debugger/jpda/resources/mf-layer.xml +++ b/java/debugger.jpda.ui/src/org/netbeans/modules/debugger/jpda/resources/mf-layer.xml @@ -91,11 +91,9 @@ - - diff --git a/java/debugger.jpda.ui/src/org/netbeans/modules/debugger/jpda/ui/breakpoints/LineBreakpointPanel.java b/java/debugger.jpda.ui/src/org/netbeans/modules/debugger/jpda/ui/breakpoints/LineBreakpointPanel.java index b36d6c855bf5..50fc0d975053 100644 --- a/java/debugger.jpda.ui/src/org/netbeans/modules/debugger/jpda/ui/breakpoints/LineBreakpointPanel.java +++ b/java/debugger.jpda.ui/src/org/netbeans/modules/debugger/jpda/ui/breakpoints/LineBreakpointPanel.java @@ -25,8 +25,10 @@ import java.net.URI; import java.net.URISyntaxException; import java.net.URL; +import java.util.Arrays; import java.util.logging.Level; import java.util.logging.Logger; +import java.util.stream.Collectors; import javax.swing.JPanel; import javax.swing.SwingUtilities; import javax.swing.event.DocumentEvent; @@ -130,7 +132,7 @@ public LineBreakpointPanel (LineBreakpoint b, boolean createBreakpoint) { tfFileName.getPreferredSize().height)); tfLineNumber.setText(Integer.toString(b.getLineNumber())); - tfLambdaIndex.setText(Integer.toString(b.getLambdaIndex())); + tfLambdaIndex.setText(Arrays.stream(b.getLambdaIndex()).mapToObj(String::valueOf).collect(Collectors.joining(", "))); conditionsPanel = new ConditionsPanel(HELP_ID); setupConditionPane(); conditionsPanel.showClassFilter(false); @@ -348,7 +350,12 @@ public boolean ok () { logger.fine(" => URL = '"+url+"'"); breakpoint.setURL((url != null) ? url.toString() : path); breakpoint.setLineNumber(Integer.parseInt(tfLineNumber.getText().trim())); - breakpoint.setLambdaIndex(Integer.parseInt(tfLambdaIndex.getText().trim())); + String lambdaIndexText = tfLambdaIndex.getText().trim(); + if (lambdaIndexText.isEmpty()) { + breakpoint.setLambdaIndex(new int[0]); + } else { + breakpoint.setLambdaIndex(Arrays.stream(lambdaIndexText.split(", *")).mapToInt(v -> Integer.parseInt(v)).toArray()); + } breakpoint.setCondition (conditionsPanel.getCondition()); breakpoint.setHitCountFilter(conditionsPanel.getHitCount(), conditionsPanel.getHitCountFilteringStyle()); diff --git a/java/debugger.jpda.ui/src/org/netbeans/modules/debugger/jpda/ui/models/BreakpointsNodeModel.java b/java/debugger.jpda.ui/src/org/netbeans/modules/debugger/jpda/ui/models/BreakpointsNodeModel.java index 0131536307ee..d0710b4a2330 100644 --- a/java/debugger.jpda.ui/src/org/netbeans/modules/debugger/jpda/ui/models/BreakpointsNodeModel.java +++ b/java/debugger.jpda.ui/src/org/netbeans/modules/debugger/jpda/ui/models/BreakpointsNodeModel.java @@ -19,9 +19,11 @@ package org.netbeans.modules.debugger.jpda.ui.models; +import java.util.Arrays; import java.util.Map; import java.util.Vector; import java.util.WeakHashMap; +import java.util.stream.Collectors; import org.netbeans.api.debugger.Breakpoint; import org.netbeans.api.debugger.Breakpoint.VALIDITY; import org.netbeans.api.debugger.DebuggerManager; @@ -107,13 +109,18 @@ public String getDisplayName (Object o) throws UnknownTypeException { } return bold ( b, - b.getLambdaIndex() >= 0 ? + b.getLambdaIndex().length > 0 ? NbBundle.getMessage ( BreakpointsNodeModel.class, "CTL_Line_Lambda_Breakpoint", EditorContextBridge.getFileName (b), line, - b.getLambdaIndex() + Arrays.stream(b.getLambdaIndex()) + .mapToObj(i -> switch (i) { + case LineBreakpoint.LAMBDA_INDEX_STOP_OUTSIDE -> NbBundle.getMessage(BreakpointsNodeModel.class, "CTL_Line_Lambda_Breakpoint_Outside"); + default -> NbBundle.getMessage(BreakpointsNodeModel.class, "CTL_Line_Lambda_Breakpoint_OnLambda", i + 1); + }) + .collect(Collectors.joining(", ")) ) : NbBundle.getMessage ( diff --git a/java/debugger.jpda.ui/src/org/netbeans/modules/debugger/jpda/ui/models/Bundle.properties b/java/debugger.jpda.ui/src/org/netbeans/modules/debugger/jpda/ui/models/Bundle.properties index b48aa4aefcce..d8126856025d 100644 --- a/java/debugger.jpda.ui/src/org/netbeans/modules/debugger/jpda/ui/models/Bundle.properties +++ b/java/debugger.jpda.ui/src/org/netbeans/modules/debugger/jpda/ui/models/Bundle.properties @@ -23,7 +23,9 @@ ACSD_Breakpoint_Customizer_Dialog=Customize this breakpoint's properties #BreakpointsNodeModel CTL_Line_Breakpoint=Line {0}:{1} -CTL_Line_Lambda_Breakpoint=Line {0}:{1}, lambda index: {2} +CTL_Line_Lambda_Breakpoint=Line {0}:{1}, stopping: {2} +CTL_Line_Lambda_Breakpoint_Outside=outside lambdas +CTL_Line_Lambda_Breakpoint_OnLambda=on {0}. lambda CTL_Thread_Started_Breakpoint=Thread started CTL_Thread_Death_Breakpoint=Thread death CTL_Thread_Breakpoint=Thread start / death diff --git a/java/debugger.jpda/src/org/netbeans/modules/debugger/jpda/breakpoints/BreakpointsReader.java b/java/debugger.jpda/src/org/netbeans/modules/debugger/jpda/breakpoints/BreakpointsReader.java index 659b7b0ee688..f0204ca865de 100644 --- a/java/debugger.jpda/src/org/netbeans/modules/debugger/jpda/breakpoints/BreakpointsReader.java +++ b/java/debugger.jpda/src/org/netbeans/modules/debugger/jpda/breakpoints/BreakpointsReader.java @@ -24,6 +24,7 @@ import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; +import java.util.Arrays; import java.util.Collections; import java.util.Map; import java.util.Set; @@ -108,7 +109,7 @@ public Object read (String typeID, Properties properties) { LineBreakpoint lb = LineBreakpoint.create ("", 0); lb.setURL(properties.getString (LineBreakpoint.PROP_URL, null)); lb.setLineNumber(properties.getInt (LineBreakpoint.PROP_LINE_NUMBER, 1)); - lb.setLambdaIndex(properties.getInt (LineBreakpoint.PROP_LAMBDA_INDEX, -1)); + lb.setLambdaIndex(Arrays.stream(properties.getArray(LineBreakpoint.PROP_LAMBDA_INDEX, new Object[0])).mapToInt(v -> (Integer) v).toArray()); lb.setCondition ( properties.getString (LineBreakpoint.PROP_CONDITION, "") ); @@ -371,9 +372,9 @@ public void write (Object object, Properties properties) { LineBreakpoint.PROP_LINE_NUMBER, lb.getLineNumber () ); - properties.setInt ( + properties.setArray( LineBreakpoint.PROP_LAMBDA_INDEX, - lb.getLambdaIndex () + Arrays.stream(lb.getLambdaIndex()).mapToObj(v -> v).toArray() ); properties.setString ( LineBreakpoint.PROP_CONDITION, diff --git a/java/debugger.jpda/src/org/netbeans/modules/debugger/jpda/breakpoints/LineBreakpointImpl.java b/java/debugger.jpda/src/org/netbeans/modules/debugger/jpda/breakpoints/LineBreakpointImpl.java index b3e9e22dac68..ba65c249686d 100644 --- a/java/debugger.jpda/src/org/netbeans/modules/debugger/jpda/breakpoints/LineBreakpointImpl.java +++ b/java/debugger.jpda/src/org/netbeans/modules/debugger/jpda/breakpoints/LineBreakpointImpl.java @@ -48,6 +48,7 @@ import java.net.URISyntaxException; import java.net.URL; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; @@ -96,6 +97,7 @@ import org.openide.util.Exceptions; import org.openide.util.NbBundle; import java.util.LinkedHashMap; +import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; import static java.util.stream.Collectors.collectingAndThen; @@ -116,7 +118,7 @@ public class LineBreakpointImpl extends ClassBasedBreakpoint { private int lineNumber; private int breakpointLineNumber; - private int lambdaIndex; + private int[] lambdaIndex; private int lineNumberForUpdate = -1; private final Object lineLock = new Object(); private BreakpointsReader reader; @@ -145,7 +147,7 @@ private void updateLineNumber() { lb, getDebugger()); int lbln = lb.getLineNumber(); - int li = lb.getLambdaIndex(); + int[] li = lb.getLambdaIndex(); synchronized (lineLock) { breakpointLineNumber = lbln; lineNumber = theLineNumber; @@ -327,7 +329,7 @@ protected void classLoaded (List referenceTypes) { String failReason = null; ReferenceType noLocRefType = null; int lineNumberToSet; - int lambdaIndexToSet; + int[] lambdaIndexToSet; final int origBreakpointLineNumber; int newBreakpointLineNumber; synchronized (lineLock) { @@ -707,25 +709,36 @@ private static String normalize(String path) { return path; } - private List filterLocationsByLambdaIndex(List locations, int lambdaIndex) { - if (lambdaIndex == Integer.MIN_VALUE) { + private List filterLocationsByLambdaIndex(List locations, int[] lambdaIndex) { + if (lambdaIndex.length == 0) { return locations; - } else if (lambdaIndex == (-1)) { - return locations.stream() - .filter(l -> !l.method().name().startsWith("lambda$")) - .collect(Collectors.toList()); } else { - List filtered = - locations.stream() - .filter(l -> l.method().name().startsWith("lambda$")) - .collect(Collectors.toList()); + Map> lambda2Locations = new LinkedHashMap<>(); + List outsideOfLambda = new ArrayList<>(); - Collections.reverse(filtered); - if (lambdaIndex < filtered.size()) { - return Collections.singletonList(filtered.get(lambdaIndex)); - } else { - return filtered; + for (Location l : locations) { + if (l.method().name().startsWith("lambda$")) { + lambda2Locations.computeIfAbsent(l.method().name(), k -> new ArrayList<>()).add(l); + } else { + outsideOfLambda.add(l); + } } + + List lambdas = new ArrayList<>(lambda2Locations.keySet()); + + Collections.reverse(lambdas); + + List result = new ArrayList<>(); + + for (int index : lambdaIndex) { + if (index == LineBreakpoint.LAMBDA_INDEX_STOP_OUTSIDE) { + result.addAll(outsideOfLambda); + } else if (index >= 0 && index < lambdas.size()) { + result.addAll(lambda2Locations.get(lambdas.get(index))); + } + } + + return result; } } diff --git a/java/debugger.jpda/test/unit/src/org/netbeans/api/debugger/jpda/ExpressionLambdaBreakpointTest.java b/java/debugger.jpda/test/unit/src/org/netbeans/api/debugger/jpda/ExpressionLambdaBreakpointTest.java index 643ffdaee29f..bdb3fa68d3b6 100644 --- a/java/debugger.jpda/test/unit/src/org/netbeans/api/debugger/jpda/ExpressionLambdaBreakpointTest.java +++ b/java/debugger.jpda/test/unit/src/org/netbeans/api/debugger/jpda/ExpressionLambdaBreakpointTest.java @@ -40,11 +40,13 @@ */ public class ExpressionLambdaBreakpointTest extends NbTestCase { - private static final String TEST_APP_PATH = System.getProperty ("test.dir.src") + + private static final String TEST_APP_PATH = System.getProperty ("test.dir.src") + "org/netbeans/api/debugger/jpda/testapps/ExpressionLambdaBreakpointApp.java"; - + private static final String TEST_MULTI_LINE_APP_PATH = System.getProperty ("test.dir.src") + + "org/netbeans/api/debugger/jpda/testapps/ExpressionLambdaBreakpointMultiLineApp.java"; + private JPDASupport support; - + public ExpressionLambdaBreakpointTest (String s) { super (s); } @@ -52,13 +54,13 @@ public ExpressionLambdaBreakpointTest (String s) { public static Test suite() { return JPDASupport.createTestSuite(ExpressionLambdaBreakpointTest.class); } - + public void testLambdaBreakpointsStopAll() throws Exception { try { Utils.BreakPositions bp = Utils.getBreakPositions(TEST_APP_PATH); LineBreakpoint[] lb = bp.getBreakpoints().toArray(new LineBreakpoint[0]); - lb[0].setLambdaIndex(Integer.MIN_VALUE); //stop on all locations + lb[0].setLambdaIndex(new int[0]); //stop on all locations DebuggerManager dm = DebuggerManager.getDebuggerManager (); for (int i = 0; i < lb.length; i++) { @@ -73,33 +75,33 @@ public void testLambdaBreakpointsStopAll() throws Exception { support = JPDASupport.attach ( "org.netbeans.api.debugger.jpda.testapps.ExpressionLambdaBreakpointApp" ); - + JPDADebugger debugger = support.getDebugger(); int lambdaBpLineHitCount = 6; //total list vaues + 1 for (int j = 0; j < lambdaBpLineHitCount; j++) { support.waitState (JPDADebugger.STATE_STOPPED); // j-th breakpoint hit assertEquals ( - "Debugger stopped at wrong line for breakpoint", - lb[0].getLineNumber (), + "Debugger stopped at wrong line for breakpoint", + lb[0].getLineNumber (), debugger.getCurrentCallStackFrame ().getLineNumber (null) ); - + if (j == 0) { support.stepOver(); assertEquals ( - "Debugger stopped at wrong line for breakpoint", - lb[0].getLineNumber ()+ 1, + "Debugger stopped at wrong line for breakpoint", + lb[0].getLineNumber ()+ 1, debugger.getCurrentCallStackFrame ().getLineNumber (null) ); } support.doContinue(); } - + support.waitState (JPDADebugger.STATE_STOPPED); - + assertEquals ( - "Debugger stopped at wrong line for breakpoint", - lb[1].getLineNumber (), + "Debugger stopped at wrong line for breakpoint", + lb[1].getLineNumber (), debugger.getCurrentCallStackFrame ().getLineNumber (null) ); @@ -111,7 +113,7 @@ public void testLambdaBreakpointsStopAll() throws Exception { } Map variablesByName = getVariablesByName(debugger.getCurrentCallStackFrame ().getLocalVariables()); assertTrue("Wrong computation value of lambda expression filter",checkMirrorValues(variablesByName, new Object[]{"a","b","c"})); - + support.doContinue (); support.waitState (JPDADebugger.STATE_DISCONNECTED); } finally { @@ -124,7 +126,7 @@ public void testLambdaBreakpointsStopAtLambda() throws Exception { Utils.BreakPositions bp = Utils.getBreakPositions(TEST_APP_PATH); LineBreakpoint[] lb = bp.getBreakpoints().toArray(new LineBreakpoint[0]); - lb[0].setLambdaIndex(0); //stop inside lambda + lb[0].setLambdaIndex(new int[] {0}); //stop inside lambda DebuggerManager dm = DebuggerManager.getDebuggerManager (); for (int i = 0; i < lb.length; i++) { @@ -139,14 +141,14 @@ public void testLambdaBreakpointsStopAtLambda() throws Exception { support = JPDASupport.attach ( "org.netbeans.api.debugger.jpda.testapps.ExpressionLambdaBreakpointApp" ); - + JPDADebugger debugger = support.getDebugger(); Object[] expectedValues = new Object[]{"a", "", "b", "", "c"}; for (int j = 0; j < expectedValues.length; j++) { support.waitState (JPDADebugger.STATE_STOPPED); // j-th breakpoint hit assertEquals ( - "Debugger stopped at wrong line for breakpoint", - lb[0].getLineNumber (), + "Debugger stopped at wrong line for breakpoint", + lb[0].getLineNumber (), debugger.getCurrentCallStackFrame ().getLineNumber (null) ); @@ -156,12 +158,12 @@ public void testLambdaBreakpointsStopAtLambda() throws Exception { support.doContinue(); } - + support.waitState (JPDADebugger.STATE_STOPPED); - + assertEquals ( - "Debugger stopped at wrong line for breakpoint", - lb[1].getLineNumber (), + "Debugger stopped at wrong line for breakpoint", + lb[1].getLineNumber (), debugger.getCurrentCallStackFrame ().getLineNumber (null) ); @@ -171,7 +173,7 @@ public void testLambdaBreakpointsStopAtLambda() throws Exception { for (int i = 0; i < tb.length; i++) { dm.removeBreakpoint (lb[i]); } - + support.doContinue (); support.waitState (JPDADebugger.STATE_DISCONNECTED); } finally { @@ -184,7 +186,7 @@ public void testLambdaBreakpointsStopOutsideOfLambda() throws Exception { Utils.BreakPositions bp = Utils.getBreakPositions(TEST_APP_PATH); LineBreakpoint[] lb = bp.getBreakpoints().toArray(new LineBreakpoint[0]); - lb[0].setLambdaIndex(-1); //stop outside lambda + lb[0].setLambdaIndex(new int[] {LineBreakpoint.LAMBDA_INDEX_STOP_OUTSIDE}); //stop outside lambda DebuggerManager dm = DebuggerManager.getDebuggerManager (); for (int i = 0; i < lb.length; i++) { @@ -199,13 +201,71 @@ public void testLambdaBreakpointsStopOutsideOfLambda() throws Exception { support = JPDASupport.attach ( "org.netbeans.api.debugger.jpda.testapps.ExpressionLambdaBreakpointApp" ); - + JPDADebugger debugger = support.getDebugger(); support.waitState (JPDADebugger.STATE_STOPPED); // j-th breakpoint hit assertEquals ( - "Debugger stopped at wrong line for breakpoint", - lb[0].getLineNumber (), + "Debugger stopped at wrong line for breakpoint", + lb[0].getLineNumber (), + debugger.getCurrentCallStackFrame ().getLineNumber (null) + ); + + Map variablesByName = getVariablesByName(debugger.getCurrentCallStackFrame ().getLocalVariables()); + + assertEquals(String.valueOf(variablesByName), Set.of("args"), variablesByName.keySet()); + + support.doContinue(); + + support.waitState (JPDADebugger.STATE_STOPPED); + + assertEquals ( + "Debugger stopped at wrong line for breakpoint", + lb[1].getLineNumber (), + debugger.getCurrentCallStackFrame ().getLineNumber (null) + ); + + for (int i = 0; i < tb.length; i++) { + tb[i].checkResult (); + } + for (int i = 0; i < tb.length; i++) { + dm.removeBreakpoint (lb[i]); + } + + support.doContinue (); + support.waitState (JPDADebugger.STATE_DISCONNECTED); + } finally { + if (support != null) support.doFinish (); + } + } + + public void testMultiLineLambdaBreakpoints() throws Exception { + try { + Utils.BreakPositions bp = Utils.getBreakPositions(TEST_MULTI_LINE_APP_PATH); + LineBreakpoint[] lb = bp.getBreakpoints().toArray(new LineBreakpoint[0]); + + lb[0].setLambdaIndex(new int[] {LineBreakpoint.LAMBDA_INDEX_STOP_OUTSIDE, 0}); //stop outside lambda + + DebuggerManager dm = DebuggerManager.getDebuggerManager (); + for (int i = 0; i < lb.length; i++) { + dm.addBreakpoint (lb[i]); + } + + TestBreakpointListener[] tb = new TestBreakpointListener[lb.length]; + for (int i = 0; i < lb.length; i++) { + tb[i] = new TestBreakpointListener (lb[i]); + lb[i].addJPDABreakpointListener (tb[i]); + } + support = JPDASupport.attach ( + "org.netbeans.api.debugger.jpda.testapps.ExpressionLambdaBreakpointMultiLineApp" + ); + + JPDADebugger debugger = support.getDebugger(); + + support.waitState (JPDADebugger.STATE_STOPPED); + assertEquals ( + "Debugger stopped at wrong line for breakpoint", + lb[0].getLineNumber (), debugger.getCurrentCallStackFrame ().getLineNumber (null) ); @@ -214,12 +274,27 @@ public void testLambdaBreakpointsStopOutsideOfLambda() throws Exception { assertEquals(String.valueOf(variablesByName), Set.of("args"), variablesByName.keySet()); support.doContinue(); - + + for (int i = 0; i < 5; i++) { + support.waitState (JPDADebugger.STATE_STOPPED); + assertEquals ( + "Debugger stopped at wrong line for breakpoint", + lb[0].getLineNumber (), + debugger.getCurrentCallStackFrame ().getLineNumber (null) + ); + + Map lambdaVariablesByName = getVariablesByName(debugger.getCurrentCallStackFrame ().getLocalVariables()); + + assertEquals(String.valueOf(lambdaVariablesByName), Set.of("l1"), lambdaVariablesByName.keySet()); + + support.doContinue(); + } + support.waitState (JPDADebugger.STATE_STOPPED); - + assertEquals ( - "Debugger stopped at wrong line for breakpoint", - lb[1].getLineNumber (), + "Debugger stopped at wrong line for breakpoint", + lb[1].getLineNumber (), debugger.getCurrentCallStackFrame ().getLineNumber (null) ); @@ -229,7 +304,7 @@ public void testLambdaBreakpointsStopOutsideOfLambda() throws Exception { for (int i = 0; i < tb.length; i++) { dm.removeBreakpoint (lb[i]); } - + support.doContinue (); support.waitState (JPDADebugger.STATE_DISCONNECTED); } finally { @@ -245,7 +320,7 @@ private static Map getVariablesByName(LocalVariable[] vars) { } return map; } - + private boolean checkMirrorValues(Map mirrorValues, Object[] actualValues){ String variableNameKey = "nonEmptyListCollection"; if(mirrorValues.containsKey(variableNameKey)){ @@ -269,7 +344,7 @@ public TestBreakpointListener (LineBreakpoint lineBreakpoint) { } public TestBreakpointListener ( - LineBreakpoint lineBreakpoint, + LineBreakpoint lineBreakpoint, int conditionResult ) { this.lineBreakpoint = lineBreakpoint; @@ -290,21 +365,21 @@ public void breakpointReached (JPDABreakpointEvent event) { private void checkEvent (JPDABreakpointEvent event) { this.event = event; assertEquals ( - "Breakpoint event: Wrong source breakpoint", - lineBreakpoint, + "Breakpoint event: Wrong source breakpoint", + lineBreakpoint, event.getSource () ); assertNotNull ( - "Breakpoint event: Context thread is null", + "Breakpoint event: Context thread is null", event.getThread () ); int result = event.getConditionResult (); - if ( result == JPDABreakpointEvent.CONDITION_FAILED && + if ( result == JPDABreakpointEvent.CONDITION_FAILED && conditionResult != JPDABreakpointEvent.CONDITION_FAILED ) failure = new AssertionError (event.getConditionException ()); - else + else if (result != conditionResult) failure = new AssertionError ( "Unexpected breakpoint condition result: " + result @@ -325,7 +400,7 @@ public void checkResult () { } if (failure != null) throw failure; } - + } - + } diff --git a/java/debugger.jpda/test/unit/src/org/netbeans/api/debugger/jpda/testapps/ExpressionLambdaBreakpointMultiLineApp.java b/java/debugger.jpda/test/unit/src/org/netbeans/api/debugger/jpda/testapps/ExpressionLambdaBreakpointMultiLineApp.java new file mode 100644 index 000000000000..b8940493566e --- /dev/null +++ b/java/debugger.jpda/test/unit/src/org/netbeans/api/debugger/jpda/testapps/ExpressionLambdaBreakpointMultiLineApp.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.netbeans.api.debugger.jpda.testapps; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Sample lambda expression breakpoints application. + * @author aksinsin + */ +public class ExpressionLambdaBreakpointMultiLineApp { + + public static void main(String... args) { + + List nonEmptyListCollection = Arrays.stream(new String[]{"a", "", "b", "", "c"}) + .map(l1 -> l1).map(l2 -> l2).map(l3 -> l3) // LBREAKPOINT + .collect(Collectors.toList()); + System.out.println(nonEmptyListCollection); // LBREAKPOINT + } +} diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/breakpoints/NbBreakpoint.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/breakpoints/NbBreakpoint.java index 93c753a5864c..537527f68454 100644 --- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/breakpoints/NbBreakpoint.java +++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/breakpoints/NbBreakpoint.java @@ -35,8 +35,6 @@ import org.eclipse.lsp4j.Position; import org.eclipse.lsp4j.debug.BreakpointEventArguments; -import org.eclipse.lsp4j.debug.BreakpointLocation; -import org.eclipse.lsp4j.debug.BreakpointLocationsResponse; import org.eclipse.lsp4j.debug.Source; import org.netbeans.api.debugger.Breakpoint; @@ -49,7 +47,6 @@ import org.netbeans.modules.java.lsp.server.debugging.DebugAdapterContext; import org.openide.filesystems.FileObject; import org.openide.filesystems.URLMapper; -import org.openide.util.Exceptions; /** * @@ -192,12 +189,11 @@ public CompletableFuture install() { return CompletableFuture.completedFuture(this); } - private int getLambdaIndex(int line, int column) { - //TODO: performance!! + private int[] getLambdaIndex(int line, int column) { try { FileObject file = Utils.fromUri(sourceURL); JavaSource js = JavaSource.forFileObject(file); - int[] index = new int[] { -1 }; + int[] index = new int[] { LineBreakpoint.LAMBDA_INDEX_STOP_OUTSIDE }; js.runUserActionTask(cc -> { cc.toPhase(JavaSource.Phase.PARSED); //TODO: span only! @@ -217,12 +213,12 @@ public Void visitLambdaExpression(LambdaExpressionTree tree, Void v) { } }.scan(cc.getCompilationUnit(), null); }, true); - return index[0]; + return index; } catch (IOException ex) { ex.printStackTrace(); } - return Integer.MIN_VALUE; + return new int[0]; } private static final String lsp2NBLogMessage(String message) { return message.replaceAll("\\{([^\\}]+)\\}", "{=$1}"); // NOI18N