diff options
author | Martin Fleck | 2017-06-26 14:50:21 +0000 |
---|---|---|
committer | Laurent Goubet | 2017-09-07 13:06:13 +0000 |
commit | dd116940207af247472cc12db866951ea50d470c (patch) | |
tree | 2b4135dc3f21a57ed398095961959e44af38ff2e | |
parent | 4779b6819bd62e5ee9e67d21eaa72d3583970f73 (diff) | |
download | org.eclipse.emf.compare-dd116940207af247472cc12db866951ea50d470c.tar.gz org.eclipse.emf.compare-dd116940207af247472cc12db866951ea50d470c.tar.xz org.eclipse.emf.compare-dd116940207af247472cc12db866951ea50d470c.zip |
[518968] Only traverse and cache necessary Papyrus tree items
Avoid traversing and caching the entire Papyrus content tree when
determining the item to be selected:
- Use incremental computation and caching of Papyrus tree items
Bug: 518968
Change-Id: I4ac04d252747600b48d01e3b6232200e5ce4fa51
Signed-off-by: Martin Fleck <mfleck@eclipsesource.com>
-rw-r--r-- | plugins/org.eclipse.emf.compare.diagram.ide.ui.papyrus/src/org/eclipse/emf/compare/diagram/ide/ui/papyrus/contentmergeviewer/PapyrusTreeContentMergeViewer.java | 471 |
1 files changed, 348 insertions, 123 deletions
diff --git a/plugins/org.eclipse.emf.compare.diagram.ide.ui.papyrus/src/org/eclipse/emf/compare/diagram/ide/ui/papyrus/contentmergeviewer/PapyrusTreeContentMergeViewer.java b/plugins/org.eclipse.emf.compare.diagram.ide.ui.papyrus/src/org/eclipse/emf/compare/diagram/ide/ui/papyrus/contentmergeviewer/PapyrusTreeContentMergeViewer.java index c9d69d1fc..b5aa5be35 100644 --- a/plugins/org.eclipse.emf.compare.diagram.ide.ui.papyrus/src/org/eclipse/emf/compare/diagram/ide/ui/papyrus/contentmergeviewer/PapyrusTreeContentMergeViewer.java +++ b/plugins/org.eclipse.emf.compare.diagram.ide.ui.papyrus/src/org/eclipse/emf/compare/diagram/ide/ui/papyrus/contentmergeviewer/PapyrusTreeContentMergeViewer.java @@ -11,14 +11,16 @@ *******************************************************************************/ package org.eclipse.emf.compare.diagram.ide.ui.papyrus.contentmergeviewer; -import static com.google.common.base.Predicates.instanceOf; -import static com.google.common.base.Predicates.not; -import static com.google.common.collect.Iterables.any; -import static java.util.Arrays.asList; - import com.google.common.collect.ImmutableList; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; import java.util.Map; import java.util.ResourceBundle; @@ -35,6 +37,7 @@ import org.eclipse.emf.compare.rcp.ui.internal.mergeviewer.impl.TreeMergeViewer; import org.eclipse.emf.compare.rcp.ui.mergeviewer.IMergeViewer.MergeViewerSide; import org.eclipse.emf.compare.rcp.ui.mergeviewer.item.IMergeViewerItem; import org.eclipse.emf.compare.rcp.ui.mergeviewer.item.provider.IMergeViewerItemProviderConfiguration; +import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.edit.provider.ComposedAdapterFactory; import org.eclipse.jface.viewers.IBaseLabelProvider; import org.eclipse.jface.viewers.IContentProvider; @@ -57,13 +60,25 @@ public class PapyrusTreeContentMergeViewer extends TreeContentMergeViewer { private static final int MAX_SEARCH_LEVEL = 20; /** - * Map of objects to {@link PapyrusContentProviderMergeViewerItem Papyrus merge viewer items } used when + * Map of objects to {@link PapyrusContentProviderMergeViewerItem Papyrus merge viewer items} used when * changing the selection in order to find the merge viewer item to be selected when a specific object * (model element or diff) is to be revealed. */ private Map<Object, IMergeViewerItem> cachedMapForSelection; /** + * A queue of computations for computing the content tree items in the {@linked #getLeftMergeViewer() + * right merge viewer}. + */ + private LinkedList<AbstractContentFunction> leftContentComputations; + + /** + * A queue of computations for computing the content tree items in the {@linked #getRightMergeViewer() + * left merge viewer}. + */ + private LinkedList<AbstractContentFunction> rightContentComputations; + + /** * Constructor. * * @param style @@ -102,7 +117,8 @@ public class PapyrusTreeContentMergeViewer extends TreeContentMergeViewer { } @Override - protected IMergeViewerItemProviderConfiguration createMergeViewerItemProviderConfiguration(MergeViewerSide side) { + protected IMergeViewerItemProviderConfiguration createMergeViewerItemProviderConfiguration( + MergeViewerSide side) { ComposedAdapterFactory factory = new ComposedAdapterFactory(new AdapterFactory[] { new PapyrusFacetContentProviderWrapperAdapterFactory(), getAdapterFactory(), }); return new MergeViewerItemProviderConfiguration(factory, getDifferenceGroupProvider(), @@ -174,6 +190,10 @@ public class PapyrusTreeContentMergeViewer extends TreeContentMergeViewer { getAncestorMergeViewer().setInput(ancestor); getLeftMergeViewer().setInput(left); getRightMergeViewer().setInput(right); + + leftContentComputations = null; + rightContentComputations = null; + cachedMapForSelection = null; } IMergeViewerItem leftInitialItem = null; @@ -204,162 +224,367 @@ public class PapyrusTreeContentMergeViewer extends TreeContentMergeViewer { } /** - * Caches the tree viewer content given by the objects <code>left</code> and <code>right</code>, if we - * haven't built a cache yet. - * <p> - * The caching builds a {@link #cachedMapForSelection map} of objects to be objects of the tree and their - * {@link IMergeViewerItem} that represent those objects. - * </p> + * Sets the selection according to the accessor. * - * @param left - * the left object, which must be a {@link ICompareAccessor}. - * @param right - * the right object, which must be a {@link ICompareAccessor}. + * @param accessor + * The {@link ICompareAccessor} which contains the root tree elements and the initial + * selection. + * @param viewer + * The {@ink TreeMergeViewer} for which the selection is to be set. */ - private void cacheTreeViewerContentIfNecessary(Object left, Object right) { - if (cachedMapForSelection != null || notICompareAccessor(left, right)) { - // we already have a cache or can't build one anyway - return; - } - - cachedMapForSelection = new HashMap<Object, IMergeViewerItem>(); + private void setSelection(ICompareAccessor accessor, TreeMergeViewer viewer) { + // First try to set the initial item directly + final IMergeViewerItem initialItem = accessor.getInitialItem(); + viewer.setSelection(new StructuredSelection(initialItem), true); - cacheTreeViewerContent((ICompareAccessor)left, getLeftMergeViewer(), MergeViewerSide.LEFT); - cacheTreeViewerContent((ICompareAccessor)right, getRightMergeViewer(), MergeViewerSide.RIGHT); + // if that didn't work (empty selection), use cache to find correct merge viewer item + if (viewer.getSelection().isEmpty()) { + IMergeViewerItem itemToBeSelected = getItemToBeSelected(initialItem); + if (itemToBeSelected != null) { + viewer.setSelection(new StructuredSelection(itemToBeSelected), true); + } else { + viewer.setSelection(new StructuredSelection(), true); + } + } } /** - * Specifies whether the given <code>objects</code> are all instances of {@link ICompareAccessor}. + * Determines the item to select from the corresponding side viewer, given an input item. * - * @param objects - * The objects to check. - * @return <code>true</code> if they all are instances of {@link ICompareAccessor}, <code>false</code> - * otherwise. + * @param item + * the input item. + * @return the item to be selected. */ - private boolean notICompareAccessor(Object... objects) { - return any(asList(objects), not(instanceOf(ICompareAccessor.class))); + private IMergeViewerItem getItemToBeSelected(IMergeViewerItem item) { + MergeViewerSide side = item.getSide(); + Object itemValue = item.getSideValue(side); + IMergeViewerItem result; + if (cachedMapForSelection == null) { + cachedMapForSelection = new HashMap<>(); + result = null; + } else { + result = cachedMapForSelection.get(itemValue); + } + + if (result == null) { + LinkedList<AbstractContentFunction> contentComputations; + + switch (side) { + case LEFT: + if (leftContentComputations == null) { + leftContentComputations = new LinkedList<>(); + leftContentComputations.add(new ElementFunction(side, getLeftMergeViewer(), + leftContentComputations, cachedMapForSelection)); + } + contentComputations = leftContentComputations; + break; + + case RIGHT: + if (rightContentComputations == null) { + rightContentComputations = new LinkedList<>(); + rightContentComputations.add(new ElementFunction(side, getRightMergeViewer(), + rightContentComputations, cachedMapForSelection)); + } + contentComputations = rightContentComputations; + break; + + default: + throw new IllegalArgumentException(); + } + + Collection<Object> containers = getContainers(itemValue); + if (!containers.isEmpty()) { + // move necessary computations to the front + List<AbstractContentFunction> containerComputations = extractComputations(contentComputations, + containers); + contentComputations.addAll(0, containerComputations); + } + + // compute item + while (!contentComputations.isEmpty()) { + result = contentComputations.removeFirst().apply(itemValue, containers); + if (result != null) { + break; + } + } + } + + return result; + } + + @Override + protected void handleDispose(DisposeEvent event) { + if (cachedMapForSelection != null) { + cachedMapForSelection = null; + } + + super.handleDispose(event); } /** - * Traverses all {@link ITreeContentProvider#getElements(Object) elements} of the given - * <code>accessor</code> and caches its content for the given <code>side</code>. - * <p> - * Note that this may be an expensive method, if the model is very large. - * </p> + * Extracts all content functions for the given objects from the computation list. * - * @param accessor - * The accessor representing the content to be cached. - * @param viewer - * The viewer for obtaining the content provider from. - * @param side - * The side of the viewer. + * @param computations + * computation list from which functions are extracted. + * @param values + * values specifying which functions should be extracted. + * @return Non-null list of computations covering the given objects. */ - private void cacheTreeViewerContent(ICompareAccessor accessor, TreeMergeViewer viewer, - MergeViewerSide side) { - final ITreeContentProvider provider = ITreeContentProvider.class.cast(viewer.getContentProvider()); - for (Object element : provider.getElements(accessor)) { - if (element instanceof IMergeViewerItem) { - final IMergeViewerItem item = IMergeViewerItem.class.cast(element); - cacheTreeViewerContent(item, provider, side, MAX_SEARCH_LEVEL); + private List<AbstractContentFunction> extractComputations( + LinkedList<AbstractContentFunction> computations, Collection<Object> values) { + List<AbstractContentFunction> extractedComputations = new ArrayList<>(); + Iterator<AbstractContentFunction> computationsIterator = computations.iterator(); + while (computationsIterator.hasNext()) { + AbstractContentFunction contentFuction = computationsIterator.next(); + if (values.contains(contentFuction.getValue())) { + computationsIterator.remove(); + extractedComputations.add(contentFuction); } } + return extractedComputations; } /** - * Caches the given <code>item</code> and its children determined by the given <code>provider</code>. + * Returns all EObject containers for the given value, if the given object is an EObject. * - * @param item - * The item to be cached. - * @param provider - * The content provider for determining the children of <code>item</code>. - * @param side - * The merge viewer side. - * @param maxSearchLevel - * The maximum search level. + * @param eObject + * eObject value. + * @return Non-null collection of containers. */ - private void cacheTreeViewerContent(IMergeViewerItem item, ITreeContentProvider provider, - MergeViewerSide side, int maxSearchLevel) { - if (maxSearchLevel == 0) { - return; - } - - cacheItem(item, side); - - for (Object child : provider.getChildren(item)) { - if (child instanceof IMergeViewerItem) { - final IMergeViewerItem childItem = (IMergeViewerItem)child; - cacheTreeViewerContent(childItem, provider, side, maxSearchLevel - 1); + private Collection<Object> getContainers(Object eObject) { + Collection<Object> containers; + if (eObject instanceof EObject) { + containers = new HashSet<>(); + for (EObject container = ((EObject)eObject).eContainer(); container != null; container = container + .eContainer()) { + containers.add(container); } + } else { + containers = Collections.emptySet(); } + return containers; } /** - * Caches the given <code>item</code> for the given side. + * A function for computing the items in the content tree induced by an item provider. Instances are + * managed in a {@link #contentComputations} queue and are processed to update the {@linked #selections + * selections} cache. * - * @param item - * The item to cache. - * @param side - * The side. + * @see PapyrusTreeContentMergeViewer#getItemToBeSelected(IMergeViewerItem) */ - private void cacheItem(IMergeViewerItem item, MergeViewerSide side) { - if (MergeViewerSide.LEFT.equals(side) && item.getLeft() != null) { - cachedMapForSelection.put(item.getLeft(), item); - } else if (MergeViewerSide.RIGHT.equals(side) && item.getRight() != null) { - cachedMapForSelection.put(item.getRight(), item); + private abstract static class AbstractContentFunction { + /** + * The side for which the computation is being done. + */ + protected final MergeViewerSide side; + + /** + * The tree content provider which induces the content tree. + */ + protected final ITreeContentProvider provider; + + /** + * The queue of computations being managed. + * + * @see PapyrusTreeContentMergeViewer#leftContentComputations + * @see PapyrusTreeContentMergeViewer#rightContentComputations + */ + protected final LinkedList<AbstractContentFunction> contentComputations; + + /** + * The selections being cached. + * + * @see PapyrusTreeContentMergeViewer#selections + */ + protected final Map<Object, IMergeViewerItem> selections; + + /** + * Creates and instance for a given side that induces content based on the given providers and manages + * the queue of content computations, updating the selections. + * + * @param side + * the side for which the content is being computed. + * @param provider + * the provider used to induce content. + * @param contentComputations + * the queue of computations being managed. + * @param selections + * the selection cache being managed. + */ + protected AbstractContentFunction(MergeViewerSide side, ITreeContentProvider provider, + LinkedList<AbstractContentFunction> contentComputations, + Map<Object, IMergeViewerItem> selections) { + this.side = side; + this.provider = provider; + this.contentComputations = contentComputations; + this.selections = selections; + } + + /** + * Computes new content and if any of the new content item matches the given value, returns the + * corresponding item from the side viewer. The {@link #contentComputations queue} is updated with + * functions for the new content items. Any new item with a value that matches one of the containers + * is placed at the front of the queue, rather than the back. + * + * @param matchValue + * the value to match. + * @param containers + * the items that are of high priority to process first. + * @return the corresponding side viewer item for the match value, or null. + */ + public abstract IMergeViewerItem apply(Object matchValue, Collection<?> containers); + + /** + * Returns the value for which content will be computed. + * + * @return the value for which content will be computed. + */ + public abstract Object getValue(); + + /** + * A base helper method that does the computation needed by {@link #apply(Object, Collection)}. + * + * @param matchValue + * the value to be matched. + * @param containers + * the items that are of high priority to process first. + * @param values + * the new content items to be processed. + * @param depth + * the current tree depth of the traversal. + * @return the corresponding side viewer item for the match value, or null. + */ + protected IMergeViewerItem apply(Object matchValue, Collection<?> containers, Object[] values, + int depth) { + IMergeViewerItem result = null; + for (Object element : values) { + if (element instanceof IMergeViewerItem) { + final IMergeViewerItem item = (IMergeViewerItem)element; + Object sideValue = item.getSideValue(side); + + // If we don't yet have a result, and the new value is equal to the matching value, then + // cache it as the result. + if (result == null) { + if (sideValue != null && sideValue.equals(matchValue)) { + result = item; + } + } + + // Create a new child function for the given item and add it to the front or the back of + // the queue depending on whether the high priority containers include the item's side + // value. So in general the content is build breadth first, but the subtree of the + // containers is processed depth first. + ChildFunction childFunction = new ChildFunction(side, provider, item, depth, + contentComputations, selections); + if (containers.contains(sideValue)) { + contentComputations.addFirst(childFunction); + } else { + contentComputations.addLast(childFunction); + + } + } + } + return result; } } /** - * Sets the selection according to the accessor. - * - * @param accessor - * The {@link ICompareAccessor} which contains the root tree elements and the initial - * selection. - * @param viewer - * The {@ink TreeMergeViewer} for which the selection is to be set. + * A content function that computes the root elements of a side viewer. */ - private void setSelection(ICompareAccessor accessor, TreeMergeViewer viewer) { - // First try to set the initial item directly - final IMergeViewerItem initialItem = accessor.getInitialItem(); - viewer.setSelection(new StructuredSelection(initialItem), true); + private static final class ElementFunction extends AbstractContentFunction { + /** + * The input object of the side viewer. + */ + private final Object input; + + /** + * Creates and instance for a given side viewer that induces content based on the viewer's provider + * and manages the queue of content computations, updating the selections. + * + * @param side + * the side for which the content is being computed. + * @param viewer + * the side viewer for which to induce content. + * @param contentComputations + * the queue of computations being managed. + * @param selections + * the selection cache being managed. + */ + private ElementFunction(MergeViewerSide side, TreeMergeViewer viewer, + LinkedList<AbstractContentFunction> contentComputations, + Map<Object, IMergeViewerItem> selections) { + super(side, (ITreeContentProvider)viewer.getContentProvider(), contentComputations, selections); + this.input = viewer.getInput(); + } - // if that didn't work (empty selection), use cache to find correct merge viewer item - if (viewer.getSelection().isEmpty()) { - // init cache, if necessary - cacheTreeViewerContentIfNecessary(getLeftMergeViewer().getInput(), - getRightMergeViewer().getInput()); - final IMergeViewerItem itemToBeSelected = getItemToBeSelectedFromCache(initialItem); - if (itemToBeSelected != null) { - viewer.setSelection(new StructuredSelection(itemToBeSelected), true); - } else { - viewer.setSelection(new StructuredSelection(), true); - } + @Override + public Object getValue() { + return input; + } + + @Override + public IMergeViewerItem apply(Object matchValue, Collection<?> containers) { + return apply(matchValue, containers, provider.getElements(input), 0); } } /** - * Obtains the item for the selection in the tree viewers for the given <code>item</code>. - * - * @param item - * The item to be selected. - * @return The item that can be used for selection in the merge viewer trees. + * A content function that computes the child elements of a side viewer. */ - private IMergeViewerItem getItemToBeSelectedFromCache(IMergeViewerItem item) { - IMergeViewerItem itemToBeSelected = null; - if (MergeViewerSide.LEFT.equals(item.getSide()) && item.getLeft() != null) { - itemToBeSelected = cachedMapForSelection.get(item.getLeft()); - } else if (MergeViewerSide.RIGHT.equals(item.getSide()) && item.getRight() != null) { - itemToBeSelected = cachedMapForSelection.get(item.getRight()); + private static final class ChildFunction extends AbstractContentFunction { + + /** + * The item for which to induce child content. + */ + private final IMergeViewerItem item; + + /** + * The depth of the item. + */ + private final int depth; + + /** + * Creates and instance for a given side viewer's content provider that induces for the given item and + * the given depth and manages the queue of content computations, updating the selections. + * + * @param side + * the side for which the content is being computed. + * @param provider + * the content provider of the side viewer. + * @param item + * the item for which to induce child content. + * @param depth + * the depth of the item. + * @param contentComputations + * the queue of computations being managed. + * @param selections + * the selection cache being managed. + */ + private ChildFunction(MergeViewerSide side, ITreeContentProvider provider, IMergeViewerItem item, + int depth, LinkedList<AbstractContentFunction> contentComputations, + Map<Object, IMergeViewerItem> selections) { + super(side, provider, contentComputations, selections); + this.item = item; + this.depth = depth; + + Object value = item.getSideValue(side); + if (value != null) { + selections.put(value, item); + } } - return itemToBeSelected; - } - @Override - protected void handleDispose(DisposeEvent event) { - if (cachedMapForSelection != null) { - this.cachedMapForSelection.clear(); - this.cachedMapForSelection = null; + @Override + public Object getValue() { + return item.getSideValue(side); + } + + @Override + public IMergeViewerItem apply(Object matchValue, Collection<?> containers) { + if (depth == MAX_SEARCH_LEVEL) { + return null; + } else { + return apply(matchValue, containers, provider.getChildren(item), depth + 1); + } } - super.handleDispose(event); } } |