| /******************************************************************************* |
| * 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(); |
| } |
| |
| } |