| /******************************************************************************* |
| * 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.Collection; |
| import java.util.Iterator; |
| |
| import org.eclipse.jpt.utility.internal.CollectionTools; |
| import org.eclipse.jpt.utility.internal.Filter; |
| import org.eclipse.jpt.utility.internal.iterators.FilteringIterator; |
| import org.eclipse.jpt.utility.internal.model.event.CollectionChangeEvent; |
| |
| /** |
| * A <code>FilteringCollectionValueModel</code> wraps another |
| * <code>CollectionValueModel</code> and uses a <code>Filter</code> |
| * to determine which items in the collection are returned by calls |
| * to <code>value()</code>. |
| * <p> |
| * As an alternative to building a <code>Filter</code>, a subclass |
| * of <code>FilteringCollectionValueModel</code> can override the |
| * <code>accept(Object)</code> method. |
| * <p> |
| * NB: If the objects in the "filtered" collection can change in such a way |
| * that they should be removed from the "filtered" collection, you will |
| * need to wrap the original collection in an ItemAspectListValueModelAdapter. |
| * For example, if the filter only "accepts" items whose names begin |
| * with "X" and the names of the items can change, you will need to |
| * wrap the original list of unfiltered items with an |
| * ItemPropertyListValueModelAdapter that listens for changes to each |
| * item's name and fires the appropriate event whenever an item's name |
| * changes. The event will cause this wrapper to re-filter the changed |
| * item and add or remove it from the "filtered" collection as appropriate. |
| */ |
| public class FilteringCollectionValueModel |
| extends CollectionValueModelWrapper |
| { |
| /** This filters the items in the nested collection. */ |
| private final Filter filter; |
| |
| /** This filters the items in the nested collection. */ |
| private final Filter localFilter; |
| |
| /** Cache the items that were accepted by the filter */ |
| private final Collection filteredItems; |
| |
| |
| // ********** constructors ********** |
| |
| /** |
| * Construct a collection value model with the specified wrapped |
| * collection value model and a disabled filter. |
| * Use this constructor if you want to override the |
| * <code>accept(Object)</code> method |
| * instead of building a <code>Filter</code>. |
| */ |
| public FilteringCollectionValueModel(CollectionValueModel collectionHolder) { |
| this(collectionHolder, Filter.Disabled.instance()); |
| } |
| |
| /** |
| * Construct a collection value model with the specified wrapped |
| * collection value model and filter. |
| */ |
| public FilteringCollectionValueModel(CollectionValueModel collectionHolder, Filter filter) { |
| super(collectionHolder); |
| this.filter = filter; |
| this.localFilter = this.buildLocalFilter(); |
| this.filteredItems = new ArrayList(); |
| } |
| |
| /** |
| * Construct a collection value model with the specified wrapped |
| * list value model and a filter that simply accepts every object. |
| * Use this constructor if you want to override the |
| * <code>accept(Object)</code> method |
| * instead of building a <code>Filter</code>. |
| */ |
| public FilteringCollectionValueModel(ListValueModel listHolder) { |
| this(new ListCollectionValueModelAdapter(listHolder)); |
| } |
| |
| /** |
| * Construct a collection value model with the specified wrapped |
| * list value model and filter. |
| */ |
| public FilteringCollectionValueModel(ListValueModel listHolder, Filter filter) { |
| this(new ListCollectionValueModelAdapter(listHolder), filter); |
| } |
| |
| |
| // ********** initialization ********** |
| |
| /** |
| * Implement the filter by calling back to the collection |
| * value model. This allows us to keep the method |
| * #accept(Object) protected. |
| */ |
| protected Filter buildLocalFilter() { |
| return new Filter() { |
| public boolean accept(Object o) { |
| return FilteringCollectionValueModel.this.accept(o); |
| } |
| }; |
| } |
| |
| |
| // ********** CollectionValueModel implementation ********** |
| |
| public Iterator iterator() { |
| return this.filteredItems.iterator(); |
| } |
| |
| public int size() { |
| return this.filteredItems.size(); |
| } |
| |
| |
| // ********** CollectionValueModelWrapper overrides/implementation ********** |
| |
| @Override |
| protected void engageModel() { |
| super.engageModel(); |
| // synch our cache *after* we start listening to the nested collection, |
| // since its value might change when a listener is added |
| this.synchFilteredItems(); |
| } |
| |
| @Override |
| protected void disengageModel() { |
| super.disengageModel(); |
| // clear out the cache when we are not listening to the nested collection |
| this.filteredItems.clear(); |
| } |
| |
| @Override |
| protected void itemsAdded(CollectionChangeEvent e) { |
| // filter the values before propagating the change event |
| this.addItemsToCollection(this.filter(e.items()), this.filteredItems, VALUES); |
| } |
| |
| @Override |
| protected void itemsRemoved(CollectionChangeEvent e) { |
| // do *not* filter the values, because they may no longer be |
| // "accepted" and that might be why they were removed in the first place; |
| // anyway, any extraneous items are harmless |
| this.removeItemsFromCollection(e.items(), this.filteredItems, VALUES); |
| } |
| |
| @Override |
| protected void collectionCleared(CollectionChangeEvent e) { |
| this.clearCollection(this.filteredItems, VALUES); |
| } |
| |
| @Override |
| protected void collectionChanged(CollectionChangeEvent e) { |
| this.synchFilteredItems(); |
| this.fireCollectionChanged(VALUES); |
| } |
| |
| |
| // ********** queries ********** |
| |
| /** |
| * Return whether the <code>FilteringCollectionValueModel</code> should |
| * include the specified value in the iterator returned from a call to the |
| * <code>value()</code> method; the value came |
| * from the nested collection value model. |
| * <p> |
| * This method can be overridden by a subclass as an |
| * alternative to building a <code>Filter</code>. |
| */ |
| protected boolean accept(Object value) { |
| return this.filter.accept(value); |
| } |
| |
| /** |
| * Return an iterator that filters the specified iterator. |
| */ |
| protected Iterator filter(Iterator items) { |
| return new FilteringIterator(items, this.localFilter); |
| } |
| |
| |
| // ********** behavior ********** |
| |
| /** |
| * Synchronize our cache with the wrapped collection. |
| */ |
| protected void synchFilteredItems() { |
| this.filteredItems.clear(); |
| CollectionTools.addAll(this.filteredItems, this.filter(this.collectionHolder.iterator())); |
| } |
| |
| } |