blob: 5bdd13fa085da49af9e14d9a6a6057ce4fa3e464 [file] [log] [blame]
/*******************************************************************************
* Licensed Materials - Property of IBM
* (c) Copyright IBM Corporation 2005-2006. All Rights Reserved.
*
* Note to U.S. Government Users Restricted Rights:
* Use, duplication or disclosure restricted by GSA ADP Schedule
* Contract with IBM Corp.
*******************************************************************************/
package org.eclipse.jface.databinding.updatables;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import org.eclipse.jface.databinding.AbstractUpdatableSet;
import org.eclipse.jface.databinding.ChangeEvent;
import org.eclipse.jface.databinding.IChangeListener;
import org.eclipse.jface.databinding.IReadableSet;
import org.eclipse.jface.util.Assert;
/**
* Represents a set consisting of the union of elements from one or more other sets.
* This object does not need to be explicitly disposed. If nobody is listening to
* the UnionSet, the set will remove its listeners.
*
* @since 3.2
*/
public final class UnionSet extends AbstractUpdatableSet {
/**
* Set of ISetWithListeners
*/
private HashSet childSets = new HashSet();
/**
* Map of elements onto Integer reference counts. This map is constructed
* when the first listener is added to the union set. Null if nobody is
* listening to the UnionSet.
*/
private HashMap refCounts = null;
private IChangeListener childListener = new IChangeListener() {
public void handleChange(ChangeEvent changeEvent) {
switch (changeEvent.getChangeType()) {
case ChangeEvent.ADD_MANY:
processAdds((Collection)changeEvent.getNewValue());
break;
case ChangeEvent.REMOVE_MANY:
processRemoves((Collection)changeEvent.getNewValue());
break;
case ChangeEvent.STALE:
processStale(((Boolean)changeEvent.getNewValue()).booleanValue());
break;
}
}
};
private int staleCount = 0;
protected void processStale(final boolean isStale) {
if (isStale) {
staleCount++;
if (staleCount == 1) {
fireStale(true);
}
} else {
staleCount--;
if (staleCount == 0) {
fireStale(false);
}
}
}
public boolean isStale() {
// If we have a cached value
if (refCounts != null) {
return (staleCount > 0);
}
IReadableSet[] children = (IReadableSet[]) childSets.toArray(new IReadableSet[childSets.size()]);
for (int i = 0; i < children.length; i++) {
IReadableSet readableSet = children[i];
if (readableSet.isStale()) {
return true;
}
}
return false;
}
/**
* Adds a new set to this union
*
* @param s set to add to the union
*/
public void add(IReadableSet s) {
Assert.isTrue(!isDisposed());
Assert.isNotNull(s);
childSets.add(s);
// If we're maintaining a cached result...
if (refCounts != null) {
initChild(s);
}
}
/**
* Removes a set from this union
*
* @param s set to remove from the union
*/
public void remove(IReadableSet s) {
Assert.isTrue(!isDisposed());
Assert.isNotNull(s);
childSets.remove(s);
if (refCounts != null) {
deinitChild(s);
}
}
private void processRemoves(Collection added) {
ArrayList removes = new ArrayList();
for (Iterator iter = added.iterator(); iter.hasNext();) {
Object next = iter.next();
Integer refCount = (Integer) refCounts.get(next);
if (refCount != null) {
int refs = refCount.intValue();
if (refs <= 1) {
removes.add(next);
refCounts.remove(next);
} else {
refCount = new Integer(refCount.intValue() - 1);
refCounts.put(next, refCount);
}
}
}
fireRemoved(removes);
}
protected void firstListenerAdded() {
super.firstListenerAdded();
refCounts = new HashMap();
staleCount = 0;
IReadableSet[] children = (IReadableSet[]) childSets.toArray(new IReadableSet[childSets.size()]);
for (int i = 0; i < children.length; i++) {
IReadableSet next = children[i];
next.addChangeListener(childListener);
if (next.isStale()) {
staleCount++;
}
incrementRefCounts(next.toCollection());
}
}
protected void lastListenerRemoved() {
super.lastListenerRemoved();
IReadableSet[] children = (IReadableSet[]) childSets.toArray(new IReadableSet[childSets.size()]);
for (int i = 0; i < children.length; i++) {
IReadableSet next = children[i];
next.removeChangeListener(childListener);
}
refCounts = null;
}
private void initChild(IReadableSet child) {
Collection added = child.toCollection();
child.addChangeListener(childListener);
if (child.isStale()) {
processStale(true);
}
processAdds(added);
}
private void deinitChild(IReadableSet child) {
Collection removed = child.toCollection();
child.removeChangeListener(childListener);
if (child.isStale()) {
processStale(false);
}
processRemoves(removed);
}
private void processAdds(Collection added) {
ArrayList adds = incrementRefCounts(added);
fireAdded(adds);
}
private ArrayList incrementRefCounts(Collection added) {
ArrayList adds = new ArrayList();
for (Iterator iter = added.iterator(); iter.hasNext();) {
Object next = iter.next();
Integer refCount = (Integer) refCounts.get(next);
if (refCount == null) {
adds.add(next);
refCount = new Integer(1);
refCounts.put(next, refCount);
} else {
refCount = new Integer(refCount.intValue() + 1);
refCounts.put(next, refCount);
}
}
return adds;
}
protected Collection computeElements() {
// If there is no cached value, compute the union from scratch
if (refCounts == null) {
HashSet result = new HashSet();
for (Iterator iter = childSets.iterator(); iter.hasNext();) {
IReadableSet next = (IReadableSet) iter.next();
result.addAll(next.toCollection());
}
return result;
}
// Else there is a cached value. Return it.
return refCounts.keySet();
}
}