source.ca.odell.glazedlists.UniqueList Maven / Gradle / Ivy
Show all versions of glazedlists_java15 Show documentation
/* Glazed Lists (c) 2003-2006 */
/* http://publicobject.com/glazedlists/ publicobject.com,*/
/* O'Dell Engineering Ltd.*/
package ca.odell.glazedlists;
import ca.odell.glazedlists.event.ListEvent;
import ca.odell.glazedlists.impl.Grouper;
import ca.odell.glazedlists.impl.adt.BarcodeIterator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
/**
* An {@link EventList} that shows the unique elements from its source
* {@link EventList}. For example, the source list {A, A, B, C, C, C, D} would
* be simplified to {A, B, C, D} by this UniqueList.
*
* Warning: This class breaks
* the contract required by {@link 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:
* Memory: N/A
* Unit Tests: N/A
* Issues:
* 27
* 34
* 35
* 45
* 46
* 55
* 58
* 114
*
*
*
* @author Kevin Maltby
* @author James Lemieux
* @author Jesse Wilson
*/
public final class UniqueList extends TransformedList {
/** the grouping service manages collapsing out duplicates */
private final Grouper grouper;
/**
* Creates a {@link UniqueList} that determines uniqueness via the
* {@link Comparable} interface. All elements of the source {@link EventList}
* must implement {@link Comparable}.
*
* @param source the {@link EventList} containing duplicates to remove
*/
public static > UniqueList create(EventList source) {
return new UniqueList(source);
}
/**
* Creates a {@link UniqueList} that determines uniqueness via the
* {@link Comparable} interface. All elements of the source {@link EventList}
* must implement {@link Comparable}.
* Usage of factory method {@link #create(EventList)} is preferable.
*
* @param source the {@link EventList} containing duplicates to remove
*/
public UniqueList(EventList source) {
this(source, (Comparator) GlazedLists.comparableComparator());
}
/**
* Creates a {@link UniqueList} that determines uniqueness using the
* specified {@link Comparator}.
*
* @param source the {@link EventList} containing duplicates to remove
* @param comparator the {@link Comparator} used to determine equality
*/
public UniqueList(EventList source, Comparator super E> comparator) {
this(new SortedList(source, comparator), (Void) null);
}
/**
* A private constructor which allows us to use the {@link SortedList} as
* the main decorated {@link EventList}.
*
* The current implementation of {@link UniqueList} uses the {@link Grouper}
* service to manage collapsing duplicates, which is more efficient than the
* previous implementation that required a longer pipeline of {@link GroupingList}s.
*
*
UniqueList exposes a few extra querying methods like {@link #getCount(int)}
* and {@link #getAll(int)} which require us to query the {@link Grouper}s
* barcode, which retains state on groups.
*
* @param source a private {@link SortedList} whose {@link Comparator} never
* changes, this is used to keep track of uniqueness.
* @param dummyParameter dummy parameter to differentiate between the different
* {@link GroupingList} constructors.
*/
private UniqueList(SortedList source, Void dummyParameter) {
super(source);
// the grouper handles changes to the SortedList
this.grouper = new Grouper(source, new GrouperClient());
source.addListEventListener(this);
}
/**
* Handle changes to the grouper's groups.
*/
private class GrouperClient implements Grouper.Client {
public void groupChanged(int index, int groupIndex, int groupChangeType, boolean primary, int elementChangeType, E oldValue, E newValue) {
switch (groupChangeType) {
case ListEvent.INSERT: updates.elementInserted(groupIndex, newValue); break;
case ListEvent.UPDATE: updates.elementUpdated(groupIndex, oldValue, newValue); break;
case ListEvent.DELETE: updates.elementDeleted(groupIndex, oldValue); break;
default: throw new IllegalStateException("Unrecognized groupChangeType: " + groupChangeType);
}
}
}
/**
* Change the {@link Comparator} which determines the unique elements
* of this List.
*
* @param comparator the {@link Comparator} used to determine groupings;
* null will be treated as {@link GlazedLists#comparableComparator()}
*/
public void setComparator(Comparator super E> comparator) {
if (comparator == null)
comparator = (Comparator) GlazedLists.comparableComparator();
((SortedList) this.source).setComparator(comparator);
}
/** {@inheritDoc} */
public int size() {
return grouper.getBarcode().colourSize(Grouper.UNIQUE);
}
/** {@inheritDoc} */
protected int getSourceIndex(int index) {
if(index == size()) return source.size();
return grouper.getBarcode().getIndex(index, Grouper.UNIQUE);
}
/**
* Get the first element that's not a duplicate of the element at the specified
* index. This is useful for things like {@link #getCount(int)} because we can
* find the full range of a value quickly.
*/
private int getEndIndex(int index) {
if(index == (size() - 1)) return source.size();
else return getSourceIndex(index + 1);
}
/** {@inheritDoc} */
public E remove(int index) {
if(index < 0 || index >= size()) throw new IndexOutOfBoundsException("Cannot remove at " + index + " on list of size " + size());
updates.beginEvent(true);
// remember the first duplicate
E result = get(index);
// remove all duplicates at this index
int startIndex = getSourceIndex(index);
int endIndex = getEndIndex(index);
((SortedList)source).subList(startIndex, endIndex).clear();
updates.commitEvent();
return result;
}
/** {@inheritDoc} */
public E set(int index, E value) {
if(index < 0 || index >= size()) throw new IndexOutOfBoundsException("Cannot set at " + index + " on list of size " + size());
updates.beginEvent(true);
// remove all duplicates of this value first
int startIndex = getSourceIndex(index) + 1;
int endIndex = getEndIndex(index);
if(endIndex > startIndex) {
((SortedList)source).subList(startIndex, endIndex).clear();
}
// now do the set
E result = super.set(index, value);
updates.commitEvent();
return result;
}
/**
* Returns the index in this list of the first occurrence of the specified
* element
, or -1 if this list does not contain this
* element
. More formally, returns the lowest index i
* such that uniqueListComparator.compare(get(i), element) == 0,
* or -1 if there is no such index.
*
* Note: This is a departure from the contract for {@link List#indexOf}
* since it does not guarantee that element.equals(get(i)) where i
* is a positive index returned from this method.
*
* @param element the element to search for.
* @return the index in this list of the first occurrence of the specified
* element, or -1 if this list does not contain this element
* @throws ClassCastException if the type of the specified element
* is incompatible with this list
*/
public int indexOf(Object element) {
final int index = Collections.binarySearch(this, (E) element, ((SortedList)source).getComparator());
// if the element is not found (index is negative) then return -1 to indicate the list does not contain it
return index < 0 ? -1 : index;
}
/** {@inheritDoc} */
protected boolean isWritable() {
return true;
}
/** {@inheritDoc} */
public void listChanged(ListEvent listChanges) {
updates.beginEvent(true);
// check if this ListEvent was caused due to a change in the
// Comparator that defines uniqueness
final SortedList sortedSource = (SortedList) source;
final Comparator super E> sourceComparator = sortedSource.getComparator();
if (sourceComparator != grouper.getComparator()) {
if (!listChanges.isReordering()) {
throw new IllegalStateException("source comparator changed without reordering!");
}
// fire delete events for the existing events.
// use the reorder map to find previous values, since everything's moved
int[] reorderingMap = listChanges.getReorderMap();
int[] reverseReorderingMap = new int[reorderingMap.length];
for (int r = 0; r < reorderingMap.length; r++) {
reverseReorderingMap[reorderingMap[r]] = r;
}
for (BarcodeIterator b = grouper.getBarcode().iterator(); b.hasNextBlack(); ) {
b.nextBlack();
int sourceIndex = b.getIndex();
updates.elementDeleted(0, sortedSource.get(reverseReorderingMap[sourceIndex]));
}
grouper.getBarcode().clear();
// adjust the Comparator used by the Grouper (which will change the barcode)
grouper.setComparator(sourceComparator);
// insert all new unique values using the new barcode
int uniqueIndex = 0;
for (BarcodeIterator b = grouper.getBarcode().iterator(); b.hasNextBlack(); ) {
b.nextBlack();
int sourceIndex = b.getIndex();
updates.elementInserted(uniqueIndex, sortedSource.get(sourceIndex));
uniqueIndex++;
}
} else {
grouper.listChanged(listChanges);
}
updates.commitEvent();
}
/**
* Returns the number of duplicates of the value found at the specified index.
*/
public int getCount(int index) {
int startIndex = getSourceIndex(index);
int endIndex = getEndIndex(index);
return endIndex - startIndex;
}
/**
* Returns the number of duplicates of the specified value.
*/
public int getCount(E value) {
final int index = this.indexOf(value);
if(index == -1) return 0;
else return getCount(index);
}
/**
* Returns a List of all original elements represented by the value at the
* given index
within this {@link UniqueList}.
*/
public List getAll(int index) {
int startIndex = getSourceIndex(index);
int endIndex = getEndIndex(index);
return new ArrayList(source.subList(startIndex, endIndex));
}
/**
* Returns a List of all original elements represented by the given
* value
within this {@link UniqueList}.
*/
public List getAll(E value) {
final int index = this.indexOf(value);
return index == -1 ? (List) Collections.EMPTY_LIST : this.getAll(index);
}
/** {@inheritDoc} */
public void dispose() {
((SortedList)source).dispose();
super.dispose();
}
}