blob: 783ded3171fc09406e16a14f024931d91462bafb [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2006, 2007 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
*******************************************************************************/
package org.eclipse.jface.internal.databinding.internal.viewers;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import org.eclipse.core.databinding.observable.Diffs;
import org.eclipse.core.databinding.observable.list.IObservableList;
import org.eclipse.core.databinding.observable.set.AbstractObservableSet;
import org.eclipse.core.databinding.observable.set.IObservableSet;
import org.eclipse.core.databinding.observable.set.SetDiff;
import org.eclipse.core.internal.databinding.observable.tree.IOrderedTreeProvider;
import org.eclipse.jface.viewers.ITreePathContentProvider;
import org.eclipse.jface.viewers.ITreeViewerListener;
import org.eclipse.jface.viewers.TreeExpansionEvent;
import org.eclipse.jface.viewers.TreePath;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
/**
* Converts an IOrderedTreeProvider into an ITreeContentProvider that is
* suitable for use with a JFace TreeViewer.
*
* <p>
* This content provider works correctly with trees containing duplicate
* elements.
* </p>
*
* @since 3.3
*/
public class OrderedTreeContentProvider implements ITreePathContentProvider {
private HashMap mapElementToTreeNode = new HashMap();
private LinkedList enqueuedPrefetches = new LinkedList();
class KnownElementsSet extends AbstractObservableSet {
/**
*/
protected KnownElementsSet() {
super(provider.getRealm());
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.internal.databinding.provisional.observable.set.AbstractObservableSet#getWrappedSet()
*/
protected Set getWrappedSet() {
return mapElementToTreeNode.keySet();
}
void doFireDiff(Set added, Set removed) {
fireSetChange(Diffs.createSetDiff(added, removed));
}
void doFireStale(boolean isStale) {
if (isStale) {
fireStale();
} else {
fireSetChange(Diffs.createSetDiff(Collections.EMPTY_SET,
Collections.EMPTY_SET));
}
}
public Object getElementType() {
return Object.class;
}
protected void fireSetChange(SetDiff diff) {
super.fireSetChange(diff);
}
}
KnownElementsSet elements;
private ITreeViewerListener expandListener = new ITreeViewerListener() {
public void treeCollapsed(TreeExpansionEvent event) {
}
public void treeExpanded(TreeExpansionEvent event) {
}
};
private IPrefetchingTree prefetchingTree;
private IOrderedTreeProvider provider;
private Object pendingNode;
private int avoidViewerUpdates;
private TreeViewer treeViewer;
private int staleCount = 0;
private boolean useRefresh;
private int maxPrefetches = -1;
/**
* Constructs a content provider that will render the given tree in a
* TreeViewer.
*
* @param provider
* IObservableTree that provides the contents of the tree. The
* given provider may optionally implement IPrefetchingTree if it
* wants to selectively enable or disable prefetching from
* particular nodes.
* @param pendingNode
* element to insert whenever a node is being fetched in the
* background
*/
public OrderedTreeContentProvider(IOrderedTreeProvider provider,
Object pendingNode) {
this(provider, pendingNode, false);
}
/**
* Constructs a content provider that will render the given tree in a
* TreeViewer.
*
* @param provider
* IObservableTree that provides the contents of the tree
* @param pendingNode
* element to insert whenever a node is being fetched in the
* background
* @param useRefresh
* true = notify the viewer of changes by calling refresh(...),
* false = notify the viewer of changes by calling add(...) and
* remove(...). Using false is more efficient, but may not work
* with TreeViewer subclasses.
*/
public OrderedTreeContentProvider(IOrderedTreeProvider provider,
Object pendingNode, boolean useRefresh) {
this.provider = provider;
this.prefetchingTree = PrefetchingTree.getPrefetchingTree(provider);
this.pendingNode = pendingNode;
this.useRefresh = useRefresh;
elements = new KnownElementsSet();
}
/**
* Sets the maximum number of pending prefetches.
*
* @param maxPrefetches
*/
public void setMaxPrefetches(int maxPrefetches) {
this.maxPrefetches = maxPrefetches;
}
/* package */IObservableList createChildList(TreePath treePath) {
Object[] segments = new Object[treePath.getSegmentCount()];
for (int i = 0; i < segments.length; i++) {
segments[i] = treePath.getSegment(i);
}
return provider.createChildList(new org.eclipse.core.internal.databinding.observable.tree.TreePath(segments));
}
/* package */void remove(Object element, List removals, boolean lastElement) {
if (avoidViewerUpdates == 0) {
for (Iterator iter = removals.iterator(); iter.hasNext();) {
Object next = iter.next();
OrderedTreeNode nextNode = (OrderedTreeNode) mapElementToTreeNode
.get(next);
if (nextNode != null) {
nextNode.removeParent(element);
removeIfUnused(nextNode);
}
}
if (lastElement || useRefresh) {
treeViewer.refresh(element);
} else {
treeViewer.remove(element, removals.toArray());
}
}
}
/* package */void add(TreePath element, List additions) {
if (avoidViewerUpdates == 0) {
// Handle new parents
addParent(element, additions);
if (useRefresh) {
treeViewer.refresh(element);
} else {
treeViewer.add(element, additions);
}
}
}
/* package */void insert(TreePath element, Object addition, int position) {
if (avoidViewerUpdates == 0) {
// Handle new parents
addParent(element, Collections.singletonList(addition));
if (useRefresh) {
treeViewer.refresh(element);
} else {
treeViewer.insert(element, addition, position);
}
}
}
/**
* Ensures that the given set of children have the given parent as one of
* their parents.
*
* @param parentPath
* @param children
*/
private void addParent(TreePath parentPath, List children) {
for (Iterator iter = children.iterator(); iter.hasNext();) {
Object next = iter.next();
OrderedTreeNode nextNode = getNode(parentPath.createChildPath(next));
nextNode.addParent(parentPath);
}
}
/**
* Returns the element that should be inserted into the tree when fetching
* the children of the node that is both stale and empty.
*
* @return the element that should be inserted into the tree when fetching
* the children of a node that is stale and empty
*/
public final Object getPendingNode() {
return pendingNode;
}
/**
* Returns the IObservableList representing the children of the given node.
* Never null.
*
* @param parent
* parent element. Must be a valid node from the tree.
* @return the list of children of the given parent node
*/
public IObservableList getChildrenList(TreePath parent) {
IObservableList result = getNode(parent).getChildrenList();
return result;
}
public void dispose() {
if (treeViewer != null) {
try {
avoidViewerUpdates++;
enqueuedPrefetches.clear();
Object[] keys = mapElementToTreeNode.keySet().toArray();
for (int i = 0; i < keys.length; i++) {
Object key = keys[i];
OrderedTreeNode result = (OrderedTreeNode) mapElementToTreeNode.get(key);
if (result != null) {
result.dispose();
}
}
} finally {
avoidViewerUpdates--;
}
}
}
public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
// This should only ever be called for a single viewer
setViewer(viewer);
if (oldInput != null && newInput != null && oldInput.equals(newInput)) {
return;
}
try {
avoidViewerUpdates++;
removeIfUnused(oldInput);
} finally {
avoidViewerUpdates--;
}
}
private void removeIfUnused(Object element) {
OrderedTreeNode result = (OrderedTreeNode) mapElementToTreeNode
.get(element);
if (result != null && result.getParent() == null) {
mapElementToTreeNode.remove(element);
elements.doFireDiff(Collections.EMPTY_SET, Collections
.singleton(element));
result.dispose();
}
}
private void setViewer(Viewer viewer) {
if (!(viewer instanceof TreeViewer)) {
throw new IllegalArgumentException(
"This content provider can only be used with TreeViewers"); //$NON-NLS-1$
}
TreeViewer newTreeViewer = (TreeViewer) viewer;
if (newTreeViewer != treeViewer) {
if (treeViewer != null) {
treeViewer.removeTreeListener(expandListener);
}
this.treeViewer = newTreeViewer;
if (newTreeViewer != null) {
newTreeViewer.addTreeListener(expandListener);
}
}
}
public Object[] getChildren(TreePath parentElement) {
List result = getNode(parentElement).getChildren();
addParent(parentElement, result);
return result.toArray();
}
private OrderedTreeNode getNode(TreePath parentElement) {
OrderedTreeNode result = (OrderedTreeNode) mapElementToTreeNode
.get(parentElement);
if (result == null) {
result = new OrderedTreeNode(parentElement, this);
mapElementToTreeNode.put(parentElement, result);
elements.fireSetChange(Diffs.createSetDiff(Collections
.singleton(parentElement), Collections.EMPTY_SET));
}
return result;
}
/**
* Returns the set of all elements that have been discovered in this tree so
* far. Callers must not dispose this set. Never null.
*
* @return the set of all elements that have been discovered in this tree so
* far.
*/
public IObservableSet getKnownElements() {
return elements;
}
/* package */void changeStale(int staleDelta) {
staleCount += staleDelta;
processPrefetches();
elements.setStale(staleCount != 0);
}
/**
* Returns the associated tree viewer.
*
* @return the associated tree viewer
*/
public TreeViewer getViewer() {
return treeViewer;
}
/**
* Returns true iff the given element is stale.
*
* @param element
* the element to query for staleness. Must exist in the tree.
* @return true iff the given element is stale
*/
public boolean isDirty(TreePath element) {
return getChildrenList(element).isStale();
}
/* package */void enqueuePrefetch(OrderedTreeNode node) {
if (prefetchingTree.shouldPrefetch(node.getElement())) {
if (staleCount == 0) {
// Call node.getChildren()... this will cause us to start
// listening to the
// node and will trigger prefetching. Don't call prefetch since
// this method
// is intended to be called inside getters (which will simply
// return the
// fetched nodes) and prefetch() is intended to be called inside
// an asyncExec,
// which will notify the viewer directly of the newly discovered
// nodes.
node.getChildren();
} else {
enqueuedPrefetches.add(node);
while (maxPrefetches >= 0
&& enqueuedPrefetches.size() > maxPrefetches) {
enqueuedPrefetches.removeFirst();
}
}
}
}
private void processPrefetches() {
while (staleCount == 0 && !enqueuedPrefetches.isEmpty()) {
OrderedTreeNode next = (OrderedTreeNode) enqueuedPrefetches.removeLast();
// Note that we don't remove nodes from the prefetch queue when they
// are disposed,
// so we may encounter disposed nodes at this time.
if (!next.isDisposed()) {
next.prefetch();
}
}
}
public TreePath[] getParents(Object element) {
return new TreePath[0];
}
public boolean hasChildren(TreePath path) {
return getNode(path).shouldShowPlus();
}
public Object[] getElements(Object inputElement) {
return getChildren(new TreePath(new Object[]{inputElement}));
}
}