/******************************************************************************* * Copyright (c) 1998, 2015 Oracle and/or its affiliates. All rights reserved. * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0 * which accompanies this distribution. * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html * and the Eclipse Distribution License is available at * http://www.eclipse.org/org/documents/edl-v10.php. * * Contributors: * Oracle - initial API and implementation from Oracle TopLink ******************************************************************************/ package org.eclipse.persistence.tools.workbench.uitools.app; import java.util.ArrayList; import java.util.IdentityHashMap; import java.util.Iterator; import org.eclipse.persistence.tools.workbench.utility.Transformer; import org.eclipse.persistence.tools.workbench.utility.events.CollectionChangeEvent; import org.eclipse.persistence.tools.workbench.utility.events.CollectionChangeListener; import org.eclipse.persistence.tools.workbench.utility.iterators.CompositeIterator; import org.eclipse.persistence.tools.workbench.utility.iterators.TransformationIterator; /** * 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 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 IdentityHashMap components; /** * Cache of the collections corresponding to the component * collection value models above; keyed by the component * collection value models. */ private IdentityHashMap collections; /** Listener that listens to all the component collection value models. */ private CollectionChangeListener componentListener; /** Cache the size of the composite collection. */ private int size; /** The transformer used when the #transform(Object) method is overridden. */ private static final Transformer DISABLED_TRANSFORMER = new Transformer() { public Object transform(Object o) { throw new IllegalStateException("CompositeCollectionValueModel.transform(Object) was not implemented."); } public String toString() { return "Disabled Transformer"; } }; // ********** 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, DISABLED_TRANSFORMER); } /** * 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; } /** * 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 void initialize() { super.initialize(); this.components = new IdentityHashMap(); this.collections = new IdentityHashMap(); this.componentListener = this.buildComponentListener(); this.size = 0; } 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 collectionChanged(CollectionChangeEvent e) { CompositeCollectionValueModel.this.componentCollectionChanged(e); } public String toString() { return "component listener"; } }; } // ********** ValueModel implementation ********** /** * @see ValueModel#getValue() */ public Object getValue() { return new CompositeIterator(this.buildCollectionsIterators()); } protected Iterator buildCollectionsIterators() { return new TransformationIterator(this.collections.values().iterator()) { protected Object transform(Object next) { return ((ArrayList) next).iterator(); } }; } // ********** CollectionValueModel implementation ********** /** * @see CollectionValueModel#size() */ public int size() { return this.size; } // ********** CollectionValueModelWrapper overrides/implementation ********** /** * @see CollectionValueModelWrapper#engageModel() */ 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.getValue()); } /** * @see CollectionValueModelWrapper#disengageModel() */ protected void disengageModel() { super.disengageModel(); // stop listening to the components... for (Iterator stream = this.components.values().iterator(); stream.hasNext(); ) { ((CollectionValueModel) stream.next()).removeCollectionChangeListener(ValueModel.VALUE, 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. * @see CollectionValueModelWrapper#itemsAdded(org.eclipse.persistence.tools.workbench.utility.events.CollectionChangeEvent) */ 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(ValueModel.VALUE, 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. * @see CollectionValueModelWrapper#itemsRemoved(org.eclipse.persistence.tools.workbench.utility.events.CollectionChangeEvent) */ 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(ValueModel.VALUE, this.componentListener); ArrayList componentCollection = (ArrayList) this.collections.remove(component); if (componentCollection == null) { throw new IllegalStateException("missing collection: " + source); } this.clearComponentItems(componentCollection); } /** * The component sources changed; * rebuild our cache. * @see CollectionValueModelWrapper#collectionChanged(org.eclipse.persistence.tools.workbench.utility.events.CollectionChangeEvent) */ 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.getValue()); } // ********** 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.size(), (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.getValue(), 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, VALUE); } /** * One of the component collections had items removed; * synchronize our caches. */ protected void componentItemsRemoved(CollectionChangeEvent e) { this.removeComponentItems(e.items(), e.size(), (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, VALUE); } /** * 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); } }