Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2007, 2014 IBM Corporation and others.
* Copyright (c) 2007, 2026 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
Expand All @@ -19,10 +19,14 @@
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.debug.core.model.IBreakpoint;
import org.eclipse.debug.ui.actions.ToggleBreakpointAction;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.debug.core.IJavaMethodBreakpoint;
import org.eclipse.jdt.debug.core.IJavaThread;
import org.eclipse.jdt.debug.core.JDIDebugModel;
import org.eclipse.jdt.debug.tests.TestUtil;
import org.eclipse.jdt.debug.tests.VerticalRulerInfoStub;
import org.eclipse.jdt.debug.tests.ui.AbstractDebugUiTests;
import org.eclipse.ui.IEditorPart;

Expand Down Expand Up @@ -72,6 +76,8 @@ private IJavaMethodBreakpoint createBreakpoint(int line) throws Exception {
assertTrue("Missing file", resource instanceof IFile);
IEditorPart editor = openEditor((IFile) resource);
IBreakpoint breakpoint = toggleBreakpoint(editor, line);
// breakpointAdded fires before the toggle job finishes setting up the breakpoint
TestUtil.waitForJobs(getName(), 100, DEFAULT_TIMEOUT);
assertTrue("Wrong breakpoint", breakpoint instanceof IJavaMethodBreakpoint);
return (IJavaMethodBreakpoint) breakpoint;
}
Expand Down Expand Up @@ -130,4 +136,105 @@ public void testGenericArrayEntryBreakpoints() throws Exception {
removeAllBreakpoints();
}
}

/** Line of the <code>methodTypeParameter</code> declaration in {@link #getTypeName()} */
private static final int METHOD_LINE = 37;

/** Line of the closing brace of <code>methodTypeParameter</code> in {@link #getTypeName()} */
private static final int CLOSING_BRACE_LINE = 39;

/**
* Toggling on the closing brace creates an exit only breakpoint, located on the closing brace line.
*/
public void testToggleOnClosingBraceCreatesExitOnlyBreakpoint() throws Exception {
try {
IJavaMethodBreakpoint breakpoint = createBreakpoint(CLOSING_BRACE_LINE);
Comment thread
SougandhS marked this conversation as resolved.
Resolved
assertFalse("Should not be an entry breakpoint", breakpoint.isEntry());
assertTrue("Should be an exit breakpoint", breakpoint.isExit());
assertEquals("Wrong line", CLOSING_BRACE_LINE, breakpoint.getLineNumber());
} finally {
removeAllBreakpoints();
}
}

/**
* Toggling on the closing brace of an entry breakpoint adds exit to the same breakpoint, which stays on the method line. Toggling again
* removes exit.
*/
public void testToggleOnClosingBraceOfEntryBreakpoint() throws Exception {
try {
IJavaMethodBreakpoint breakpoint = createBreakpoint(METHOD_LINE);
assertTrue("Should be an entry breakpoint", breakpoint.isEntry());
assertFalse("Should not be an exit breakpoint", breakpoint.isExit());
IEditorPart editor = openEditor();

toggle(editor, CLOSING_BRACE_LINE);
assertSame("Breakpoint should be kept", breakpoint, getSingleBreakpoint());
assertTrue("Should be an entry breakpoint", breakpoint.isEntry());
assertTrue("Should be an exit breakpoint", breakpoint.isExit());
assertEquals("Should stay on the method line", -1, breakpoint.getLineNumber());

toggle(editor, CLOSING_BRACE_LINE);
assertSame("Breakpoint should be kept", breakpoint, getSingleBreakpoint());
assertTrue("Should be an entry breakpoint", breakpoint.isEntry());
assertFalse("Should not be an exit breakpoint", breakpoint.isExit());
} finally {
removeAllBreakpoints();
}
}

