diff --git a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AutomatedSuite.java b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AutomatedSuite.java index a5e5d591c9..08395e4b80 100644 --- a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AutomatedSuite.java +++ b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AutomatedSuite.java @@ -29,6 +29,7 @@ import org.eclipse.jdt.debug.tests.breakpoints.ConditionalBreakpointsWithFileClass; import org.eclipse.jdt.debug.tests.breakpoints.ConditionalBreakpointsWithGenerics; import org.eclipse.jdt.debug.tests.breakpoints.DeferredBreakpointTests; +import org.eclipse.jdt.debug.tests.breakpoints.DisableOnHitTest; import org.eclipse.jdt.debug.tests.breakpoints.ExceptionBreakpointTests; import org.eclipse.jdt.debug.tests.breakpoints.HitCountBreakpointsTests; import org.eclipse.jdt.debug.tests.breakpoints.ImportBreakpointsTest; @@ -399,6 +400,7 @@ public AutomatedSuite() { addTest(new TestSuite(JavaThreadEventHandlerTests.class)); addTest(new TestSuite(ConditionalBreakpointsWithFileClass.class)); addTest(new TestSuite(CompareObjectsTest.class)); + addTest(new TestSuite(DisableOnHitTest.class)); if (JavaProjectHelper.isJava8Compatible()) { addTest(new TestSuite(TestToggleBreakpointsTarget8.class)); diff --git a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/breakpoints/DisableOnHitTest.java b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/breakpoints/DisableOnHitTest.java new file mode 100644 index 0000000000..7b5386b70d --- /dev/null +++ b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/breakpoints/DisableOnHitTest.java @@ -0,0 +1,72 @@ +/******************************************************************************* + * Copyright (c) 2025 IBM Corporation and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.jdt.debug.tests.breakpoints; + +import org.eclipse.jdt.debug.core.IJavaLineBreakpoint; +import org.eclipse.jdt.debug.core.IJavaThread; +import org.eclipse.jdt.debug.tests.AbstractDebugTest; + +/** + * Tests for disable on hit breakpoints. + */ + +public class DisableOnHitTest extends AbstractDebugTest { + + /** + * Constructor + * + * @param name + * the name of the test + */ + public DisableOnHitTest(String name) { + super(name); + } + + public void testBreakpointHitWith() throws Exception { + String typeName = "TriggerPoint_01"; + IJavaLineBreakpoint bp1 = createLineBreakpoint(19, typeName); + IJavaLineBreakpoint bp2 = createLineBreakpoint(20, typeName); + bp1.setDisableOnHit(true); + IJavaThread thread = null; + try { + thread = launchToBreakpoint(typeName); + thread.resume(); + boolean isBp1Disabled = bp1.isEnabled(); + assertFalse("Breakpoint should be disabled after hit", isBp1Disabled); + bp1.delete(); + bp2.delete(); + } finally { + terminateAndRemove(thread); + removeAllBreakpoints(); + } + } + + public void testBreakpointHitWithout() throws Exception { + String typeName = "TriggerPoint_01"; + IJavaLineBreakpoint bp1 = createLineBreakpoint(19, typeName); + IJavaLineBreakpoint bp2 = createLineBreakpoint(20, typeName); + IJavaThread thread = null; + try { + thread = launchToBreakpoint(typeName); + thread.resume(); + boolean isBp1Disabled = bp1.isEnabled(); + assertTrue("Breakpoint should be not be disabled after hit", isBp1Disabled); + bp1.delete(); + bp2.delete(); + } finally { + terminateAndRemove(thread); + removeAllBreakpoints(); + } + } +} \ No newline at end of file diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/breakpoints/StandardJavaBreakpointEditor.java b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/breakpoints/StandardJavaBreakpointEditor.java index e36391bfd4..fdfcbd297a 100644 --- a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/breakpoints/StandardJavaBreakpointEditor.java +++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/breakpoints/StandardJavaBreakpointEditor.java @@ -20,6 +20,7 @@ import org.eclipse.debug.internal.ui.SWTFactory; import org.eclipse.jdt.debug.core.IJavaBreakpoint; import org.eclipse.jdt.debug.ui.breakpoints.JavaBreakpointConditionEditor; +import org.eclipse.jdt.internal.debug.core.breakpoints.JavaBreakpoint; import org.eclipse.jdt.internal.debug.ui.JDIDebugUIPlugin; import org.eclipse.jdt.internal.debug.ui.propertypages.PropertyPageMessages; import org.eclipse.jface.util.Util; @@ -48,6 +49,7 @@ public class StandardJavaBreakpointEditor extends AbstractJavaBreakpointEditor { private Button fResumeOnHit; private Button fSuspendVM; protected Button fTriggerPointButton; + protected Button fDisableOnHit; private final JavaBreakpointConditionEditor javaBpConditionEditor; @@ -158,6 +160,8 @@ public void widgetSelected(SelectionEvent e) { } protected Control createStandardControls(Composite parent) { + Composite comp = SWTFactory.createComposite(parent, parent.getFont(), 1, 1, 0, 0, 0); + fDisableOnHit = SWTFactory.createCheckButton(comp, PropertyPageMessages.BreakpointDisableOnHit, null, false, 2); Composite composite = SWTFactory.createComposite(parent, parent.getFont(), 4, 1, 0, 0, 0); fHitCountButton = SWTFactory.createCheckButton(composite, processMnemonics(PropertyPageMessages.JavaBreakpointPage_4), null, false, 1); fHitCountButton.setLayoutData(new GridData()); @@ -202,6 +206,15 @@ public void widgetSelected(SelectionEvent e) { setDirty(PROP_SUSPEND_POLICY); } }); + fDisableOnHit.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent event) { + boolean enabled = fDisableOnHit.getSelection(); + if (fBreakpoint instanceof JavaBreakpoint javaBp) { + javaBp.setDisableOnHit(enabled); + } + } + }); composite.addDisposeListener(new DisposeListener() { @Override public void widgetDisposed(DisposeEvent e) { @@ -249,6 +262,7 @@ protected void setBreakpoint(IJavaBreakpoint breakpoint) throws CoreException { String text = Util.ZERO_LENGTH_STRING; boolean suspendThread = true; boolean resumeOnHit = false; + boolean isDisableOnHit = false; if (breakpoint != null) { enabled = true; int hitCount = breakpoint.getHitCount(); @@ -258,6 +272,7 @@ protected void setBreakpoint(IJavaBreakpoint breakpoint) throws CoreException { } suspendThread= breakpoint.getSuspendPolicy() == IJavaBreakpoint.SUSPEND_THREAD; resumeOnHit = breakpoint.getSuspendPolicy() == IJavaBreakpoint.RESUME_ON_HIT && isTriggerPoint(); + isDisableOnHit = breakpoint.isDisableOnHit(); } fHitCountButton.setEnabled(enabled); fHitCountButton.setSelection(enabled && hasHitCount); @@ -271,6 +286,8 @@ protected void setBreakpoint(IJavaBreakpoint breakpoint) throws CoreException { fSuspendVM.setSelection(!suspendThread && !resumeOnHit); fTriggerPointButton.setEnabled(enabled); fTriggerPointButton.setSelection(isTriggerPoint()); + fDisableOnHit.setEnabled(!isTriggerPoint() && enabled); + fDisableOnHit.setSelection(isDisableOnHit); setDirty(false); } diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/propertypages/PropertyPageMessages.java b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/propertypages/PropertyPageMessages.java index 78b6a11fb5..3981157b92 100644 --- a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/propertypages/PropertyPageMessages.java +++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/propertypages/PropertyPageMessages.java @@ -121,5 +121,6 @@ public class PropertyPageMessages extends NLS { public static String BreakpointResumeOnHit; public static String BreakpointResumeConditionalTrue; public static String BreakpointResumeConditionalValue; + public static String BreakpointDisableOnHit; } diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/propertypages/PropertyPageMessages.properties b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/propertypages/PropertyPageMessages.properties index d734043042..28a9f7299f 100644 --- a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/propertypages/PropertyPageMessages.properties +++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/propertypages/PropertyPageMessages.properties @@ -94,3 +94,4 @@ VMCapabilitiesPropertyPage_31=This page displays optional debug capabilities tha BreakpointResumeOnHit=Continue execution on hit BreakpointResumeConditionalTrue=Resume when 'true' BreakpointResumeConditionalValue=Resume when value changes +BreakpointDisableOnHit=Disable on hit diff --git a/org.eclipse.jdt.debug/model/org/eclipse/jdt/debug/core/IJavaBreakpoint.java b/org.eclipse.jdt.debug/model/org/eclipse/jdt/debug/core/IJavaBreakpoint.java index 0a5322e241..f472491c89 100644 --- a/org.eclipse.jdt.debug/model/org/eclipse/jdt/debug/core/IJavaBreakpoint.java +++ b/org.eclipse.jdt.debug/model/org/eclipse/jdt/debug/core/IJavaBreakpoint.java @@ -275,4 +275,19 @@ public IJavaThread getThreadFilter(IJavaDebugTarget target) public boolean removeBreakpointListener(String identifier) throws CoreException; + /** + * Returns whether the breakpoint should disable on hit or not. + * + * @return whether the breakpoint should disable on hit or not. + * @since 3.23 + */ + public boolean isDisableOnHit(); + + /** + * Sets whether the breakpoint should disable on hit or not + * + * @since 3.23 + */ + public void setDisableOnHit(boolean disable); + } diff --git a/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/breakpoints/BreakpointListenerManager.java b/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/breakpoints/BreakpointListenerManager.java index 74b230a239..9c9c90ee05 100644 --- a/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/breakpoints/BreakpointListenerManager.java +++ b/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/breakpoints/BreakpointListenerManager.java @@ -151,6 +151,13 @@ public void breakpointHasRuntimeException(IJavaLineBreakpoint breakpoint, DebugE public int breakpointHit(IJavaThread thread, IJavaBreakpoint breakpoint) { IJavaBreakpointListener delegate = getDelegate(); if (delegate != null) { + try { + if (breakpoint.isDisableOnHit()) { + breakpoint.setEnabled(false); + } + } catch (CoreException e) { + JDIDebugPlugin.log(e); + } return delegate.breakpointHit(thread, breakpoint); } return IJavaBreakpointListener.DONT_CARE; diff --git a/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/breakpoints/JavaBreakpoint.java b/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/breakpoints/JavaBreakpoint.java index f864f17020..ff8f874d7a 100644 --- a/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/breakpoints/JavaBreakpoint.java +++ b/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/breakpoints/JavaBreakpoint.java @@ -177,6 +177,8 @@ public abstract class JavaBreakpoint extends Breakpoint implements IJavaBreakpoi protected static final String[] fgExpiredEnabledAttributes = new String[] { EXPIRED, ENABLED }; + private boolean disableOnHit; + public JavaBreakpoint() { fRequestsByTarget = new HashMap<>(1); fFilteredThreadsByTarget = new HashMap<>(1); @@ -1493,4 +1495,24 @@ private String[] readBreakpointListeners() throws CoreException { } return value.split(","); //$NON-NLS-1$ } + + /* + * (non-Javadoc) + * + * @see org.eclipse.jdt.debug.core.IJavaBreakpoint#isDisableOnHit() + */ + @Override + public boolean isDisableOnHit() { + return disableOnHit; + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.jdt.debug.core.IJavaBreakpoint#setDisableOnHit() + */ + @Override + public void setDisableOnHit(boolean disable) { + disableOnHit = disable; + } }