blob: 5de23fb4d0dc8fa049a9e2976f6b03be9a7c292b [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
* Stefan Xenos, IBM - initial API and implementation
*******************************************************************************/
package org.eclipse.jface.internal.databinding.provisional.viewers;
import java.util.ArrayList;
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.Set;
import org.eclipse.core.databinding.observable.Diffs;
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.IUnorderedTreeProvider;
import org.eclipse.jface.viewers.ITreeContentProvider;
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;
/**
* NON-API - Generic tree content provider to be used with an AbstractTreeViewer based on a IUnorderedTreeProvider.
* @since 1.1
*
*/
public class UnorderedTreeContentProvider implements ITreeContentProvider, ITreePathContentProvider {
private HashMap mapElementToTreeNode = new HashMap();
private LinkedList enqueuedPrefetches = new LinkedList();
private IParentProvider rootParentProvider = null;
private boolean useTreePaths = false;
class KnownElementsSet extends AbstractObservableSet {
protected KnownElementsSet() {
super();
}
/* (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));
}
public void fireSetChange(SetDiff diff) {
super.fireSetChange(diff);
}
void doFireStale(boolean isStale) {
if (isStale) {
fireStale();
} else {
fireSetChange(Diffs.createSetDiff(Collections.EMPTY_SET, Collections.EMPTY_SET));
}
}
/* (non-Javadoc)
* @see org.eclipse.jface.internal.databinding.provisional.observable.set.IObservableSet#getElementType()
*/
public Object getElementType() {
return new Object();
}
}
KnownElementsSet elements = new KnownElementsSet();
private ITreeViewerListener expandListener = new ITreeViewerListener() {
public void treeCollapsed(TreeExpansionEvent event) {
}
public void treeExpanded(TreeExpansionEvent event) {
}
};
private IUnorderedTreeProvider provider;
private Object pendingNode;
private int avoidViewerUpdates;
private TreeViewer treeViewer;
private int staleCount = 0;
private boolean useRefresh;
private int maxPrefetches = 0;
/**
* 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 UnorderedTreeContentProvider(IUnorderedTreeProvider provider,
Object pendingNode, boolean useRefresh) {
this.provider = provider;
this.pendingNode = pendingNode;
this.useRefresh = useRefresh;
}
/**
* Sets whether this content provider should add/remove elements using
* TreePaths (true) or elements (false).
*
* <p></p>
* <p>When using elements:</p>
*
* <ul>
* <li>Cycles are permitted (elements can be their own ancestor)</li>
* <li>Addition, removal, and refresh are slightly faster</li>
* <li>It is not possible to have more than one content provider per tree</li>
* <li>The setRootPath(...) method is ignored</li>
* </ul>
*
* <p></p>
* <p>When using TreePaths:</p>
*
* <ul>
* <li>Cycles are not permitted (elements cannot be their own parent)</li>
* <li>Addition, removal, and refresh are slightly slower</li>
* <li>It is possible to use more than one content provider in the same tree</li>
* <li>The setRootPath(...) method can be used to direct the output to a particular
* subtree</li>
* </ul>
*
* @param usePaths
*/
public void useTreePaths(boolean usePaths) {
this.useTreePaths = usePaths;
}
/**
* @param rootParentProvider
*/
public void setRootPath(IParentProvider rootParentProvider) {
this.rootParentProvider = rootParentProvider;
}
/**
* @param maxPrefetches
*/
public void setMaxPrefetches(int maxPrefetches) {
this.maxPrefetches = maxPrefetches;
}
/* package */ IObservableSet createChildSet(Object element) {
return provider.createChildSet(element);
}
/* package */ void remove(Object element, Set removals, boolean lastElement) {
if (removals.isEmpty()) {
return;
}
if (avoidViewerUpdates == 0) {
if (lastElement || useRefresh) {
doRefresh(element);
} else {
if (useTreePaths) {
List toRemove = new ArrayList();
TreePath[] parents = getParents(element);
for (int i = 0; i < parents.length; i++) {
TreePath parent = parents[i];
for (Iterator iter = removals.iterator(); iter.hasNext();) {
Object elementToRemove = (Object) iter.next();
toRemove.add(parent.createChildPath(element).createChildPath(elementToRemove));
}
}
treeViewer.remove((TreePath[]) toRemove.toArray(new TreePath[toRemove.size()]));
} else {
treeViewer.remove(element, removals.toArray());
}
}
for (Iterator iter = removals.iterator(); iter.hasNext();) {
Object next = (Object) iter.next();
TreeNode nextNode = (TreeNode)mapElementToTreeNode.get(next);
if (nextNode != null) {
nextNode.removeParent(element);
removeIfUnused(nextNode);
}
}
}
}
/* package */ void add(Object element, Set additions) {
if (additions.isEmpty()) {
return;
}
if (avoidViewerUpdates == 0) {
// Handle new parents
addParent(element, additions);
if (useRefresh) {
doRefresh(element);
} else {
if (useTreePaths) {
TreePath[] parents = getParents(element);
for (int i = 0; i < parents.length; i++) {
TreePath parent = parents[i];
treeViewer.add(parent.createChildPath(element), additions.toArray());
}
} else {
treeViewer.add(element, additions.toArray());
}
}
}
}
private void doRefresh(Object element) {
treeViewer.refresh(element);
}
/**
* Ensures that the given set of children have the given parent as
* one of their parents.
*
* @param parent
* @param children
*/
private void addParent(Object parent, Set children) {
for (Iterator iter = children.iterator(); iter.hasNext();) {
Object next = (Object) iter.next();
TreeNode nextNode = getNode(next);
nextNode.addParent(parent);
}
}
/**
* @return saouesnth
*/
public final Object getPendingNode() {
return pendingNode;
}
/**
* @param parent
* @return aueosnht
*/
public IObservableSet getChildrenSet(Object parent) {
IObservableSet result = getNode(parent).getChildrenSet();
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];
TreeNode result = (TreeNode)mapElementToTreeNode.get(key);
if (result != null) {
result.dispose();
}
}
setViewer(null);
} 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++;
TreeNode oldNode = (TreeNode)mapElementToTreeNode.get(oldInput);
if (oldNode != null) {
removeIfUnused(oldNode);
}
} finally {
avoidViewerUpdates--;
}
}
private void removeIfUnused(TreeNode toRemove) {
//TreeNode result = (TreeNode)mapElementToTreeNode.get(element);
Object element = toRemove.getElement();
if (toRemove.getParent() == null) {
mapElementToTreeNode.remove(element);
elements.doFireDiff(Collections.EMPTY_SET, Collections.singleton(element));
toRemove.dispose();
}
}
private void setViewer(Viewer viewer) {
if (viewer != null && !(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(Object parentElement) {
Set result = getNode(parentElement).getChildren();
addParent(parentElement, result);
return result.toArray();
}
private TreeNode getNode(Object parentElement) {
TreeNode result = (TreeNode)mapElementToTreeNode.get(parentElement);
if (result == null) {
result = new TreeNode(parentElement, this);
mapElementToTreeNode.put(parentElement, result);
elements.fireSetChange(Diffs.createSetDiff(Collections.singleton(parentElement),
Collections.EMPTY_SET));
}
return result;
}
public Object getParent(Object element) {
Object result = getNode(element).getParent();
if (result == null && rootParentProvider != null) {
result = rootParentProvider.getParent(element);
}
return result;
}
public boolean hasChildren(Object element) {
return getNode(element).shouldShowPlus();
}
public Object[] getElements(Object inputElement) {
return getChildren(inputElement);
}
/**
* @return aouesnth
*/
public IObservableSet getKnownElements() {
return elements;
}
/* package */ void changeStale(int staleDelta) {
staleCount += staleDelta;
processPrefetches();
elements.setStale(staleCount != 0);
}
/**
* @return aoueesnth
*/
public TreeViewer getViewer() {
return treeViewer;
}
/**
* @param element
* @return aoeusnth
*/
public boolean isDirty(Object element) {
return false;
}
/* package */ void enqueuePrefetch(TreeNode node) {
if (maxPrefetches > 0 || maxPrefetches == -1) {
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()) {
TreeNode next = (TreeNode)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 Object[] getChildren(TreePath parentPath) {
return getChildren(parentPath.getLastSegment());
}
public TreePath[] getParents(Object element) {
// Compute all paths that do not contain cycles
/**
* List of Lists
*/
List parentPaths = computeParents(element, new HashSet());
/**
* List of TreePath
*/
List result = new ArrayList();
for (Iterator iterator = parentPaths.iterator(); iterator.hasNext();) {
List nextPath = (List) iterator.next();
LinkedList resultPath = new LinkedList();
resultPath.addAll(nextPath);
Object nextParent = resultPath.isEmpty() ? element : resultPath.getFirst();
for(;nextParent != null;) {
if (rootParentProvider != null) {
nextParent = rootParentProvider.getParent(nextParent);
if (nextParent != null) {
resultPath.addFirst(nextParent);
}
} else {
nextParent = null;
}
}
result.add(new TreePath(resultPath.toArray()));
}
if (result.isEmpty() && rootParentProvider != null) {
Object nextParent = rootParentProvider.getParent(element);
if (nextParent != null) {
LinkedList resultPath = new LinkedList();
while (nextParent != null) {
resultPath.addFirst(nextParent);
nextParent = rootParentProvider.getParent(nextParent);
}
result.add(new TreePath(resultPath.toArray()));
}
}
return (TreePath[]) result.toArray(new TreePath[result.size()]);
}
/**
*
* @param node
* @param toIgnore
* @return a list of Lists, indicating all known paths to the given node
*/
private List computeParents(Object node, HashSet toIgnore) {
List result = new ArrayList();
boolean containedNode = toIgnore.add(node);
TreeNode tn = getNode(node);
HashSet parents = new HashSet();
parents.addAll(tn.getParents());
parents.removeAll(toIgnore);
if (parents.isEmpty()) {
ArrayList newPath = new ArrayList();
result.add(newPath);
} else {
for (Iterator iterator = parents.iterator(); iterator.hasNext();) {
Object parent = iterator.next();
List parentPaths = computeParents(parent, toIgnore);
for (Iterator iterator2 = parentPaths.iterator(); iterator2
.hasNext();) {
List parentPath = (List) iterator2.next();
parentPath.add(parent);
result.add(parentPath);
}
}
}
if (containedNode) {
toIgnore.remove(node);
}
return result;
}
public boolean hasChildren(TreePath path) {
return hasChildren(path.getLastSegment());
}
}