/**
* Toggling on the method line of an exit only breakpoint adds entry to the same breakpoint and moves it to the method line.
*/
public void testToggleOnMethodLineOfExitOnlyBreakpoint() throws Exception {
try {
IJavaMethodBreakpoint breakpoint = createBreakpoint(CLOSING_BRACE_LINE);
assertFalse("Should not be an entry breakpoint", breakpoint.isEntry());
assertTrue("Should be an exit breakpoint", breakpoint.isExit());

toggle(openEditor(), METHOD_LINE);
assertSame("Breakpoint should be kept", breakpoint, getSingleBreakpoint());
assertTrue("Should be an entry breakpoint", breakpoint.isEntry());
assertTrue("Should be an exit breakpoint", breakpoint.isExit());
assertEquals("Should be on the method line", -1, breakpoint.getLineNumber());
} finally {
removeAllBreakpoints();
}
}

/**
* Toggling on the closing brace of an exit only breakpoint removes it.
*/
public void testToggleOnClosingBraceOfExitOnlyBreakpoint() throws Exception {
try {
createBreakpoint(CLOSING_BRACE_LINE);

toggle(openEditor(), CLOSING_BRACE_LINE);
assertEquals("Breakpoint should be removed", 0, getBreakpointManager().getBreakpoints(JDIDebugModel.getPluginIdentifier()).length);
} finally {
removeAllBreakpoints();
}
}

private IEditorPart openEditor() throws Exception {
IType type = get15Project().findType(getTypeName());
assertNotNull("Missing file", type);
IResource resource = type.getResource();
assertTrue("Missing file", resource instanceof IFile);
return openEditor((IFile) resource);
}

/**
* Toggles at the given line (1 based) and waits for the asynchronous toggle job.
*/
private void toggle(IEditorPart editor, int line) throws Exception {
sync(() -> new ToggleBreakpointAction(editor, null, new VerticalRulerInfoStub(line - 1)).run());
TestUtil.waitForJobs(getName(), 100, DEFAULT_TIMEOUT);
}

