diff --git a/org.eclipse.jdt.debug.tests/testprograms/WatchItemContext.java b/org.eclipse.jdt.debug.tests/testprograms/WatchItemContext.java new file mode 100644 index 0000000000..76b0e86c6e --- /dev/null +++ b/org.eclipse.jdt.debug.tests/testprograms/WatchItemContext.java @@ -0,0 +1,44 @@ +/******************************************************************************* + * Copyright (c) 2026 IBM Corporation. + * + * 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 + *******************************************************************************/ + +/** + * WatchItemContext + */ +public class WatchItemContext { + + private class X { + private int x = 0; + } + + private class Y { + private X x = new X(); + private int y = 0; + } + + private class Z { + private Y y = new Y(); + private int z = 0; + } + + public static void main(String[] args) { + new WatchItemContext().foo(); + } + + public void foo() { + X x = new X(); + Y y = new Y(); + Z z = new Z(); + System.out.println(x + " " + y + " " + z); + } +} \ No newline at end of file diff --git a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AbstractDebugTest.java b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AbstractDebugTest.java index c700d5afa3..ddcf0d230b 100644 --- a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AbstractDebugTest.java +++ b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AbstractDebugTest.java @@ -219,7 +219,7 @@ public abstract class AbstractDebugTest extends TestCase implements IEvaluation "OutSync", "OutSync2", "ConsoleOutputUmlaut", "ErrorRecurrence", "ModelPresentationTests", "Bug565982", "SuspendVMConditionalBreakpointsTestSnippet", "FileConditionSnippet2", "compare.CompareObjectsStringTest", "compare.CompareListObjects", "compare.CompareMapObjects", "compare.CompareSetObjects", "compare.CompareNormalObjects", "compare.CompareArrayObjects", - "StatementStep", "StatementStepArgument", "StatementStepNested", "StatementStepWithOperations" }; + "StatementStep", "StatementStepArgument", "StatementStepNested", "StatementStepWithOperations", "WatchItemContext" }; /** * the default timeout diff --git a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/core/WatchExpressionTests.java b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/core/WatchExpressionTests.java index 157b68e435..1d46e01d6c 100644 --- a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/core/WatchExpressionTests.java +++ b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/core/WatchExpressionTests.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2015 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 @@ -13,10 +13,13 @@ *******************************************************************************/ package org.eclipse.jdt.debug.tests.core; +import java.util.List; + import org.eclipse.debug.core.DebugEvent; import org.eclipse.debug.core.DebugPlugin; import org.eclipse.debug.core.IExpressionManager; import org.eclipse.debug.core.model.IValue; +import org.eclipse.debug.core.model.IVariable; import org.eclipse.debug.core.model.IWatchExpression; import org.eclipse.debug.internal.ui.DebugUIPlugin; import org.eclipse.debug.ui.IDebugUIConstants; @@ -27,6 +30,7 @@ import org.eclipse.jdt.debug.testplugin.DebugElementEventWaiter; import org.eclipse.jdt.debug.testplugin.ExpressionWaiter; import org.eclipse.jdt.debug.tests.AbstractDebugTest; +import org.eclipse.jdt.internal.debug.ui.heapwalking.JavaNestedWatchExpressionFilter; import org.eclipse.swt.widgets.Display; import org.eclipse.ui.IWorkbench; import org.eclipse.ui.IWorkbenchPage; @@ -165,6 +169,32 @@ public void DisabledtestStepping() throws Exception { } } + public void testExpressionContext() throws Exception { + String typeName = "WatchItemContext"; + createLineBreakpoint(42, typeName); + IJavaThread thread = null; + try { + thread = launchToBreakpoint(typeName); + assertNotNull("Breakpoint not hit within timeout period", thread); + IJavaStackFrame stackFrame = (IJavaStackFrame) thread.getTopStackFrame(); + IVariable v1 = stackFrame.findVariable("y"); + IVariable v2 = v1.getValue().getVariables()[2]; + JavaNestedWatchExpressionFilter javaFilter = new JavaNestedWatchExpressionFilter(); + assertTrue(javaFilter.canCreateWatchExpression(List.of(v1, v2))); + String exp = javaFilter.createWatchExpression(List.of(v1, v2)); + assertEquals("Invalid Expression", "y.y", exp); + IWatchExpression expression = getExpressionManager().newWatchExpression(exp); + DebugElementEventWaiter waiter = new ExpressionWaiter(DebugEvent.CHANGE, expression); + getExpressionManager().addExpression(expression); + waiter.waitForEvent(); + assertEquals("Wrong Value", "0", expression.getValue().toString()); + } finally { + terminateAndRemove(thread); + removeAllBreakpoints(); + removeAllExpressions(); + } + } + /** * Dumps any error messages to the console. */ diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/heapwalking/JavaNestedWatchExpressionFilter.java b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/heapwalking/JavaNestedWatchExpressionFilter.java new file mode 100644 index 0000000000..d83495c379 --- /dev/null +++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/heapwalking/JavaNestedWatchExpressionFilter.java @@ -0,0 +1,91 @@ +/******************************************************************************* + * Copyright (c) 2026 IBM Corporation. + * + * 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.internal.debug.ui.heapwalking; + +import java.util.List; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.debug.core.DebugException; +import org.eclipse.debug.core.model.IVariable; +import org.eclipse.debug.internal.ui.views.variables.IndexedVariablePartition; +import org.eclipse.debug.ui.actions.IWatchExpressionFactoryAdapter2; +import org.eclipse.debug.ui.actions.IWatchExpressionFactoryAdapterExtension; +import org.eclipse.jdt.internal.debug.core.logicalstructures.JDIPlaceholderVariable; +import org.eclipse.jdt.internal.debug.core.model.JDIArrayEntryVariable; +import org.eclipse.jdt.internal.debug.core.model.JDIPlaceholderValue; +import org.eclipse.jdt.internal.debug.core.model.JDIReferenceListEntryVariable; +import org.eclipse.jdt.internal.debug.core.model.JDIReferenceListVariable; + +/** + * Uses the {@link IWatchExpressionFactoryAdapterExtension} to filter when the watch expression action is available based on the variable selected. + * + * Currently removes the action from {@link JDIPlaceholderVariable}s and {@link JDIReferenceListVariable}s. + */ +public class JavaNestedWatchExpressionFilter implements IWatchExpressionFactoryAdapter2 { + + @Override + public String createWatchExpression(Object element) throws CoreException { + if (element instanceof List expVariablesList) { + StringBuilder expresion = new StringBuilder(); + for (Object ob : expVariablesList) { + if (ob instanceof IVariable variable) { + String current = variable.getName(); + expresion.append(current); + expresion.append('.'); + } + } + expresion.deleteCharAt(expresion.length() - 1); + return expresion.toString(); + } + return null; + } + + @Override + public boolean canCreateWatchExpression(Object variable) { + if (variable instanceof List expVariablesList) { + for (Object ob : expVariablesList) { + if (ob instanceof IVariable variable2) { + return checkForValidVariable(variable2); + } + } + } + return false; + } + + /** + * Checks whether the given {@link IVariable} is suitable for creating a watch expression. + *

+ * Filters out variables that represent internal or synthetic structures (such as reference lists, array entries, indexed partitions) or + * placeholder values that do not have a concrete value. + *

+ * + * @param variable + * the variable to check + * @return {@code true} if the variable can be used to create a watch expression, {@code false} otherwise + */ + private boolean checkForValidVariable(IVariable variable) { + if (variable instanceof JDIReferenceListVariable || variable instanceof JDIReferenceListEntryVariable + || variable instanceof JDIArrayEntryVariable || variable instanceof IndexedVariablePartition) { + return false; + } + try { + if (variable.getValue() instanceof JDIPlaceholderValue) { + return false; + } + } catch (DebugException e) { + } + return true; + } + +} diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/variables/JavaDebugElementAdapterFactory.java b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/variables/JavaDebugElementAdapterFactory.java index 1a6400d67c..3827d5de86 100644 --- a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/variables/JavaDebugElementAdapterFactory.java +++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/variables/JavaDebugElementAdapterFactory.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2006, 2015 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 @@ -19,10 +19,12 @@ import org.eclipse.debug.internal.ui.viewers.model.provisional.IElementLabelProvider; import org.eclipse.debug.internal.ui.viewers.model.provisional.IElementMementoProvider; import org.eclipse.debug.ui.actions.IWatchExpressionFactoryAdapter; +import org.eclipse.debug.ui.actions.IWatchExpressionFactoryAdapter2; import org.eclipse.jdt.debug.core.IJavaStackFrame; import org.eclipse.jdt.debug.core.IJavaValue; import org.eclipse.jdt.debug.core.IJavaVariable; import org.eclipse.jdt.internal.debug.ui.display.JavaInspectExpression; +import org.eclipse.jdt.internal.debug.ui.heapwalking.JavaNestedWatchExpressionFilter; import org.eclipse.jdt.internal.debug.ui.heapwalking.JavaWatchExpressionFilter; /** @@ -33,6 +35,7 @@ * @see JavaVariableContentProvider * @see ExpressionLabelProvider * @see JavaExpressionContentProvider + * @see JavaNestedWatchExpressionFilter * @see JavaWatchExpressionFilter * @see JavaStackFrameMementoProvider * @since 3.3 @@ -44,6 +47,7 @@ public class JavaDebugElementAdapterFactory implements IAdapterFactory { private static final IElementLabelProvider fgLPExpression = new ExpressionLabelProvider(); private static final IElementContentProvider fgCPExpression = new JavaExpressionContentProvider(); private static final IWatchExpressionFactoryAdapter fgWEVariable = new JavaWatchExpressionFilter(); + private static final IWatchExpressionFactoryAdapter2 NestedWatchExpressionsAdapter = new JavaNestedWatchExpressionFilter(); private static final IElementMementoProvider fgMPStackFrame = new JavaStackFrameMementoProvider(); private static final IElementLabelProvider fgLPFrame = new JavaStackFrameLabelProvider(); @@ -83,6 +87,14 @@ public T getAdapter(Object adaptableObject, Class adapterType) { return (T) fgWEVariable; } } + if (IWatchExpressionFactoryAdapter2.class.equals(adapterType)) { + if (adaptableObject instanceof IJavaVariable) { + return (T) NestedWatchExpressionsAdapter; + } + if (adaptableObject instanceof JavaInspectExpression) { + return (T) NestedWatchExpressionsAdapter; + } + } if (IElementMementoProvider.class.equals(adapterType)) { if (adaptableObject instanceof IJavaStackFrame) { return (T) fgMPStackFrame; @@ -96,6 +108,7 @@ public T getAdapter(Object adaptableObject, Class adapterType) { */ @Override public Class[] getAdapterList() { - return new Class[] {IElementLabelProvider.class, IElementContentProvider.class, IWatchExpressionFactoryAdapter.class, IElementMementoProvider.class}; + return new Class[] { IElementLabelProvider.class, IElementContentProvider.class, IWatchExpressionFactoryAdapter.class, + IWatchExpressionFactoryAdapter2.class, IElementMementoProvider.class }; } }