diff --git a/org.eclipse.jdt.debug.ui/plugin.properties b/org.eclipse.jdt.debug.ui/plugin.properties index 805de3ad88..294acef914 100644 --- a/org.eclipse.jdt.debug.ui/plugin.properties +++ b/org.eclipse.jdt.debug.ui/plugin.properties @@ -178,6 +178,9 @@ showMonitorThreadInfo.tooltip=Show the Thread & Monitor Information showNullEntriesAction.label=Show &Null Array Entries showNullEntriesAction.tooltip=Show Null Array Entries +collapseStackFrames.label=Collapse Stack Frames +collapseStackFrames.tooltip=Hide less relevant stack frames + stepIntoSelectionHyperlinkDetector.label=Step Into Selection stepIntoSelectionHyperlinkDetector.description=Performs the step into selection command on demand via a hyperlink diff --git a/org.eclipse.jdt.debug.ui/plugin.xml b/org.eclipse.jdt.debug.ui/plugin.xml index c68914fc4c..115b1a6f35 100644 --- a/org.eclipse.jdt.debug.ui/plugin.xml +++ b/org.eclipse.jdt.debug.ui/plugin.xml @@ -2242,6 +2242,14 @@ style="toggle" menubarPath="org.eclipse.jdt.debug.ui.LaunchView.javaSubmenu/javaPart" id="org.eclipse.jdt.debug.ui.launchViewActions.ShowMonitorThreadInfo"/> + null * if none is defined. @@ -959,21 +994,11 @@ protected Image getExpressionImage(Object expression) { } /** - * Returns the adornment flags for the given element. - * These flags are used to render appropriate overlay - * icons for the element. + * Returns the adornment flags for the given element. These flags are used to render appropriate overlay icons for the element. It only supports + * {@link IJavaThread} and {@link IJavaDebugTarget}, for other types it always returns 0. */ private int computeJDIAdornmentFlags(Object element) { try { - if (element instanceof IJavaStackFrame) { - IJavaStackFrame javaStackFrame = ((IJavaStackFrame)element); - if (javaStackFrame.isOutOfSynch()) { - return JDIImageDescriptor.IS_OUT_OF_SYNCH; - } - if (!javaStackFrame.isObsolete() && javaStackFrame.isSynchronized()) { - return JDIImageDescriptor.SYNCHRONIZED; - } - } if (element instanceof IJavaThread) { int flag= 0; IJavaThread javaThread = ((IJavaThread)element); @@ -2049,6 +2074,16 @@ protected JavaElementLabelProvider getJavaLabelProvider() { return fJavaLabelProvider; } + /** + * @return a {@link StackFramePresentationProvider} which responsible to classify stack frames into categories and could provide category specific + * visual representations. + */ + private StackFramePresentationProvider getStackFrameProvider() { + if (fStackFrameProvider == null) { + fStackFrameProvider = new StackFramePresentationProvider(); + } + return fStackFrameProvider; + } /** * Returns whether the given field variable has the same name as any variables */ diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/StackFramePresentationProvider.java b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/StackFramePresentationProvider.java new file mode 100644 index 0000000000..d5a9e30988 --- /dev/null +++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/StackFramePresentationProvider.java @@ -0,0 +1,209 @@ +/******************************************************************************* + * Copyright (c) 2021, 2025 Zsombor Gegesy 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: + * Zsombor Gegesy - initial API and implementation + *******************************************************************************/ +package org.eclipse.jdt.internal.debug.ui; + +import org.eclipse.core.resources.IFile; +import org.eclipse.debug.core.DebugException; +import org.eclipse.jdt.core.IClassFile; +import org.eclipse.jdt.core.JavaCore; +import org.eclipse.jdt.debug.core.IJavaStackFrame; +import org.eclipse.jdt.debug.core.IJavaStackFrame.Category; +import org.eclipse.jdt.internal.ui.JavaPluginImages; +import org.eclipse.jdt.internal.ui.filtertable.FilterManager; +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.jface.util.IPropertyChangeListener; +import org.eclipse.jface.util.PropertyChangeEvent; + +/** + * Provides foreground and background colors for stack frames in a debug view. After usage, it needs to be closed, so it could unregister itself from + * preference storage. + * + */ +public final class StackFramePresentationProvider implements IPropertyChangeListener { + + public static final FilterManager PLATFORM_STACK_FRAMES = new FilterManager(IJDIPreferencesConstants.PREF_ACTIVE_PLATFORM_FRAME_FILTER_LIST, IJDIPreferencesConstants.PREF_INACTIVE_PLATFORM_FRAME_FILTER_LIST); + public static final FilterManager CUSTOM_STACK_FRAMES = new FilterManager(IJDIPreferencesConstants.PREF_ACTIVE_CUSTOM_FRAME_FILTER_LIST, IJDIPreferencesConstants.PREF_INACTIVE_CUSTOM_FRAME_FILTER_LIST); + + /** + * Class to decide if a particular class name is part of a list of classes and list of packages. + */ + record Filters(String[] filters) { + boolean match(String fqcName) { + for (String filter : filters) { + if (filter.endsWith("*")) { //$NON-NLS-1$ + if (fqcName.startsWith(filter.substring(0, filter.length() - 1))) { + return true; + } + } else { + if (filter.equals(fqcName)) { + return true; + } + } + } + return false; + } + } + + private Filters platform; + private Filters custom; + private final IPreferenceStore store; + private boolean collapseStackFrames; + + public StackFramePresentationProvider(IPreferenceStore store) { + this.store = store; + platform = createActivePlatformFilters(store); + custom = createActiveCustomFilters(store); + store.addPropertyChangeListener(this); + collapseStackFrames = store.getBoolean(IJDIPreferencesConstants.PREF_COLLAPSE_STACK_FRAMES); + } + + /** + * Create a {@link Filters} object to decide if a class is part of the 'platform'. The platform definition is stored in the + * {@link IPreferenceStore}. By default, this is the classes provided by the JVM. + * + */ + private Filters createActivePlatformFilters(IPreferenceStore store) { + return new Filters(PLATFORM_STACK_FRAMES.getActiveList(store)); + } + + /** + * Create a {@link Filters} object to decide if a class is considered part of a custom, very important layer, which needs to be highlighted. This + * definition is stored in the {@link IPreferenceStore}. By default, this is an empty list. + */ + private Filters createActiveCustomFilters(IPreferenceStore store) { + return new Filters(CUSTOM_STACK_FRAMES.getActiveList(store)); + } + + public StackFramePresentationProvider() { + this(JDIDebugUIPlugin.getDefault().getPreferenceStore()); + } + + /** + * @return the category specific image for the stack frame, or null, if there is no one defined. + */ + public ImageDescriptor getStackFrameImage(IJavaStackFrame frame) { + var category = getCategory(frame); + if (category != null) { + switch (category) { + case LIBRARY: + case SYNTHETIC: + case PLATFORM: + return JavaPluginImages.DESC_OBJS_JAR; + default: + break; + } + } + return null; + } + + /** + * @return the {@link IJavaStackFrame.Category} which matches the rules - and store the category inside the frame, if the category is already + * calculated for the frame this just retrieves that. + */ + public IJavaStackFrame.Category getCategory(IJavaStackFrame frame) { + if (frame.getCategory() != null) { + return frame.getCategory(); + } + IJavaStackFrame.Category result = Category.UNKNOWN; + try { + result = categorize(frame); + } catch (DebugException e) { + JDIDebugUIPlugin.log(e); + } + frame.setCategory(result); + return result; + } + + private boolean isEnabled(@SuppressWarnings("unused") Category category) { + // Category will be used in subsequent changes. + return true; + } + + /** + * Categorize the given {@link IJavaStackFrame} into a {@link Category} based on the rules and filters, and where those classes are in the + * project. For example if in a source folder, in a library or in a test source folder, etc. + */ + public IJavaStackFrame.Category categorize(IJavaStackFrame frame) throws DebugException { + var refTypeName = frame.getReferenceType().getName(); + if (isEnabled(Category.CUSTOM_FILTERED) && custom.match(refTypeName)) { + return Category.CUSTOM_FILTERED; + } + if (isEnabled(Category.SYNTHETIC) && frame.isSynthetic()) { + return Category.SYNTHETIC; + } + if (isEnabled(Category.PLATFORM) && platform.match(refTypeName)) { + return Category.PLATFORM; + } + + return categorizeSourceElement(frame); + } + + /** + * Do the categorization with the help of a {@link org.eclipse.debug.core.model.ISourceLocator} coming from the associated + * {@link org.eclipse.debug.core.ILaunch}. This is how we can find, if the class file is in a jar or comes from a source folder. + */ + private Category categorizeSourceElement(IJavaStackFrame frame) { + var sourceLocator = frame.getLaunch().getSourceLocator(); + if (sourceLocator == null) { + return Category.UNKNOWN; + } + var source = sourceLocator.getSourceElement(frame); + if (source == null) { + return Category.UNKNOWN; + } + if (source instanceof IFile file) { + if (isEnabled(Category.TEST)) { + var jproj = JavaCore.create(file.getProject()); + var cp = jproj.findContainingClasspathEntry(file); + if (cp != null && cp.isTest()) { + return Category.TEST; + } + } + if (isEnabled(Category.PRODUCTION)) { + return Category.PRODUCTION; + } + } else if (source instanceof IClassFile && isEnabled(Category.LIBRARY)) { + return Category.LIBRARY; + } + return Category.UNKNOWN; + } + + /** + * Unsubscribes to not receive notifications from the {@link IPreferenceStore}. + */ + public void close() { + store.removePropertyChangeListener(this); + } + + @Override + public void propertyChange(PropertyChangeEvent event) { + String prop = event.getProperty(); + if (IJDIPreferencesConstants.PREF_ACTIVE_PLATFORM_FRAME_FILTER_LIST.equals(prop)) { + platform = createActivePlatformFilters(store); + } else if (IJDIPreferencesConstants.PREF_ACTIVE_CUSTOM_FRAME_FILTER_LIST.equals(prop)) { + custom = createActiveCustomFilters(store); + } else if (IJDIPreferencesConstants.PREF_COLLAPSE_STACK_FRAMES.equals(prop)) { + collapseStackFrames = (Boolean) event.getNewValue(); + } + } + + /** + * @return if stack frames should be collapsed. + */ + public boolean isCollapseStackFrames() { + return collapseStackFrames; + } + +} diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/actions/CollapseStackFramesAction.java b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/actions/CollapseStackFramesAction.java new file mode 100644 index 0000000000..5e4aa053b2 --- /dev/null +++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/actions/CollapseStackFramesAction.java @@ -0,0 +1,34 @@ +/******************************************************************************* + * Copyright (c) 2022, 2025 Zsombor Gegesy 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: + * Zsombor Gegesy - initial API and implementation + *******************************************************************************/ +package org.eclipse.jdt.internal.debug.ui.actions; + +import org.eclipse.jdt.internal.debug.ui.IJDIPreferencesConstants; + +/** + * Enable/disable collapsing of the non-relevant stack frames in debug views. + * + */ +public class CollapseStackFramesAction extends ToggleBooleanPreferenceAction { + + @Override + protected String getPreferenceKey() { + return IJDIPreferencesConstants.PREF_COLLAPSE_STACK_FRAMES; + } + + @Override + protected String getCompositeKey() { + return getPreferenceKey(); + } + +} diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/monitors/JavaThreadContentProvider.java b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/monitors/JavaThreadContentProvider.java index 69524111c5..9dd3ebde24 100644 --- a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/monitors/JavaThreadContentProvider.java +++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/monitors/JavaThreadContentProvider.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2006, 2012 IBM Corporation and others. + * Copyright (c) 2006, 2024 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -13,20 +13,27 @@ *******************************************************************************/ package org.eclipse.jdt.internal.debug.ui.monitors; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.jobs.ISchedulingRule; import org.eclipse.debug.core.DebugException; import org.eclipse.debug.core.model.IDebugElement; -import org.eclipse.debug.core.model.IStackFrame; import org.eclipse.debug.internal.ui.viewers.model.provisional.IChildrenCountUpdate; import org.eclipse.debug.internal.ui.viewers.model.provisional.IChildrenUpdate; import org.eclipse.debug.internal.ui.viewers.model.provisional.IHasChildrenUpdate; import org.eclipse.debug.internal.ui.viewers.model.provisional.IPresentationContext; import org.eclipse.debug.internal.ui.viewers.model.provisional.IViewerUpdate; import org.eclipse.jdt.debug.core.IJavaDebugTarget; +import org.eclipse.jdt.debug.core.IJavaStackFrame.Category; import org.eclipse.jdt.debug.core.IJavaThread; import org.eclipse.jdt.debug.ui.JavaDebugUtils; +import org.eclipse.jdt.internal.debug.core.model.GroupedStackFrame; +import org.eclipse.jdt.internal.debug.core.model.JDIStackFrame; import org.eclipse.jdt.internal.debug.core.model.JDIThread; +import org.eclipse.jdt.internal.debug.ui.StackFramePresentationProvider; /** * Java thread presentation adapter. @@ -35,16 +42,21 @@ */ public class JavaThreadContentProvider extends JavaElementContentProvider { + private StackFramePresentationProvider stackFrameProvider; + /* (non-Javadoc) * @see org.eclipse.debug.internal.ui.viewers.model.provisional.elements.ElementContentProvider#getChildCount(java.lang.Object, org.eclipse.debug.internal.ui.viewers.provisional.IPresentationContext) */ @Override protected int getChildCount(Object element, IPresentationContext context, IViewerUpdate monitor) throws CoreException { + if (element instanceof GroupedStackFrame groupedFrame) { + return groupedFrame.getFrameCount(); + } IJavaThread thread = (IJavaThread)element; if (!thread.isSuspended()) { return 0; } - int childCount = thread.getFrameCount(); + int childCount = getFrameCount(thread); if (isDisplayMonitors()) { if (((IJavaDebugTarget) thread.getDebugTarget()).supportsMonitorInformation()) { childCount+= thread.getOwnedMonitors().length; @@ -59,11 +71,25 @@ protected int getChildCount(Object element, IPresentationContext context, IViewe return childCount; } + /** + * return the number of child objects for the given {@link IJavaThread}, either the number of frames, or if frame grouping is enabled, the + * {@link GroupedStackFrame}s are properly accounted. + */ + private int getFrameCount(IJavaThread thread) throws DebugException { + if (getStackFrameProvider().isCollapseStackFrames()) { + return getStackFrames(thread).size(); + } + return thread.getFrameCount(); + } + /* (non-Javadoc) * @see org.eclipse.debug.internal.ui.model.elements.ElementContentProvider#getChildren(java.lang.Object, int, int, org.eclipse.debug.internal.ui.viewers.model.provisional.IPresentationContext, org.eclipse.core.runtime.IProgressMonitor) */ @Override protected Object[] getChildren(Object parent, int index, int length, IPresentationContext context, IViewerUpdate monitor) throws CoreException { + if (parent instanceof GroupedStackFrame groupedFrame) { + return groupedFrame.getFramesAsArray(index, length); + } IJavaThread thread = (IJavaThread)parent; if (!thread.isSuspended()) { return EMPTY; @@ -81,40 +107,71 @@ protected Object[] getChildren(IJavaThread thread) { } } } - IStackFrame[] frames = thread.getStackFrames(); + var frames = getStackFrames(thread); if (!isDisplayMonitors()) { - return frames; + return frames.toArray(); } - Object[] children; - int length = frames.length; if (((IJavaDebugTarget) thread.getDebugTarget()).supportsMonitorInformation()) { IDebugElement[] ownedMonitors = JavaDebugUtils.getOwnedMonitors(thread); IDebugElement contendedMonitor = JavaDebugUtils.getContendedMonitor(thread); - length += ownedMonitors.length; if (contendedMonitor != null) { - length++; + // Insert the contended monitor after the owned monitors + frames.add(0, contendedMonitor); } - children = new Object[length]; if (ownedMonitors.length > 0) { - System.arraycopy(ownedMonitors, 0, children, 0, ownedMonitors.length); - } - if (contendedMonitor != null) { - // Insert the contended monitor after the owned monitors - children[ownedMonitors.length] = contendedMonitor; + frames.addAll(0, Arrays.asList(ownedMonitors)); } } else { - children = new Object[length + 1]; - children[0] = new NoMonitorInformationElement(thread.getDebugTarget()); + frames.add(0, new NoMonitorInformationElement(thread.getDebugTarget())); } - int offset = children.length - frames.length; - System.arraycopy(frames, 0, children, offset, frames.length); - return children; + return frames.toArray(); } catch (DebugException e) { return EMPTY; } } + /** + * Return the stack frames for the given {@link IJavaThread}. If stack frames grouping is not switched on, it just returns all the stack frames + * reported by the JVM. When stack frame grouping enabled, all frames that are not considered as {@link Category#PRODUCTION}, + * {@link Category#TEST} and {@link Category#CUSTOM_FILTERED} grouped into {@link GroupedStackFrame}s, those are folded by default, but can be + * expanded on demand by the user. + */ + private List getStackFrames(IJavaThread thread) throws DebugException { + var frames = thread.getStackFrames(); + var stackFrameProvider = getStackFrameProvider(); + var result = new ArrayList<>(frames.length); + if (!stackFrameProvider.isCollapseStackFrames()) { + result.addAll(Arrays.asList(frames)); + return result; + } + GroupedStackFrame lastGroupping = null; + boolean first = true; + for (var frame : frames) { + if (first) { + result.add(frame); + first = false; + } else { + if (frame instanceof JDIStackFrame javaFrame) { + var category = stackFrameProvider.getCategory(javaFrame); + if (category == null || category == Category.TEST || category == Category.PRODUCTION || category == Category.CUSTOM_FILTERED) { + result.add(javaFrame); + lastGroupping = null; + } else { + if (lastGroupping == null) { + lastGroupping = new GroupedStackFrame(javaFrame.getJavaDebugTarget()); + result.add(lastGroupping); + } + lastGroupping.add(javaFrame); + } + } else { + result.add(frame); + } + } + } + return result; + } + /* (non-Javadoc) * @see org.eclipse.debug.internal.ui.model.elements.ElementContentProvider#hasChildren(java.lang.Object, org.eclipse.debug.internal.ui.viewers.model.provisional.IPresentationContext, org.eclipse.core.runtime.IProgressMonitor) */ @@ -128,6 +185,9 @@ protected boolean hasChildren(Object element, IPresentationContext context, IVie } } } + if (element instanceof GroupedStackFrame groupedFrame) { + return groupedFrame.getFrameCount() > 0; + } return ((IJavaThread)element).hasStackFrames() || (isDisplayMonitors() && ((IJavaThread)element).hasOwnedMonitors()); } @@ -174,5 +234,14 @@ private ISchedulingRule getThreadRule(IViewerUpdate[] updates) { return null; } + /** + * @return a {@link StackFramePresentationProvider} to provide classification of the stack frames. + */ + private synchronized StackFramePresentationProvider getStackFrameProvider() { + if (stackFrameProvider == null) { + stackFrameProvider = new StackFramePresentationProvider(); + } + return stackFrameProvider; + } } diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/monitors/MonitorsAdapterFactory.java b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/monitors/MonitorsAdapterFactory.java index 81046e2de0..a98a9c7733 100644 --- a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/monitors/MonitorsAdapterFactory.java +++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/monitors/MonitorsAdapterFactory.java @@ -17,6 +17,7 @@ import org.eclipse.debug.internal.ui.viewers.model.provisional.IElementContentProvider; import org.eclipse.jdt.debug.core.IJavaStackFrame; import org.eclipse.jdt.debug.core.IJavaThread; +import org.eclipse.jdt.internal.debug.core.model.GroupedStackFrame; import org.eclipse.jdt.internal.debug.ui.variables.JavaStackFrameContentProvider; /** @@ -39,11 +40,11 @@ public class MonitorsAdapterFactory implements IAdapterFactory { @SuppressWarnings("unchecked") public T getAdapter(Object adaptableObject, Class adapterType) { - if (IElementContentProvider.class.equals(adapterType)) { - if (adaptableObject instanceof IJavaThread) { - return (T) getThreadPresentation(); - } - if (adaptableObject instanceof IJavaStackFrame) { + if (IElementContentProvider.class.equals(adapterType)) { + if (adaptableObject instanceof IJavaThread || adaptableObject instanceof GroupedStackFrame) { + return (T) getThreadPresentation(); + } + if (adaptableObject instanceof IJavaStackFrame) { return (T) fgCPFrame; } if (adaptableObject instanceof JavaOwnedMonitor) { diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/sourcelookup/JavaDebugShowInAdapterFactory.java b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/sourcelookup/JavaDebugShowInAdapterFactory.java index df57f68c4f..4c7f3855e3 100644 --- a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/sourcelookup/JavaDebugShowInAdapterFactory.java +++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/sourcelookup/JavaDebugShowInAdapterFactory.java @@ -14,7 +14,10 @@ package org.eclipse.jdt.internal.debug.ui.sourcelookup; import org.eclipse.core.runtime.IAdapterFactory; +import org.eclipse.debug.internal.ui.sourcelookup.SourceLookupFacility; +import org.eclipse.debug.ui.sourcelookup.ISourceDisplay; import org.eclipse.jdt.debug.core.IJavaStackFrame; +import org.eclipse.jdt.internal.debug.core.model.GroupedStackFrame; import org.eclipse.ui.part.IShowInSource; import org.eclipse.ui.part.IShowInTargetList; @@ -40,6 +43,15 @@ public T getAdapter(Object adaptableObject, Class adapterType) { return (T) new StackFrameShowInTargetListAdapter(); } } + if (adapterType == ISourceDisplay.class) { + if (adaptableObject instanceof GroupedStackFrame groupedFrames) { + return (T) (ISourceDisplay) (element, page, forceSourceLookup) -> { + var frame = groupedFrames.getTopMostFrame(); + SourceLookupFacility.getDefault().displaySource(frame, page, forceSourceLookup); + }; + } + + } return null; } @@ -48,7 +60,7 @@ public T getAdapter(Object adaptableObject, Class adapterType) { */ @Override public Class[] getAdapterList() { - return new Class[]{IShowInSource.class, IShowInTargetList.class}; + return new Class[] { IShowInSource.class, IShowInTargetList.class, ISourceDisplay.class }; } } diff --git a/org.eclipse.jdt.debug/META-INF/MANIFEST.MF b/org.eclipse.jdt.debug/META-INF/MANIFEST.MF index 84d4ebd713..c7e05af5e1 100644 --- a/org.eclipse.jdt.debug/META-INF/MANIFEST.MF +++ b/org.eclipse.jdt.debug/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %pluginName Bundle-SymbolicName: org.eclipse.jdt.debug; singleton:=true -Bundle-Version: 3.21.700.qualifier +Bundle-Version: 3.22.0.qualifier Bundle-ClassPath: jdimodel.jar Bundle-Activator: org.eclipse.jdt.internal.debug.core.JDIDebugPlugin Bundle-Vendor: %providerName diff --git a/org.eclipse.jdt.debug/model/org/eclipse/jdt/debug/core/IJavaStackFrame.java b/org.eclipse.jdt.debug/model/org/eclipse/jdt/debug/core/IJavaStackFrame.java index a77f2e3ebf..476608bb8e 100644 --- a/org.eclipse.jdt.debug/model/org/eclipse/jdt/debug/core/IJavaStackFrame.java +++ b/org.eclipse.jdt.debug/model/org/eclipse/jdt/debug/core/IJavaStackFrame.java @@ -35,6 +35,43 @@ public interface IJavaStackFrame extends IStackFrame, IJavaModifiers, IFilteredStep, IDropToFrame { + /** + * Categorization of the stack frame. + * + * @since 3.22 + * + */ + enum Category { + /** + * The user specified a filter, can be used for highlighting specific, very important code layers. + */ + CUSTOM_FILTERED, + /** + * The stack frame represents a synthetic function call, which is not based on actual Java source code. + */ + SYNTHETIC, + /** + * Methods in classes that considered as platform, like code in 'java.*' packages. + */ + PLATFORM, + /** + * Classes found in a test source folder in the project. + */ + TEST, + /** + * Classes found in a non-test source folder in the project. + */ + PRODUCTION, + /** + * Classes coming from a library, not from the actual project. + */ + LIBRARY, + /** + * Classes with unknown origin. + */ + UNKNOWN + } + /** * Status code indicating a stack frame is invalid. A stack frame becomes * invalid when the thread containing the stack frame resumes. A stack frame @@ -533,4 +570,18 @@ public IJavaVariable findVariable(String variableName) * @since 3.3 */ public void forceReturn(IJavaValue value) throws DebugException; + + /** + * @since 3.22 + * @param category + * the new category of the stack frame. + */ + public void setCategory(Category category); + + /** + * @since 3.22 + * @return the category of the stack frame, null if it's not yet categorized. + */ + public Category getCategory(); + } diff --git a/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/model/GroupedStackFrame.java b/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/model/GroupedStackFrame.java new file mode 100644 index 0000000000..0fe61d37c3 --- /dev/null +++ b/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/model/GroupedStackFrame.java @@ -0,0 +1,66 @@ +/******************************************************************************* + * Copyright (c) 2022, 2025 Zsombor Gegesy 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: + * Zsombor Gegesy - initial API and implementation + *******************************************************************************/ +package org.eclipse.jdt.internal.debug.core.model; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.jdt.debug.core.IJavaStackFrame; + +/** + * Class to collect multiple stack frame. + * + */ +public class GroupedStackFrame extends JDIDebugElement { + private final List stackFrames = new ArrayList<>(); + + public GroupedStackFrame(JDIDebugTarget target) { + super(target); + } + + public void add(IJavaStackFrame frame) { + stackFrames.add(frame); + } + + public int getFrameCount() { + return stackFrames.size(); + } + + /** + * Return an array of child stack frames from the internally stored frames. + */ + public Object[] getFramesAsArray(int index, int length) { + var subList = stackFrames.subList(index, Math.min(index + length, stackFrames.size())); + return subList.isEmpty() ? null : subList.toArray(); + } + + /** + * @return the top most frame from the stack frames grouped inside. + */ + public IJavaStackFrame getTopMostFrame() { + return !stackFrames.isEmpty() ? stackFrames.get(0) : null; + } + + /** + * Delegates actions, and everything to the top most stack frame. + */ + @Override + public T getAdapter(Class adapterType) { + var adapter = super.getAdapter(adapterType); + if (adapter == null && !stackFrames.isEmpty()) { + adapter = stackFrames.get(0).getAdapter(adapterType); + } + return adapter; + } +} diff --git a/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/model/JDIStackFrame.java b/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/model/JDIStackFrame.java index d92e27b34a..379270e71c 100644 --- a/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/model/JDIStackFrame.java +++ b/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/model/JDIStackFrame.java @@ -139,6 +139,8 @@ public class JDIStackFrame extends JDIDebugElement implements IJavaStackFrame { */ private boolean fIsTop; + private Category fCategory; + @SuppressWarnings("restriction") private static final String SYNTHETIC_OUTER_LOCAL_PREFIX = new String(org.eclipse.jdt.internal.compiler.lookup.TypeConstants.SYNTHETIC_OUTER_LOCAL_PREFIX); @@ -1730,4 +1732,14 @@ public void forceReturn(IJavaValue value) throws DebugException { public void setIsTop(boolean isTop) { this.fIsTop = isTop; } + + @Override + public Category getCategory() { + return fCategory; + } + + @Override + public void setCategory(Category fCategory) { + this.fCategory = fCategory; + } } diff --git a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/JavaSourceLookupDirector.java b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/JavaSourceLookupDirector.java index b38d2968c0..4a5aa4983a 100644 --- a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/JavaSourceLookupDirector.java +++ b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/JavaSourceLookupDirector.java @@ -32,7 +32,7 @@ */ public class JavaSourceLookupDirector extends AbstractSourceLookupDirector { - private static Set fFilteredTypes; + private static final Set fFilteredTypes; static { fFilteredTypes = new HashSet<>();