blob: 8dc865a0f3bb9aae5c2713fa42719acab63701ab [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.Collection;
import java.util.Iterator;
import org.eclipse.jpt.utility.Filter;
import org.eclipse.jpt.utility.internal.CollectionTools;
import org.eclipse.jpt.utility.internal.iterators.FilteringIterator;
import org.eclipse.jpt.utility.internal.iterators.ReadOnlyIterator;
import org.eclipse.jpt.utility.model.event.CollectionChangeEvent;
import org.eclipse.jpt.utility.model.value.CollectionValueModel;
import org.eclipse.jpt.utility.model.value.ListValueModel;
/**
* 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>#iterator()</code>.
* <p>
* The filter can be changed at any time; allowing the same
* adapter to be used with different filter criteria (e.g. when the user
* wants to view a list of .java files).
* <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<E>
extends CollectionValueModelWrapper<E>
implements CollectionValueModel<E>
{
/** This filters the items in the nested collection. */
private Filter<E> filter;
/** Cache the items that were accepted by the filter */
private final Collection<E> filteredItems;
// ********** constructors **********
/**
* Construct a collection value model with the specified wrapped
* collection value model and a filter that simply accepts every object.
*/
public FilteringCollectionValueModel(CollectionValueModel<? extends E> collectionHolder) {
this(collectionHolder, Filter.Null.<E>instance());
}
/**
* Construct a collection value model with the specified wrapped
* collection value model and filter.
*/
public FilteringCollectionValueModel(CollectionValueModel<? extends E> collectionHolder, Filter<E> filter) {
super(collectionHolder);
this.filter = filter;
this.filteredItems = new ArrayList<E>();
}
/**
* Construct a collection value model with the specified wrapped
* list value model and a filter that simply accepts every object.
*/
public FilteringCollectionValueModel(ListValueModel<E> listHolder) {
this(new ListCollectionValueModelAdapter<E>(listHolder));
}
/**
* Construct a collection value model with the specified wrapped
* list value model and filter.
*/
public FilteringCollectionValueModel(ListValueModel<E> listHolder, Filter<E> filter) {
this(new ListCollectionValueModelAdapter<E>(listHolder), filter);
}
// ********** CollectionValueModel implementation **********
public Iterator<E> iterator() {
return new ReadOnlyIterator<E>(this.filteredItems);
}
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
CollectionTools.addAll(this.filteredItems, this.filter(this.collectionHolder.iterator()));
}
@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 event) {
// filter the values before propagating the change event
this.addItemsToCollection(this.filter(this.items(event)), this.filteredItems, VALUES);
}
@Override
protected void itemsRemoved(CollectionChangeEvent event) {
// 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(event.items(), this.filteredItems, VALUES);
}
@Override
protected void collectionCleared(CollectionChangeEvent event) {
this.clearCollection(this.filteredItems, VALUES);
}
@Override
protected void collectionChanged(CollectionChangeEvent event) {
this.rebuildFilteredItems();
}
// ********** miscellaneous **********
/**
* Change the filter and rebuild the collection.
*/
public void setFilter(Filter<E> filter) {
this.filter = filter;
this.rebuildFilteredItems();
}
/**
* Return an iterator that filters the specified iterator.
*/
protected Iterator<E> filter(Iterator<? extends E> items) {
return new FilteringIterator<E, E>(items, this.filter);
}
/**
* Synchronize our cache with the wrapped collection.
*/
protected void rebuildFilteredItems() {
this.filteredItems.clear();
CollectionTools.addAll(this.filteredItems, this.filter(this.collectionHolder.iterator()));
this.fireCollectionChanged(VALUES);
}
}