blob: 29a0f0fefff47e8afc7fd6a4118a0b6e921a6163 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2007, 2008 Oracle. 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:
* Oracle - initial API and implementation
******************************************************************************/
package org.eclipse.jpt.utility.internal.model.value;
import java.util.ArrayList;
import java.util.IdentityHashMap;
import java.util.Iterator;
import org.eclipse.jpt.utility.internal.Transformer;
import org.eclipse.jpt.utility.internal.iterators.CompositeIterator;
import org.eclipse.jpt.utility.internal.iterators.TransformationIterator;
import org.eclipse.jpt.utility.internal.model.event.CollectionChangeEvent;
import org.eclipse.jpt.utility.internal.model.listener.CollectionChangeListener;
/**
* A <code>CompositeCollectionValueModel</code> wraps another
* <code>CollectionValueModel</code> and uses a <code>Transformer</code>
* to convert each item in the wrapped collection to yet another
* <code>CollectionValueModel</code>. This composite collection contains
* the combined items from all these component collections.
*
* NB: The wrapped collection must be an "identity set" that does not
* contain the same item twice or this class will throw an exception.
*
* Terminology:
* - sources - the items in the wrapped collection value model; these
* are converted into components by the transformer
* - components - the component collection value models that are combined
* by this composite collection value model
*/
public class CompositeCollectionValueModel<T, E>
extends CollectionValueModelWrapper<T>
implements CollectionValueModel<E>
{
/**
* This is the (optional) user-supplied object that transforms
* the items in the wrapped collection to collection value models.
*/
private final Transformer<T, CollectionValueModel<E>> transformer;
/**
* Cache of the component collection value models that
* were generated by the transformer; keyed by the item
* in the wrapped collection that was passed to the transformer.
*/
private final IdentityHashMap<T, CollectionValueModel<E>> components;
/**
* Cache of the collections corresponding to the component
* collection value models above; keyed by the component
* collection value models.
*/
private final IdentityHashMap<CollectionValueModel<E>, ArrayList<E>> collections;
/** Listener that listens to all the component collection value models. */
private final CollectionChangeListener componentListener;
/** Cache the size of the composite collection. */
private int size;
// ********** constructors **********
/**
* Construct a collection value model with the specified wrapped
* collection value model. Use this constructor if you want to override the
* <code>transform(Object)</code> method instead of building a
* <code>Transformer</code>.
*/
public CompositeCollectionValueModel(CollectionValueModel<? extends T> collectionHolder) {
this(collectionHolder, Transformer.Disabled.<T, CollectionValueModel<E>>instance());
}
/**
* Construct a collection value model with the specified wrapped
* collection value model and transformer.
*/
public CompositeCollectionValueModel(CollectionValueModel<? extends T> collectionHolder, Transformer<T, CollectionValueModel<E>> transformer) {
super(collectionHolder);
this.transformer = transformer;
this.components = new IdentityHashMap<T, CollectionValueModel<E>>();
this.collections = new IdentityHashMap<CollectionValueModel<E>, ArrayList<E>>();
this.componentListener = this.buildComponentListener();
this.size = 0;
}
/**
* Construct a collection value model with the specified wrapped
* list value model. Use this constructor if you want to override the
* <code>transform(Object)</code> method instead of building a
* <code>Transformer</code>.
*/
public CompositeCollectionValueModel(ListValueModel<? extends T> listHolder) {
this(new ListCollectionValueModelAdapter<T>(listHolder));
}
/**
* Construct a collection value model with the specified wrapped
* list value model and transformer.
*/
public CompositeCollectionValueModel(ListValueModel<? extends T> listHolder, Transformer<T, CollectionValueModel<E>> transformer) {
this(new ListCollectionValueModelAdapter<T>(listHolder), transformer);
}
// ********** initialization **********
protected CollectionChangeListener buildComponentListener() {
return new CollectionChangeListener() {
public void itemsAdded(CollectionChangeEvent e) {
CompositeCollectionValueModel.this.componentItemsAdded(e);
}
public void itemsRemoved(CollectionChangeEvent e) {
CompositeCollectionValueModel.this.componentItemsRemoved(e);
}
public void collectionCleared(CollectionChangeEvent e) {
CompositeCollectionValueModel.this.componentCollectionCleared(e);
}
public void collectionChanged(CollectionChangeEvent e) {
CompositeCollectionValueModel.this.componentCollectionChanged(e);
}
@Override
public String toString() {
return "component listener";
}
};
}
// ********** CollectionValueModel implementation **********
public Iterator<E> iterator() {
return new CompositeIterator<E>(this.buildCollectionsIterators());
}
protected Iterator<Iterator<E>> buildCollectionsIterators() {
return new TransformationIterator<ArrayList<E>, Iterator<E>>(this.collections.values().iterator()) {
@Override
protected Iterator<E> transform(ArrayList<E> next) {
return next.iterator();
}
};
}
public int size() {
return this.size;
}
// ********** CollectionValueModelWrapper overrides/implementation **********
@Override
protected void engageModel() {
super.engageModel();
// synch our cache *after* we start listening to the wrapped collection,
// since its value might change when a listener is added;
// the following will trigger the firing of a number of unnecessary events
// (since we don't have any listeners yet),
// but it reduces the amount of duplicate code
this.addComponentSources(this.collectionHolder.iterator());
}
@Override
protected void disengageModel() {
super.disengageModel();
// stop listening to the components...
for (CollectionValueModel<E> cvm : this.components.values()) {
cvm.removeCollectionChangeListener(CollectionValueModel.VALUES, this.componentListener);
}
// ...and clear the cache
this.components.clear();
this.collections.clear();
this.size = 0;
}
/**
* Some component sources were added;
* add their corresponding items to our cache.
*/
@Override
protected void itemsAdded(CollectionChangeEvent e) {
this.addComponentSources(this.items(e));
}
/**
* Transform the specified sources to collection value models
* and add their items to our cache.
*/
protected void addComponentSources(Iterator<? extends T> sources) {
while (sources.hasNext()) {
this.addComponentSource(sources.next());
}
}
/**
* Transform the specified source to a collection value model
* and add its items to our cache.
*/
protected void addComponentSource(T source) {
CollectionValueModel<E> component = this.transform(source);
if (this.components.put(source, component) != null) {
throw new IllegalStateException("duplicate component: " + source);
}
component.addCollectionChangeListener(CollectionValueModel.VALUES, this.componentListener);
ArrayList<E> componentCollection = new ArrayList<E>(component.size());
if (this.collections.put(component, componentCollection) != null) {
throw new IllegalStateException("duplicate collection: " + source);
}
this.addComponentItems(component, componentCollection);
}
/**
* Some component sources were removed;
* remove their corresponding items from our cache.
*/
@Override
protected void itemsRemoved(CollectionChangeEvent e) {
this.removeComponentSources(this.items(e));
}
/**
* Remove the items corresponding to the specified sources
* from our cache.
*/
protected void removeComponentSources(Iterator<T> sources) {
while (sources.hasNext()) {
this.removeComponentSource(sources.next());
}
}
/**
* Remove the items corresponding to the specified source
* from our cache.
*/
protected void removeComponentSource(T source) {
CollectionValueModel<E> component = this.components.remove(source);
if (component == null) {
throw new IllegalStateException("missing component: " + source);
}
component.removeCollectionChangeListener(CollectionValueModel.VALUES, this.componentListener);
ArrayList<E> componentCollection = this.collections.remove(component);
if (componentCollection == null) {
throw new IllegalStateException("missing collection: " + source);
}
this.clearComponentItems(componentCollection);
}
/**
* The component sources cleared;
* clear our cache.
*/
@Override
protected void collectionCleared(CollectionChangeEvent e) {
// copy the keys so we don't eat our own tail
this.removeComponentSources(new ArrayList<T>(this.components.keySet()).iterator());
}
/**
* The component sources changed;
* rebuild our cache.
*/
@Override
protected void collectionChanged(CollectionChangeEvent e) {
// copy the keys so we don't eat our own tail
this.removeComponentSources(new ArrayList<T>(this.components.keySet()).iterator());
this.addComponentSources(this.collectionHolder.iterator());
}
// ********** queries **********
/**
* Return the cached collection for the specified component model.
* Cast to ArrayList so we can use ArrayList-specific methods
* (e.g. #clone() and #ensureCapacity()).
*/
protected ArrayList<E> componentCollection(CollectionValueModel<E> collectionValueModel) {
return this.collections.get(collectionValueModel);
}
// ********** behavior **********
/**
* Transform the specified object into a collection value model.
* <p>
* This method can be overridden by a subclass as an
* alternative to building a <code>Transformer</code>.
*/
protected CollectionValueModel<E> transform(T value) {
return this.transformer.transform(value);
}
/**
* One of the component collections had items added;
* synchronize our caches.
*/
protected void componentItemsAdded(CollectionChangeEvent e) {
this.addComponentItems(this.componentItems(e), e.itemsSize(), this.componentCVM(e));
}
/**
* Update our cache.
*/
protected void addComponentItems(Iterator<E> items, int itemsSize, CollectionValueModel<E> cvm) {
this.addComponentItems(items, itemsSize, this.componentCollection(cvm));
}
/**
* Update our cache.
*/
protected void addComponentItems(CollectionValueModel<E> itemsHolder, ArrayList<E> componentCollection) {
this.addComponentItems(itemsHolder.iterator(), itemsHolder.size(), componentCollection);
}
/**
* Update our size and collection cache.
*/
protected void addComponentItems(Iterator<E> items, int itemsSize, ArrayList<E> componentCollection) {
this.size += itemsSize;
componentCollection.ensureCapacity(componentCollection.size() + itemsSize);
this.addItemsToCollection(items, componentCollection, CollectionValueModel.VALUES);
}
/**
* One of the component collections had items removed;
* synchronize our caches.
*/
protected void componentItemsRemoved(CollectionChangeEvent e) {
this.removeComponentItems(this.componentItems(e), e.itemsSize(), this.componentCVM(e));
}
/**
* Update our size and collection cache.
*/
protected void removeComponentItems(Iterator<E> items, int itemsSize, CollectionValueModel<E> cvm) {
this.removeComponentItems(items, itemsSize, this.componentCollection(cvm));
}
/**
* Update our size and collection cache.
*/
protected void clearComponentItems(ArrayList<E> items) {
// clone the collection so we don't eat our own tail
@SuppressWarnings("unchecked") ArrayList<E> clone = (ArrayList<E>) items.clone();
this.removeComponentItems(clone.iterator(), items.size(), items);
}
/**
* Update our size and collection cache.
*/
protected void removeComponentItems(Iterator<E> items, int itemsSize, ArrayList<E> componentCollection) {
this.size -= itemsSize;
this.removeItemsFromCollection(items, componentCollection, CollectionValueModel.VALUES);
}
/**
* One of the component collections was cleared;
* synchronize our caches by clearing out the appropriate
* collection.
*/
protected void componentCollectionCleared(CollectionChangeEvent e) {
ArrayList<E> items = this.componentCollection(this.componentCVM(e));
this.clearComponentItems(items);
}
/**
* One of the component collections changed;
* synchronize our caches by clearing out the appropriate
* collection and then rebuilding it.
*/
protected void componentCollectionChanged(CollectionChangeEvent e) {
CollectionValueModel<E> componentCVM = this.componentCVM(e);
ArrayList<E> items = this.componentCollection(componentCVM);
this.clearComponentItems(items);
this.addComponentItems(componentCVM, items);
}
// minimize suppressed warnings
@SuppressWarnings("unchecked")
protected Iterator<E> componentItems(CollectionChangeEvent e) {
return (Iterator<E>) e.items();
}
// minimize suppressed warnings
@SuppressWarnings("unchecked")
protected CollectionValueModel<E> componentCVM(CollectionChangeEvent e) {
return (CollectionValueModel<E>) e.getSource();
}
}