| /******************************************************************************* |
| * Copyright (c) 2007 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 |
| extends CollectionValueModelWrapper |
| { |
| /** |
| * This is the (optional) user-supplied object that transforms |
| * the items in the wrapped collection to collection value models. |
| */ |
| private final Transformer 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 components; |
| |
| /** |
| * Cache of the collections corresponding to the component |
| * collection value models above; keyed by the component |
| * collection value models. |
| */ |
| private final IdentityHashMap 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 collectionHolder) { |
| this(collectionHolder, Transformer.Disabled.instance()); |
| } |
| |
| /** |
| * Construct a collection value model with the specified wrapped |
| * collection value model and transformer. |
| */ |
| public CompositeCollectionValueModel(CollectionValueModel collectionHolder, Transformer transformer) { |
| super(collectionHolder); |
| this.transformer = transformer; |
| this.components = new IdentityHashMap(); |
| this.collections = new IdentityHashMap(); |
| 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 listHolder) { |
| this(new ListCollectionValueModelAdapter(listHolder)); |
| } |
| |
| /** |
| * Construct a collection value model with the specified wrapped |
| * list value model and transformer. |
| */ |
| public CompositeCollectionValueModel(ListValueModel listHolder, Transformer transformer) { |
| this(new ListCollectionValueModelAdapter(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 iterator() { |
| return new CompositeIterator(this.buildCollectionsIterators()); |
| } |
| |
| protected Iterator buildCollectionsIterators() { |
| return new TransformationIterator(this.collections.values().iterator()) { |
| protected Object transform(Object next) { |
| return ((ArrayList) 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((Iterator) this.collectionHolder.iterator()); |
| } |
| |
| @Override |
| protected void disengageModel() { |
| super.disengageModel(); |
| // stop listening to the components... |
| for (Iterator stream = this.components.values().iterator(); stream.hasNext(); ) { |
| ((CollectionValueModel) stream.next()).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(e.items()); |
| } |
| |
| /** |
| * Transform the specified sources to collection value models |
| * and add their items to our cache. |
| */ |
| protected void addComponentSources(Iterator 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(Object source) { |
| CollectionValueModel component = this.transform(source); |
| if (this.components.put(source, component) != null) { |
| throw new IllegalStateException("duplicate component: " + source); |
| } |
| component.addCollectionChangeListener(CollectionValueModel.VALUES, this.componentListener); |
| ArrayList componentCollection = new ArrayList(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(e.items()); |
| } |
| |
| /** |
| * Remove the items corresponding to the specified sources |
| * from our cache. |
| */ |
| protected void removeComponentSources(Iterator sources) { |
| while (sources.hasNext()) { |
| this.removeComponentSource(sources.next()); |
| } |
| } |
| |
| /** |
| * Remove the items corresponding to the specified source |
| * from our cache. |
| */ |
| protected void removeComponentSource(Object source) { |
| CollectionValueModel component = (CollectionValueModel) this.components.remove(source); |
| if (component == null) { |
| throw new IllegalStateException("missing component: " + source); |
| } |
| component.removeCollectionChangeListener(CollectionValueModel.VALUES, this.componentListener); |
| ArrayList componentCollection = (ArrayList) 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(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(this.components.keySet()).iterator()); |
| this.addComponentSources((Iterator) 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 getComponentCollection(CollectionValueModel collectionValueModel) { |
| return (ArrayList) 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 transform(Object value) { |
| return (CollectionValueModel) this.transformer.transform(value); |
| } |
| |
| /** |
| * One of the component collections had items added; |
| * synchronize our caches. |
| */ |
| protected void componentItemsAdded(CollectionChangeEvent e) { |
| this.addComponentItems(e.items(), e.itemsSize(), (CollectionValueModel) e.getSource()); |
| } |
| |
| /** |
| * Update our cache. |
| */ |
| protected void addComponentItems(Iterator items, int itemsSize, CollectionValueModel cvm) { |
| this.addComponentItems(items, itemsSize, this.getComponentCollection(cvm)); |
| } |
| |
| /** |
| * Update our cache. |
| */ |
| protected void addComponentItems(CollectionValueModel itemsHolder, ArrayList componentCollection) { |
| this.addComponentItems((Iterator) itemsHolder.iterator(), itemsHolder.size(), componentCollection); |
| } |
| |
| /** |
| * Update our size and collection cache. |
| */ |
| protected void addComponentItems(Iterator items, int itemsSize, ArrayList 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(e.items(), e.itemsSize(), (CollectionValueModel) e.getSource()); |
| } |
| |
| /** |
| * Update our size and collection cache. |
| */ |
| protected void removeComponentItems(Iterator items, int itemsSize, CollectionValueModel cvm) { |
| this.removeComponentItems(items, itemsSize, this.getComponentCollection(cvm)); |
| } |
| |
| /** |
| * Update our size and collection cache. |
| */ |
| protected void clearComponentItems(ArrayList items) { |
| // clone the collection so we don't eat our own tail |
| this.removeComponentItems(((ArrayList) items.clone()).iterator(), items.size(), items); |
| } |
| |
| /** |
| * Update our size and collection cache. |
| */ |
| protected void removeComponentItems(Iterator items, int itemsSize, ArrayList 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) { |
| CollectionValueModel component = (CollectionValueModel) e.getSource(); |
| ArrayList items = this.getComponentCollection(component); |
| 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 component = (CollectionValueModel) e.getSource(); |
| ArrayList items = this.getComponentCollection(component); |
| this.clearComponentItems(items); |
| this.addComponentItems(component, items); |
| } |
| |
| } |