blob: bbf93cdba189ed1e04945733d1ba180e7a7e2391 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2015 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
* Patrick Chuong (Texas Instruments) - Improve usability of the breakpoint view (Bug 238956)
*****************************************************************/
package org.eclipse.debug.internal.ui.views.breakpoints;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.Platform;
import org.eclipse.debug.core.model.IBreakpoint;
import org.eclipse.debug.internal.ui.breakpoints.provisional.IBreakpointContainer;
import org.eclipse.debug.internal.ui.breakpoints.provisional.IBreakpointOrganizer;
import org.eclipse.debug.internal.ui.breakpoints.provisional.OtherBreakpointCategory;
import org.eclipse.debug.internal.ui.model.elements.ElementContentProvider;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IModelDelta;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IPresentationContext;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IViewerUpdate;
import org.eclipse.debug.internal.ui.viewers.model.provisional.ModelDelta;
import org.eclipse.debug.ui.IDebugUIConstants;
/**
* This class contains the list of container or a list of breakpoint, elements are sorted according to rules
* in the comparator.
*/
public class BreakpointContainer extends ElementContentProvider implements IAdaptable, IBreakpointContainer {
/**
* Child breakpoints - inserting new element into this collection should use the insertBreakpoint method
*/
final private List<IBreakpoint> fBreakpoints = new ArrayList<>();
/**
* Child containers - inserting new element into this container should use the insertChildContainer method
*/
final private List<BreakpointContainer> fChildContainers = new ArrayList<>();
/**
* The category for this container
*/
private IAdaptable fCategory;
/**
* The breakpoint organizer for this container
*/
private IBreakpointOrganizer fOrganizer;
/**
* The nested breakpoint organizer
*/
private IBreakpointOrganizer[] fNesting;
/**
* A flag to indicate this is the default container
*/
private boolean fDefaultContainer;
/**
* Parent container
*/
private BreakpointContainer fParent;
/**
* The comparator, will be use to compare the order for inserting new element into this container
*/
private ElementComparator fComparator;
/**
* Constructor, intended to be call when creating the root container.
*
* @param organizers the breakpoint organizer for this container
* @param comparator the element comparator, can be <code>null</code>. If <code>null</code> than new element
* will be added to the end of the list.
*/
public BreakpointContainer(IBreakpointOrganizer[] organizers, ElementComparator comparator) {
fNesting = organizers;
fComparator = comparator;
}
/**
* Constructor, intended to be call within this class only.
*
* @param parent the parent breakpoint container
* @param category the category for this container
* @param organizer the organizer for this container
* @param comparator the element comparator
* @param nesting the nested breakpoint organizer
*/
private BreakpointContainer(BreakpointContainer parent, IAdaptable category, IBreakpointOrganizer organizer,
ElementComparator comparator, IBreakpointOrganizer[] nesting) {
this(category, organizer, nesting);
fParent = parent;
fComparator = comparator;
}
/**
* Constructor, intended to be call when reorganizing the content.
*
* @param category the breakpoint category
* @param organizer the breakpoint organizer
* @param nesting the nested breakpoint organizer
*/
BreakpointContainer(IAdaptable category, IBreakpointOrganizer organizer, IBreakpointOrganizer[] nesting) {
fCategory = category;
fOrganizer = organizer;
fNesting = nesting;
}
/**
* Initialize the default containers.
*
* @param parentDelta the parent delta, addition child delta will be added to the parent
*/
public void initDefaultContainers(ModelDelta parentDelta) {
// seed with all nested categories
if (fNesting != null && fNesting.length > 0) {
IAdaptable[] emptyCategories = fNesting[0].getCategories();
if (emptyCategories != null) {
for (int i = 0; i < emptyCategories.length; i++) {
IAdaptable empty = emptyCategories[i];
BreakpointContainer container = findExistingContainer(fChildContainers, empty);
if (container == null) {
IBreakpointOrganizer[] siblings = new IBreakpointOrganizer[fNesting.length - 1];
System.arraycopy(fNesting, 1, siblings, 0, siblings.length);
container = new BreakpointContainer(this, empty, fNesting[0], fComparator, siblings);
insertChildContainer(container);
container.fDefaultContainer = true;
int size = container.getChildren().length;
parentDelta.addNode(container, fChildContainers.indexOf(container), IModelDelta.INSTALL|IModelDelta.ADDED|IModelDelta.EXPAND, size);
}
}
}
}
}
/**
* Insert the breakpoint to this container.
*
* @param breakpoint the new breakpoint
* @return the index of the breakpoint in the cache, -1 if the breakpoint already exist
*/
private int insertBreakpoint(IBreakpoint breakpoint) {
if (fBreakpoints.contains(breakpoint) || breakpoint == null) {
return -1;
}
int index = fBreakpoints.size();
for (; fComparator != null && index > 0; index--) {
if (fComparator.compare(fBreakpoints.get(index-1), breakpoint) < 0) {
break;
}
}
if (index < 0) {
index = 0;
}
fBreakpoints.add(index, breakpoint);
return index;
}
/**
* Insert the child container this container.
*
* @param container the child container
* @return the index of the container in the cache, -1 if the child container already exist
*/
private int insertChildContainer(BreakpointContainer container) {
int index = fChildContainers.size();
for (; fComparator != null && index > 0; index--) {
if (fComparator.compare(fChildContainers.get(index-1), container) < 0) {
break;
}
}
if (index < 0) {
index = 0;
}
fChildContainers.add(index, container);
return index;
}
/**
* Returns the element comparator.
*
* @return the element comparator
*/
public ElementComparator getElementComparator() {
return fComparator;
}
/**
* Returns the parent container, can be <code>null</code>.
*
* @return the parent container
*/
public BreakpointContainer getParent() {
return fParent;
}
/**
* Determine whether there is any nested container.
*
* @return true if has nested container
*/
private boolean hasNesting() {
return fNesting != null && fNesting.length > 0;
}
/**
* Get the categories for the breakpoint with the given organizer.
*
* @param breakpoint the breakpoint
* @param organizer the organizer
* @return the categories
*/
private static IAdaptable[] getCategories(IBreakpoint breakpoint, IBreakpointOrganizer organizer) {
IAdaptable[] categories = organizer.getCategories(breakpoint);
if (categories == null || categories.length == 0) {
categories = OtherBreakpointCategory.getCategories(organizer);
}
return categories;
}
/**
* Find existing breakpoint container in the container array the given category.
*
* @param containers the container array
* @param category the category
* @return the breakpoint container, can be <code>null</code>.
*/
private static BreakpointContainer findExistingContainer(List<BreakpointContainer> containers, IAdaptable category) {
for (BreakpointContainer c : containers) {
if (category.equals(c.getCategory())) {
return c;
}
}
return null;
}
// TODO [pchuong]: can be remove if BreakpointsContentProvider no longer uses this class
void addBreakpoint(IBreakpoint breakpoint) {
addBreakpoint(breakpoint, new ModelDelta(null, IModelDelta.NO_CHANGE));
}
/**
* Add a breakpoint to the container, additional delta will be added to the root delta.
*
* @param breakpoint the breakpoint to added
* @param rootDelta the root delta of this container
* @see #removeBreakpoint
*/
public void addBreakpoint(IBreakpoint breakpoint, ModelDelta rootDelta) {
final int bpIndex = insertBreakpoint(breakpoint);
if (bpIndex < 0) {
return;
}
if (hasNesting()) {
IBreakpointOrganizer organizer = fNesting[0];
// get the breakpoint categories from the organizer
IAdaptable[] categories = getCategories(breakpoint, organizer);
for (int i = 0; i < categories.length; ++i) {
ModelDelta childDelta = null;
IAdaptable category = categories[i];
BreakpointContainer container = findExistingContainer(fChildContainers, category);
// create a new container if it doesn't exist
if (container == null) {
IBreakpointOrganizer[] nesting = null;
if (fNesting.length > 1) {
nesting = new IBreakpointOrganizer[fNesting.length - 1];
System.arraycopy(fNesting, 1, nesting, 0, nesting.length);
}
container = new BreakpointContainer(this, category, organizer, fComparator, nesting);
insertChildContainer(container);
childDelta = rootDelta.addNode(container, fChildContainers.indexOf(container), IModelDelta.INSERTED|IModelDelta.INSTALL, -1);
} else {
childDelta = rootDelta.addNode(container, fChildContainers.indexOf(container), IModelDelta.STATE, -1);
}
container.addBreakpoint(breakpoint, childDelta);
childDelta.setChildCount(container.getChildren().length);
}
} else {
// TODO [pchuong]: There seems to be some kind of problem when the INSERTED flag is used,
// there is a additional checkbox added to the end of the tree.
// Also the tree seems to have a strange visual effect when using the INSERTED
// flag for the child node instead of ADDED flag. Note: all breakpoint delta
// is using the ADDED flag in this class.
rootDelta.addNode(breakpoint, bpIndex, IModelDelta.ADDED|IModelDelta.INSTALL, 0);
// rootDelta.addNode(breakpoint, bpIndex, IModelDelta.INSERTED|IModelDelta.INSTALL, 0);
rootDelta.setFlags(rootDelta.getFlags() | IModelDelta.EXPAND);
}
}
/**
* Remove a breakpoint from the container, additional delta will be added to the root delta.
*
* @param breakpoint the breakpoint to remove
* @param rootDelta the root delta of this container
* @return if the breakpoint was successfully removed
* @see #addBreakpoint
*/
public boolean removeBreakpoint(IBreakpoint breakpoint, ModelDelta rootDelta) {
boolean removed = fBreakpoints.remove(breakpoint);
if (removed) {
boolean addRemoveBpDelta = getContainers().length == 0;
Iterator<BreakpointContainer> it = fChildContainers.iterator();
while (it.hasNext()) {
BreakpointContainer container = it.next();
// if the breakpoint contains in the container and it is the only breakpoint,
// than remove the container from the collection
if (container.contains(breakpoint)) {
ModelDelta childDelta = null;
if ((!container.isDefaultContainer()) && (container.getBreakpoints().length <= 1)) {
it.remove();
childDelta = rootDelta.addNode(container, IModelDelta.REMOVED|IModelDelta.UNINSTALL);
} else {
childDelta = rootDelta.addNode(container, IModelDelta.STATE);
}
// remove the breakpoint from the nested containers
container.removeBreakpoint(breakpoint, childDelta);
}
}
if (addRemoveBpDelta) {
rootDelta.addNode(breakpoint, IModelDelta.REMOVED|IModelDelta.UNINSTALL);
}
}
return removed;
}
/**
* A helper method to copy the organizers between two containers.
*
* @param destContainer the destination container
* @param sourceContainer the source container
*/
public static void copyOrganizers(BreakpointContainer destContainer, BreakpointContainer sourceContainer) {
destContainer.fNesting = sourceContainer.fNesting;
destContainer.fOrganizer = sourceContainer.fOrganizer;
destContainer.fCategory = sourceContainer.fCategory;
}
/**
* A helper method to update the breakpoint cache of the container and it's ancestors.
*
* @param container the breakpoint container
* @param breakpoints the breakpoint to update
* @param add true if breakpoint should be added to the cache, otherwise remove the breakpoint from the cache
*/
private static void updateSelfAndAncestorsBreakpointCache(BreakpointContainer container, List<IBreakpoint> breakpoints, boolean add) {
if (container != null) {
container.fBreakpoints.removeAll(breakpoints);
if (add) {
container.fBreakpoints.addAll(breakpoints);
}
updateSelfAndAncestorsBreakpointCache(container.getParent(), breakpoints, add);
}
}
/**
* A helper method to add a breakpoint to an existing container.
*
* @param destContainer the destination container
* @param breakpoint the breakpoint to add
* @param destContainerDelta the destination container delta, additional delta will be added to this delta
*/
static public void addBreakpoint(BreakpointContainer destContainer, IBreakpoint breakpoint, ModelDelta destContainerDelta) {
int index = destContainer.insertBreakpoint(breakpoint);
Assert.isTrue(index >= 0);
List<IBreakpoint> breakpoints = destContainer.fBreakpoints;
destContainerDelta.addNode(breakpoint, index/*breakpoints.indexOf(breakpoint)*/, IModelDelta.ADDED|IModelDelta.INSTALL, 0);
destContainerDelta.setFlags(destContainerDelta.getFlags() | IModelDelta.EXPAND);
// add the breakpoints to the parent containers.
updateSelfAndAncestorsBreakpointCache(destContainer.getParent(), breakpoints, true);
}
/**
* A helper method to add a child container to an existing container.
*
* @param destContainer the destination container
* @param sourceContainer the source container
* @param destContainerDelta the delta of the destination container, additional delta will be added to this delta
*/
static public void addChildContainer(BreakpointContainer destContainer, BreakpointContainer sourceContainer, ModelDelta destContainerDelta) {
destContainer.insertChildContainer(sourceContainer);
sourceContainer.fParent = destContainer;
// add the breakpoints to the parent containers.
List<IBreakpoint> breakpoints = Arrays.asList(sourceContainer.getBreakpoints());
updateSelfAndAncestorsBreakpointCache(destContainer, breakpoints, true);
int index = destContainer.fChildContainers.indexOf(sourceContainer);
int size = sourceContainer.getChildren().length;
ModelDelta childDelta = destContainerDelta.addNode(sourceContainer, index, IModelDelta.INSERTED|IModelDelta.INSTALL|IModelDelta.EXPAND, size);
appendContainerDelta(sourceContainer, childDelta);
}
/**
* A helper method to append delta to the breakpoint container. This method is used by addContainer only.
*
* @param container the container to append child delta
* @param containerDelta the delta of the breakpoint container, additional delta will be added to this delta
*/
static private void appendContainerDelta(BreakpointContainer container, ModelDelta containerDelta) {
Object[] children = container.getChildren();
for (int i = 0; i < children.length; ++i) {
boolean isBreakpoint = children[0] instanceof IBreakpoint;
int numChild = isBreakpoint ? 0 : children.length;
int flag = isBreakpoint ? IModelDelta.ADDED|IModelDelta.INSTALL
: IModelDelta.INSERTED|IModelDelta.INSTALL|IModelDelta.EXPAND;
ModelDelta childDelta = containerDelta.addNode(children[i], i, flag, numChild);
if (children[i] instanceof BreakpointContainer) {
BreakpointContainer childContainer = (BreakpointContainer) children[i];
appendContainerDelta(childContainer, childDelta);
}
}
}
/**
* A helper method to remove the breakpoint from the container.
*
* @param container the container to remove the breakpoint
* @param breakpoint the breakpoint to remove
* @param containerDelta the delta of the breakpoint container, additional delta will be added to this delta
*/
static public void removeBreakpoint(BreakpointContainer container, IBreakpoint breakpoint, ModelDelta containerDelta) {
container.removeBreakpoint(breakpoint, containerDelta);
List<IBreakpoint> breakpoints = new ArrayList<>();
breakpoints.add(breakpoint);
updateSelfAndAncestorsBreakpointCache(container.getParent(), breakpoints, false);
}
/**
* Remove all child elements including the given container itself.
*
* @param container the breakpoint container
* @param delta the parent delta
*/
static public void removeAll(BreakpointContainer container, ModelDelta delta) {
BreakpointContainer parent = container.getParent();
if (parent != null) {
parent.fChildContainers.remove(container);
delta = delta.addNode(container, IModelDelta.UNINSTALL|IModelDelta.REMOVED);
}
if (container.fChildContainers.size() == 0) {
List<IBreakpoint> breakpoints = new ArrayList<>();
Iterator<IBreakpoint> iterator = container.fBreakpoints.iterator();
while (iterator.hasNext()) {
IBreakpoint obj = iterator.next();
breakpoints.add(obj);
delta.addNode(obj, IModelDelta.UNINSTALL|IModelDelta.REMOVED);
iterator.remove();
}
// remove the breakpoints from the parent containers.
updateSelfAndAncestorsBreakpointCache(container.getParent(), breakpoints, false);
return;
}
Iterator<BreakpointContainer> iterator = container.fChildContainers.iterator();
while (iterator.hasNext()) {
BreakpointContainer childContainer = iterator.next();
ModelDelta childDelta = delta.addNode(childContainer, IModelDelta.REMOVED|IModelDelta.UNINSTALL);
iterator.remove();
removeAll(childContainer, childDelta);
}
}
/**
* Returns whether this is the default container.
*
* @return true if it is a default container
*/
boolean isDefaultContainer() {
return fDefaultContainer;
}
/**
* Returns the breakpoints in this container
*
* @return the breakpoints in this container
*/
@Override
public IBreakpoint[] getBreakpoints() {
return fBreakpoints.toArray(new IBreakpoint[fBreakpoints.size()]);
}
/**
* Returns this container's category.
*
* @return container category
*/
@Override
public IAdaptable getCategory() {
return fCategory;
}
/**
* Returns children as breakpoints or nested containers.
*
* @return children as breakpoints or nested containers
*/
public Object[] getChildren() {
if (fChildContainers.isEmpty()) {
return getBreakpoints();
}
return getContainers();
}
/**
* Returns the index of the given child element (breakpoint or container.
*
* @param child Child to calculate index of.
* @return index of child
*/
public int getChildIndex(Object child) {
if (fChildContainers.isEmpty()) {
return fBreakpoints.indexOf(child);
}
return fChildContainers.indexOf(child);
}
/**
* Returns the containers nested in this container, possibly empty.
*
* @return the containers nested in this container, can be empty.
*/
public BreakpointContainer[] getContainers() {
return fChildContainers.toArray(new BreakpointContainer[fChildContainers.size()]);
}
/**
* Returns this container's organizer.
*
* @return this container's organizer
*/
@Override
public IBreakpointOrganizer getOrganizer() {
return fOrganizer;
}
/**
* Returns whether this container contains the given breakpoint.
*
* @param breakpoint the breakpoint to check
* @return true if this container contains the given breakpoint
*/
@Override
public boolean contains(IBreakpoint breakpoint) {
return fBreakpoints.contains(breakpoint);
}
/**
* Returns the child containers for the given breakpoint.
*
* @param breakpoint the breakpoint to get containers for
* @return child containers
*/
public BreakpointContainer[] getContainers(IBreakpoint breakpoint) {
if (contains(breakpoint)) {
BreakpointContainer[] containers = getContainers();
if (containers.length == 0) {
return new BreakpointContainer[]{this};
}
ArrayList<BreakpointContainer> list = new ArrayList<>();
for (int i = 0; i < containers.length; i++) {
BreakpointContainer container = containers[i];
BreakpointContainer[] subcontainers = container.getContainers(breakpoint);
if (subcontainers != null) {
for (int j = 0; j < subcontainers.length; j++) {
list.add(subcontainers[j]);
}
}
}
return list.toArray(new BreakpointContainer[list.size()]);
}
return new BreakpointContainer[0];
}
/*
* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (obj instanceof BreakpointContainer) {
BreakpointContainer container = (BreakpointContainer) obj;
// With Group by "Advanced" the same category can contain a different subset of breakpoints,
// therefore to have the same category is not enough to be equal.
if (! (fParent != null && container.fParent != null && fParent.equals(container.fParent) ||
fParent == null && container.fParent == null) ) {
return false;
}
if (getCategory() != null && container.getCategory() != null) {
return getCategory().equals(container.getCategory());
} else {
return true;
}
}
return super.equals(obj);
}
/*
* (non-Javadoc)
* @see org.eclipse.debug.internal.ui.model.elements.ElementContentProvider#getChildCount(java.lang.Object, org.eclipse.debug.internal.ui.viewers.model.provisional.IPresentationContext, org.eclipse.debug.internal.ui.viewers.model.provisional.IViewerUpdate)
*/
@Override
protected int getChildCount(Object element, IPresentationContext context, IViewerUpdate monitor) throws CoreException {
return getChildren().length;
}
/*
* (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.debug.internal.ui.viewers.model.provisional.IViewerUpdate)
*/
@Override
protected Object[] getChildren(Object parent, int index, int length, IPresentationContext context, IViewerUpdate monitor) throws CoreException {
return getElements(getChildren(), index, length);
}
/*
* (non-Javadoc)
* @see org.eclipse.debug.internal.ui.model.elements.ElementContentProvider#supportsContextId(java.lang.String)
*/
@Override
protected boolean supportsContextId(String id) {
return id.equals(IDebugUIConstants.ID_BREAKPOINT_VIEW);
}
/*
* (non-Javadoc)
* @see org.eclipse.core.runtime.IAdaptable#getAdapter(java.lang.Class)
*/
@Override
public <T> T getAdapter(Class<T> adapter) {
return Platform.getAdapterManager().getAdapter(this, adapter);
}
}