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;
// the Glazed Lists' change objects
import ca.odell.glazedlists.event.*;
import ca.odell.glazedlists.impl.Grouper;
// Java collections are used for underlying data storage
import java.util.*;
/**
* 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.
*
*
* 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 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 comparator) {
this(new SortedList(source, comparator), comparator, 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 comparator the {@link Comparator} used to determine equality.
* @param dummyParameter dummy parameter to differentiate between the different
* {@link GroupingList} constructors.
*/
private UniqueList(SortedList source, Comparator comparator, Void dummyParameter) {
super(source);
// the grouper handles changes to the SortedList
GrouperClient grouperClient = new GrouperClient();
this.grouper = new Grouper(source, 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) {
if(groupChangeType == ListEvent.INSERT) {
updates.addInsert(groupIndex);
} else if(groupChangeType == ListEvent.DELETE) {
updates.addDelete(groupIndex);
} else if(groupChangeType == ListEvent.UPDATE) {
updates.addUpdate(groupIndex);
} else {
throw new IllegalStateException();
}
}
}
/** {@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);
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();
}
}