All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ca.odell.glazedlists.FilterList Maven / Gradle / Ivy

/* Glazed Lists                                                 (c) 2003-2006 */
/* http://publicobject.com/glazedlists/                      publicobject.com,*/
/*                                                     O'Dell Engineering Ltd.*/
package ca.odell.glazedlists;

// the core Glazed Lists packages
import ca.odell.glazedlists.event.ListEvent;
import ca.odell.glazedlists.impl.adt.Barcode;
import ca.odell.glazedlists.impl.adt.BarcodeIterator;
import ca.odell.glazedlists.matchers.Matcher;
import ca.odell.glazedlists.matchers.MatcherEditor;
import ca.odell.glazedlists.matchers.Matchers;

/**
 * An {@link EventList} that shows a subset of the elements of a source
 * {@link EventList}. This subset is composed of all elements of the source
 * {@link EventList} that match the filter.
 *
 * 

The filter can be static or dynamic. Changing the behaviour of the filter * will change which elements of the source list are included. * *

Warning: This class * breaks the contract required by {@link java.util.List}. See {@link EventList} * for an example. * *

Warning: This class is * thread ready but not thread safe. See {@link EventList} for an example * of thread safe code. * *

* * * * * * * *
EventList Overview
Writable:yes
Concurrency:thread ready, not thread safe
Performance:reads: O(log N), writes O(log N), filter changes O(N)
Memory:0 to 26 bytes per element
Unit Tests:N/A
Issues: * 1 * 2 * 7 * 46 * 187 * 254 * 312 *
* * @author Jesse Wilson * @author James Lemieux */ public final class FilterList extends TransformedList { /** the flag list contains Barcode.BLACK for items that match the current filter and Barcode.WHITE for others */ private Barcode flagList = new Barcode(); /** the matcher determines whether elements get filtered in or out */ private Matcher currentMatcher = Matchers.trueMatcher(); /** the editor changes the matcher and fires events */ private MatcherEditor currentEditor = null; /** listener handles changes to the matcher */ private final MatcherEditor.Listener listener = new PrivateMatcherEditorListener(); /** is this list already disposed? */ private volatile boolean disposed; /** * Creates a {@link FilterList} that includes a subset of the specified * source {@link EventList}. */ public FilterList(EventList source) { super(source); // build a list of what is filtered and what's not flagList.addBlack(0, source.size()); // listen for changes to the source list source.addListEventListener(this); } /** * Convenience constructor for creating a {@link FilterList} and setting its * {@link Matcher}. */ public FilterList(EventList source, Matcher matcher) { this(source); // if no matcher was given, we have no further initialization work if (matcher == null) return; currentMatcher = matcher; changed(); } /** * Convenience constructor for creating a {@link FilterList} and setting its * {@link MatcherEditor}. */ public FilterList(EventList source, MatcherEditor matcherEditor) { this(source); // if no matcherEditor was given, we have no further initialization work if (matcherEditor == null) return; currentEditor = matcherEditor; currentEditor.addMatcherEditorListener(listener); currentMatcher = currentEditor.getMatcher(); changed(); } /** * Set the {@link Matcher} which specifies which elements shall be filtered. * *

This will remove the current {@link Matcher} or {@link MatcherEditor} * and refilter the entire list. */ public void setMatcher(Matcher matcher) { // cancel the previous editor if(currentEditor != null) { currentEditor.removeMatcherEditorListener(listener); currentEditor = null; } if (matcher != null) changeMatcherWithLocks(currentEditor, matcher, MatcherEditor.Event.CHANGED); else changeMatcherWithLocks(currentEditor, null, MatcherEditor.Event.MATCH_ALL); } /** * Set the {@link MatcherEditor} which provides a dynamic {@link Matcher} * to determine which elements shall be filtered. * *

This will remove the current {@link Matcher} or {@link MatcherEditor} * and refilter the entire list. */ public void setMatcherEditor(MatcherEditor editor) { // cancel the previous editor if (currentEditor != null) currentEditor.removeMatcherEditorListener(listener); // use the new editor currentEditor = editor; if (currentEditor != null) { currentEditor.addMatcherEditorListener(listener); changeMatcherWithLocks(currentEditor, currentEditor.getMatcher(), MatcherEditor.Event.CHANGED); } else { changeMatcherWithLocks(currentEditor, null, MatcherEditor.Event.MATCH_ALL); } } /** @inheritDoc */ @Override public void dispose() { super.dispose(); // stop listening to the MatcherEditor if one exists if (currentEditor != null) { currentEditor.removeMatcherEditorListener(listener); } // mark this list as disposed before clearing fields // this flag is checked in #changeMatcher disposed = true; currentEditor = null; currentMatcher = null; } /** {@inheritDoc} */ @Override public final void listChanged(ListEvent listChanges) { // all of these changes to this list happen "atomically" updates.beginEvent(); // handle reordering events if(listChanges.isReordering()) { int[] sourceReorderMap = listChanges.getReorderMap(); int[] filterReorderMap = new int[flagList.blackSize()]; // adjust the flaglist & construct a reorder map to propagate Barcode previousFlagList = flagList; flagList = new Barcode(); for(int i = 0; i < sourceReorderMap.length; i++) { Object flag = previousFlagList.get(sourceReorderMap[i]); flagList.add(i, flag, 1); if(flag != Barcode.WHITE) filterReorderMap[flagList.getBlackIndex(i)] = previousFlagList.getBlackIndex(sourceReorderMap[i]); } // fire the reorder updates.reorder(filterReorderMap); // handle non-reordering events } else { // for all changes, one index at a time while(listChanges.next()) { // get the current change info int sourceIndex = listChanges.getIndex(); int changeType = listChanges.getType(); // handle delete events if(changeType == ListEvent.DELETE) { // determine if this value was already filtered out or not int filteredIndex = flagList.getBlackIndex(sourceIndex); // if this value was not filtered out, it is now so add a change if(filteredIndex != -1) { E removed = listChanges.getOldValue(); updates.elementDeleted(filteredIndex, removed); } // remove this entry from the flag list flagList.remove(sourceIndex, 1); // handle insert events } else if(changeType == ListEvent.INSERT) { // whether we should add this item E element = source.get(sourceIndex); boolean include = currentMatcher.matches(element); // if this value should be included, add a change and add the item if(include) { flagList.addBlack(sourceIndex, 1); int filteredIndex = flagList.getBlackIndex(sourceIndex); updates.elementInserted(filteredIndex, element); // if this value should not be included, just add the item } else { flagList.addWhite(sourceIndex, 1); } // handle update events } else if(changeType == ListEvent.UPDATE) { // determine if this value was already filtered out or not int filteredIndex = flagList.getBlackIndex(sourceIndex); boolean wasIncluded = filteredIndex != -1; // whether we should add this item E updated = source.get(sourceIndex); boolean include = currentMatcher.matches(updated); // if this element is being removed as a result of the change if(wasIncluded && !include) { flagList.setWhite(sourceIndex, 1); updates.elementDeleted(filteredIndex, listChanges.getOldValue()); // if this element is being added as a result of the change } else if(!wasIncluded && include) { flagList.setBlack(sourceIndex, 1); updates.elementInserted(flagList.getBlackIndex(sourceIndex), updated); // this element is still here } else if(wasIncluded && include) { updates.elementUpdated(filteredIndex, listChanges.getOldValue(), updated); } } } } // commit the changes and notify listeners updates.commitEvent(); } /** * This method acquires the write lock for the FilterList and then selects * an appropriate delegate method to perform the correct work for each of * the possible changeTypes. */ private void changeMatcherWithLocks(MatcherEditor matcherEditor, Matcher matcher, int changeType) { getReadWriteLock().writeLock().lock(); try { changeMatcher(matcherEditor, matcher, changeType); } finally { getReadWriteLock().writeLock().unlock(); } } /** * This method selects an appropriate delegate method to perform the * correct work for each of the possible changeTypes. This * method does NOT acquire any locks and is thus used * during initialization of FilterList. */ private void changeMatcher(MatcherEditor matcherEditor, Matcher matcher, int changeType) { // first check if this list is already disposed if (!disposed) { // ensure the MatcherEvent is from OUR MatcherEditor if (currentEditor != matcherEditor) throw new IllegalStateException(); switch (changeType) { case MatcherEditor.Event.CONSTRAINED: currentMatcher = matcher; this.constrained(); break; case MatcherEditor.Event.RELAXED: currentMatcher = matcher; this.relaxed(); break; case MatcherEditor.Event.CHANGED: currentMatcher = matcher; this.changed(); break; case MatcherEditor.Event.MATCH_ALL: currentMatcher = Matchers.trueMatcher(); this.matchAll(); break; case MatcherEditor.Event.MATCH_NONE: currentMatcher = Matchers.falseMatcher(); this.matchNone(); break; } } } /** * Handles a constraining of the filter to a degree that guarantees no * values can be matched. That is, the filter list will act as a total * filter and not match any of the elements of the wrapped source list. */ private void matchNone() { // all of these changes to this list happen "atomically" updates.beginEvent(); // fire all the elements in the list as deleted for(int i = 0; i < size(); i++) { updates.elementDeleted(0, get(i)); } // reset the flaglist to all white (which matches nothing) flagList.clear(); flagList.addWhite(0, source.size()); // commit the changes and notify listeners updates.commitEvent(); } /** * Handles a clearing of the filter. That is, the filter list will act as * a passthrough and not discriminate any of the elements of the wrapped * source list. */ private void matchAll() { // all of these changes to this list happen "atomically" updates.beginEvent(); // for all filtered items, add them. // this code exploits the fact that all flags before // the current index are all conceptually black. we don't change // the flag to black immediately as a performance optimization // because the current implementation of barcode is faster for // batch operations. The call to i.getIndex() is exploiting the // fact that i.getIndex() == i.blackIndex() when all flags before // are conceptually black. Otherwise we would need to change flags // to black as we go so that flag offsets are correct for(BarcodeIterator i = flagList.iterator(); i.hasNextWhite();) { i.nextWhite(); int index = i.getIndex(); updates.elementInserted(index, source.get(index)); } flagList.clear(); flagList.addBlack(0, source.size()); // commit the changes and notify listeners updates.commitEvent(); } /** * Handles a relaxing or widening of the filter. This may change the * contents of this {@link EventList} as filtered elements are unfiltered * due to the relaxation of the filter. */ private void relaxed() { // all of these changes to this list happen "atomically" updates.beginEvent(); // for all filtered items, see what the change is for(BarcodeIterator i = flagList.iterator(); i.hasNextWhite();) { i.nextWhite(); E element = source.get(i.getIndex()); if(currentMatcher.matches(element)) { updates.elementInserted(i.setBlack(), element); } } // commit the changes and notify listeners updates.commitEvent(); } /** * Handles a constraining or narrowing of the filter. This may change the * contents of this {@link EventList} as elements are further filtered due * to the constraining of the filter. */ private void constrained() { // all of these changes to this list happen "atomically" updates.beginEvent(); // for all unfiltered items, see what the change is for(BarcodeIterator i = flagList.iterator(); i.hasNextBlack();) { i.nextBlack(); E value = source.get(i.getIndex()); if(!currentMatcher.matches(value)) { int blackIndex = i.getBlackIndex(); i.setWhite(); updates.elementDeleted(blackIndex, value); } } // commit the changes and notify listeners updates.commitEvent(); } /** * Handles changes to the behavior of the filter. This may change the contents * of this {@link EventList} as elements are filtered and unfiltered. */ private void changed() { // all of these changes to this list happen "atomically" updates.beginEvent(); // for all source items, see what the change is for(BarcodeIterator i = flagList.iterator();i.hasNext();) { i.next(); // determine if this value was already filtered out or not int filteredIndex = i.getBlackIndex(); boolean wasIncluded = filteredIndex != -1; // whether we should add this item E value = source.get(i.getIndex()); boolean include = currentMatcher.matches(value); // this element is being removed as a result of the change if(wasIncluded && !include) { i.setWhite(); updates.elementDeleted(filteredIndex, value); // this element is being added as a result of the change } else if(!wasIncluded && include) { updates.elementInserted(i.setBlack(), value); } } // commit the changes and notify listeners updates.commitEvent(); } /** * Listens to changes from the current {@link MatcherEditor} and handles them. */ private class PrivateMatcherEditorListener implements MatcherEditor.Listener { /** * This method changes the current Matcher controlling the filtering on * the FilterList. It does so in a thread-safe manner by acquiring the * write lock. * * @param matcherEvent a MatcherEvent describing the change in the * Matcher produced by the MatcherEditor */ public void changedMatcher(MatcherEditor.Event matcherEvent) { final MatcherEditor matcherEditor = matcherEvent.getMatcherEditor(); final Matcher matcher = matcherEvent.getMatcher(); final int changeType = matcherEvent.getType(); changeMatcherWithLocks(matcherEditor, matcher, changeType); } } /** {@inheritDoc} */ @Override public final int size() { return flagList.blackSize(); } /** {@inheritDoc} */ @Override protected final int getSourceIndex(int mutationIndex) { return flagList.getIndex(mutationIndex, Barcode.BLACK); } /** {@inheritDoc} */ @Override protected boolean isWritable() { return true; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy