blob: bdd0af316dc5fd4814fffa2375cefa7d0026f435 [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.HashSet;
import java.util.Iterator;
import java.util.Set;
import org.eclipse.core.databinding.observable.Diffs;
import org.eclipse.core.databinding.observable.IStaleListener;
import org.eclipse.core.databinding.observable.StaleEvent;
import org.eclipse.core.databinding.observable.set.AbstractObservableSet;
import org.eclipse.core.databinding.observable.set.IObservableSet;
import org.eclipse.core.databinding.observable.set.ISetChangeListener;
import org.eclipse.core.databinding.observable.set.SetChangeEvent;
import org.eclipse.core.databinding.observable.set.SetDiff;
import org.eclipse.core.internal.databinding.observable.tree.IUnorderedTreeProvider;
import org.eclipse.core.internal.databinding.observable.tree.TreePath;
/**
* This set consists of all leaf nodes from the given tree (that is, all nodes
* for which ITreeProvider.createChildSet returns null).
*/
public class LeafNodesSet extends AbstractObservableSet {
private HashSet leafNodes = new HashSet();
private HashMap mapElementsOntoNodeInfo = new HashMap();
private IUnorderedTreeProvider tree;
private Object input;
private int staleCount = 0;
private class NodeInfo implements IStaleListener, ISetChangeListener {
// Number of times the element occurs in the tree
private int count;
// Element
private TreePath treePath;
// Children set (or null if this is a leaf node)
IObservableSet children;
private boolean wasStale = false;
/**
* @param treePath
*/
public NodeInfo(TreePath treePath) {
this.treePath = treePath;
children = tree.createChildSet(this.treePath);
if (children != null) {
children.addStaleListener(this);
children.addSetChangeListener(this);
}
count = 1;
}
public void handleSetChange(SetChangeEvent event) {
processDiff(treePath, event.diff);
}
public void handleStale(StaleEvent event) {
if (wasStale != children.isStale()) {
if (wasStale) {
staleCount--;
} else {
staleCount++;
}
wasStale = !wasStale;
}
setStale(staleCount > 0);
}
/**
*
*/
public void dispose() {
if (children != null) {
children.dispose();
children = null;
if (wasStale) {
staleCount--;
}
}
}
}
/**
* Creates a set that will contain the leaf nodes from the given tree
*
* @param tree
* tree whose leaf nodes will be computed
*/
public LeafNodesSet(IUnorderedTreeProvider tree) {
this(null, tree);
}
/**
* Creates a set that will contain the leaf nodes from the given tree, and
* sets the root of the tree to the given element.
*
* @param initialInput
* root of the tree
* @param tree
* tree whose leaf nodes will be computed
*/
public LeafNodesSet(Object initialInput, IUnorderedTreeProvider tree) {
super(tree.getRealm());
this.tree = tree;
if (initialInput != null) {
setInput(initialInput);
}
}
private void processDiff(TreePath treePath, SetDiff diff) {
Set removals = new HashSet();
HashSet additions = new HashSet();
for (Iterator iter = diff.getRemovals().iterator(); iter.hasNext();) {
Object next = iter.next();
elementRemoved(treePath.createChildPath(next), removals);
}
for (Iterator iter = diff.getAdditions().iterator(); iter.hasNext();) {
Object next = iter.next();
elementDiscovered(treePath.createChildPath(next), additions);
}
HashSet newRemovals = new HashSet();
newRemovals.addAll(removals);
newRemovals.removeAll(additions);
HashSet newAdditions = new HashSet();
newAdditions.addAll(additions);
newAdditions.removeAll(removals);
leafNodes.addAll(newAdditions);
leafNodes.removeAll(newRemovals);
if (!newAdditions.isEmpty() || !newRemovals.isEmpty()) {
setStale(staleCount > 0);
fireSetChange(Diffs.createSetDiff(newAdditions, newRemovals));
}
}
/**
* Sets the root of the tree to the given element.
*
* @param input
* new root of the tree
*/
public void setInput(Object input) {
Set removals = Collections.EMPTY_SET;
Set additions = Collections.EMPTY_SET;
if (this.input != null) {
removals = Collections.singleton(this.input);
} else if (input != null) {
additions = Collections.singleton(input);
}
this.input = input;
processDiff(TreePath.EMPTY, Diffs.createSetDiff(additions, removals));
}
/**
* Called when an element is removed from the tree. The given HashSet will
* be filled in with all removed leaf nodes.
*
* @param treePath
* @param removals
*/
private void elementRemoved(TreePath treePath, Set removals) {
NodeInfo newNode = (NodeInfo) mapElementsOntoNodeInfo.get(treePath);
if (newNode != null) {
newNode = new NodeInfo(treePath);
newNode.count--;
if (newNode.count == 0) {
mapElementsOntoNodeInfo.remove(treePath);
if (newNode.children != null) {
for (Iterator iter = newNode.children.iterator(); iter
.hasNext();) {
Object next = iter.next();
elementRemoved(treePath.createChildPath(next), removals);
}
newNode.children.dispose();
} else {
removals.add(treePath);
}
}
}
}
/**
* Called when a new element is discovered in the tree. The given HashSet
* will be filled in with all newly discovered leaf nodes.
*
* @param treePath
* @param additions
*/
private void elementDiscovered(TreePath treePath, HashSet additions) {
NodeInfo newNode = (NodeInfo) mapElementsOntoNodeInfo.get(treePath);
if (newNode == null) {
newNode = new NodeInfo(treePath);
mapElementsOntoNodeInfo.put(treePath, newNode);
if (newNode.children != null) {
for (Iterator iter = newNode.children.iterator(); iter
.hasNext();) {
Object next = iter.next();
elementDiscovered(treePath.createChildPath(next), additions);
}
} else {
additions.add(treePath);
}
} else {
// If this node was already known, increment the reference count.
newNode.count++;
}
}
protected Set getWrappedSet() {
return leafNodes;
}
public Object getElementType() {
return Object.class;
}
public void dispose() {
for (Iterator iter = mapElementsOntoNodeInfo.values().iterator(); iter
.hasNext();) {
NodeInfo next = (NodeInfo) iter.next();
if (next.children != null) {
next.dispose();
}
}
mapElementsOntoNodeInfo.clear();
leafNodes.clear();
super.dispose();
}
}