/******************************************************************************* * Copyright (c) 2000, 2015 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * 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<IBreakpoint>(); /** * Child containers - inserting new element into this container should use the insertChildContainer method */ final private List<BreakpointContainer> fChildContainers = new ArrayList<BreakpointContainer>(); /** * 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<IBreakpoint>(); 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<IBreakpoint>(); 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<BreakpointContainer>(); 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); } }