private IBreakpoint getSingleBreakpoint() {
IBreakpoint[] breakpoints = getBreakpointManager().getBreakpoints(JDIDebugModel.getPluginIdentifier());
assertEquals("Wrong number of breakpoints", 1, breakpoints.length);
return breakpoints[0];
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2006, 2021 IBM Corporation and others.
* Copyright (c) 2006, 2026 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
Expand All @@ -22,7 +22,11 @@
import org.eclipse.debug.core.model.IBreakpoint;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.Block;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.NodeFinder;
import org.eclipse.jdt.core.manipulation.SharedASTProviderCore;
import org.eclipse.jdt.debug.core.IJavaLineBreakpoint;
import org.eclipse.jdt.debug.core.IJavaMethodBreakpoint;
Expand Down Expand Up @@ -112,6 +116,18 @@ public boolean updateMarker(IMarker marker, IDocument document, Position positio
return false;
}
try {
if (breakpoint instanceof IJavaMethodBreakpoint mBreakpoint && !mBreakpoint.isLambdaBreakpoint() && mBreakpoint.isExit() && !mBreakpoint.isEntry()
&& MarkerUtilities.getLineNumber(marker) != -1) {
// exit-only method breakpoint is located on the closing brace of its method: keep it only while that is still the case
if (!isClosingBraceOfMethod(unit, position.getOffset(), mBreakpoint.getMethodName())) {
return false;
}
int exitLine = document.getLineOfOffset(position.getOffset()) + 1;
if (MarkerUtilities.getLineNumber(marker) != exitLine) {
MarkerUtilities.setLineNumber(marker, exitLine);
}
return true;
}
ValidBreakpointLocationLocator loc;
if (breakpoint instanceof IJavaMethodBreakpoint && ((IJavaMethodBreakpoint) breakpoint).isEntry()) {
IMarker m = breakpoint.getMarker();
Expand Down Expand Up @@ -162,6 +178,26 @@ public boolean updateMarker(IMarker marker, IDocument document, Position positio
return false;
}

/**
* Returns whether the given offset is the closing brace of the body of the method with the given name.
*
* @param unit the AST of the current source
* @param offset the offset to check
* @param methodName the method name of the breakpoint, <code>&lt;init&gt;</code> for constructors
* @return <code>true</code> if the offset is the closing brace of the method, <code>false</code> otherwise
*/
private boolean isClosingBraceOfMethod(CompilationUnit unit, int offset, String methodName) {
ASTNode node = NodeFinder.perform(unit, offset, 1);
if (!(node instanceof Block block) || !(block.getParent() instanceof MethodDeclaration method)) {
return false;
}
if (block.getStartPosition() + block.getLength() - 1 != offset) {
return false;
}
String name = method.isConstructor() ? "<init>" : method.getName().getIdentifier(); //$NON-NLS-1$
return name.equals(methodName);
}

/**
* Updates the charstart and charend ranges if necessary for the given line.
* Returns immediately if the line is not valid (< 0 or greater than the total line number count)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2000, 2012 IBM Corporation and others.
* Copyright (c) 2000, 2026 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
Expand Down Expand Up @@ -28,6 +28,7 @@
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IMember;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.ISourceRange;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
Expand All @@ -36,6 +37,7 @@
import org.eclipse.jdt.debug.core.IJavaMethodBreakpoint;
import org.eclipse.jdt.debug.core.IJavaWatchpoint;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.source.Annotation;
Expand Down Expand Up @@ -265,6 +267,52 @@ public static void addRunToLineAttributes(Map<String, Object> attributes) {
attributes.put(RUN_TO_LINE, Boolean.TRUE);
}

/**
* Positions the given method breakpoint's marker: at the closing brace of the method if the breakpoint is an exit-only breakpoint, otherwise at
* the method name (the default location).
*
* @param breakpoint the method breakpoint
* @throws CoreException if accessing the breakpoint or the Java model fails
*/
public static void updateMethodBreakpointLocation(IJavaMethodBreakpoint breakpoint) throws CoreException {
if (breakpoint.isLambdaBreakpoint()) {
return;
}
IMethod method = getMethod(breakpoint);
if (method == null || !method.exists() || method.getTypeRoot() == null) {
return;
}
ISourceRange nameRange = method.getNameRange();
ISourceRange methodRange = method.getSourceRange();
if (nameRange == null || methodRange == null || nameRange.getOffset() < 0 || methodRange.getOffset() < 0) {
return;
}
int start = nameRange.getOffset();
int end = start + nameRange.getLength();
int line = -1;
if (breakpoint.isExit() && !breakpoint.isEntry()) {
String source = method.getCompilationUnit().getSource();
int closingBrace = methodRange.getOffset() + methodRange.getLength() - 1;
if (source == null || closingBrace < 0 || closingBrace >= source.length()) {
return;
}
try {
line = new Document(source).getLineOfOffset(closingBrace) + 1;
} catch (BadLocationException e) {
return;
}
start = closingBrace;
end = closingBrace + 1;
}
IMarker marker = breakpoint.getMarker();
if (marker.getAttribute(IMarker.CHAR_START, -1) == start && marker.getAttribute(IMarker.CHAR_END, -1) == end
&& marker.getAttribute(IMarker.LINE_NUMBER, -1) == line) {
return;
}
marker.setAttributes(new String[] { IMarker.CHAR_START, IMarker.CHAR_END, IMarker.LINE_NUMBER },
new Object[] { Integer.valueOf(start), Integer.valueOf(end), Integer.valueOf(line) });
}

/**
* Returns the method associated with the method entry
* breakpoint.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2000, 2025 IBM Corporation and others.
* Copyright (c) 2000, 2026 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
Expand All @@ -19,6 +19,7 @@
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jdt.debug.core.IJavaBreakpoint;
import org.eclipse.jdt.debug.core.IJavaMethodBreakpoint;
import org.eclipse.jdt.internal.debug.ui.BreakpointUtils;
import org.eclipse.jface.viewers.IStructuredSelection;

public class EntryToggleAction extends BreakpointToggleAction {
Expand All @@ -36,7 +37,9 @@ protected boolean getToggleState(IJavaBreakpoint breakpoint) throws CoreExceptio
*/
@Override
public void doAction(IJavaBreakpoint breakpoint) throws CoreException {
((IJavaMethodBreakpoint)breakpoint).setEntry(!((IJavaMethodBreakpoint)breakpoint).isEntry());
IJavaMethodBreakpoint methodBreakpoint = (IJavaMethodBreakpoint) breakpoint;
methodBreakpoint.setEntry(!methodBreakpoint.isEntry());
BreakpointUtils.updateMethodBreakpointLocation(methodBreakpoint);
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2000, 2025 IBM Corporation and others.
* Copyright (c) 2000, 2026 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
Expand All @@ -19,6 +19,7 @@
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jdt.debug.core.IJavaBreakpoint;
import org.eclipse.jdt.debug.core.IJavaMethodBreakpoint;
import org.eclipse.jdt.internal.debug.ui.BreakpointUtils;
import org.eclipse.jface.viewers.IStructuredSelection;

public class ExitToggleAction extends BreakpointToggleAction {
Expand All @@ -36,7 +37,12 @@ protected boolean getToggleState(IJavaBreakpoint breakpoint) throws CoreExceptio
*/
@Override
public void doAction(IJavaBreakpoint breakpoint) throws CoreException {
((IJavaMethodBreakpoint)breakpoint).setExit(!((IJavaMethodBreakpoint)breakpoint).isExit());
if (!(breakpoint instanceof IJavaMethodBreakpoint)) {
return;
}
IJavaMethodBreakpoint methodBreakpoint = (IJavaMethodBreakpoint) breakpoint;
methodBreakpoint.setExit(!methodBreakpoint.isExit());
BreakpointUtils.updateMethodBreakpointLocation(methodBreakpoint);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,20 @@ private static void doToggleMethodBreakpoint(IMethod member, String lambdaMethod
if (BreakpointToggleUtils.isToggleTracepoint()) {
deleteTracepoint(breakpoint, part, monitor);
BreakpointToggleUtils.setUnsetTracepoint(false);
} else if (ValidBreakpointLocationLocator.LOCATION_METHOD_CLOSE && breakpoint instanceof IJavaMethodBreakpoint existing && !existing.isLambdaBreakpoint()) {
// toggled at the method's closing brace: switch the exit mode of the existing breakpoint instead of deleting it
ValidBreakpointLocationLocator.LOCATION_METHOD_CLOSE = false;
if (existing.isExit() && !existing.isEntry()) {
Comment thread
SougandhS marked this conversation as resolved.
Resolved
deleteBreakpoint(breakpoint, part, monitor);
} else {
existing.setExit(!existing.isExit());
BreakpointUtils.updateMethodBreakpointLocation(existing);
}
} else if (!ValidBreakpointLocationLocator.LOCATION_METHOD_CLOSE && finalSelection instanceof ITextSelection && breakpoint instanceof IJavaMethodBreakpoint existing
&& !existing.isLambdaBreakpoint() && existing.isExit() && !existing.isEntry()) {
// toggled at the method line of an exit-only breakpoint: add entry instead of deleting it
existing.setEntry(true);
BreakpointUtils.updateMethodBreakpointLocation(existing);
} else {
if (ValidBreakpointLocationLocator.LOCATION_METHOD_CLOSE) {
ValidBreakpointLocationLocator.LOCATION_METHOD_CLOSE = false;
Expand Down Expand Up @@ -477,6 +491,7 @@ private static void doToggleMethodBreakpoint(IMethod member, String lambdaMethod
if (ValidBreakpointLocationLocator.LOCATION_METHOD_CLOSE) {
methodBreakpoint.setEntry(false);
methodBreakpoint.setExit(true);
BreakpointUtils.updateMethodBreakpointLocation(methodBreakpoint);
ValidBreakpointLocationLocator.LOCATION_METHOD_CLOSE = false;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2009, 2011 IBM Corporation and others.
* Copyright (c) 2009, 2026 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
Expand All @@ -17,6 +17,7 @@
import org.eclipse.debug.internal.ui.SWTFactory;
import org.eclipse.jdt.debug.core.IJavaBreakpoint;
import org.eclipse.jdt.debug.core.IJavaMethodBreakpoint;
import org.eclipse.jdt.internal.debug.ui.BreakpointUtils;
import org.eclipse.jdt.internal.debug.ui.propertypages.PropertyPageMessages;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
Expand Down Expand Up @@ -84,6 +85,7 @@ public void doSave() throws CoreException {
if (breakpoint instanceof IJavaMethodBreakpoint watchpoint) {
watchpoint.setEntry(fEntry.getSelection());
watchpoint.setExit(fExit.getSelection());
BreakpointUtils.updateMethodBreakpointLocation(watchpoint);
}
}
}
Loading