ca.odell.glazedlists.GroupingList 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.*;
// volatile implementation support
import ca.odell.glazedlists.impl.adt.barcode2.SimpleTree;
import ca.odell.glazedlists.impl.adt.barcode2.Element;
import ca.odell.glazedlists.impl.Grouper;
// Java collections are used for underlying data storage
import java.util.*;
/**
* A grouping list contains elements which are themselves Lists. Those Lists
* are infact elements of the source list which have been grouped together into
* a List. The logic of how to group the source elements into groups is specified
* via a Comparator. Elements for which the Comparator returns 0 are guaranteed
* to be contained within the same group within this GroupingList. This implies
* that source elements may only participate in a single group within this
* GroupingList.
*
* Further transformations may be layered on top of this GroupingList to
* transform the group lists into any other desirable form.
*
*
* EventList Overview
* Writable: yes
* Concurrency: thread ready, not thread safe
* Performance: reads: O(log N), writes O(log N)
* Memory:
* Unit Tests: GroupingListTest
* Issues:
* 281
*
*
*
* @author James Lemieux
*/
public final class GroupingList extends TransformedList> {
/** The GroupLists defined by the comparator. They are stored in an IndexedTree so their indices can be quickly updated. */
private SimpleTree groupLists = new SimpleTree();
/** the grouping service manages creating and deleting groups */
private final Grouper grouper;
/**
* Creates a {@link GroupingList} that determines groupings via the
* {@link Comparable} interface which all elements of the source
* are assumed to implement.
*/
public GroupingList(EventList source) {
this(source, (Comparator) GlazedLists.comparableComparator());
}
/**
* Creates a {@link GroupingList} that determines groups using the specified
* {@link Comparator}.
*
* @param source the {@link EventList} containing elements to be grouped
* @param comparator the {@link Comparator} used to determine groupings
*/
public GroupingList(EventList source, Comparator comparator) {
this(new SortedList(source, comparator), comparator, null);
}
/**
* A private constructor which provides a convenient handle to the
* {@link SortedList} which will serve as the source of this list.
*
* @param source the elements to be grouped arranged in sorted order
* @param comparator the {@link Comparator} used to determine groupings
* @param dummyParameter dummy parameter to differentiate between the different
* {@link GroupingList} constructors.
*/
private GroupingList(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);
// initialize the tree of GroupLists
for(int i = 0; i < grouper.getBarcode().colourSize(Grouper.UNIQUE); i++) {
grouperClient.insertGroupList(i);
}
source.addListEventListener(this);
}
/**
* Handle changes to the grouping list groups.
*/
private class GrouperClient implements Grouper.Client {
public void groupChanged(int index, int groupIndex, int groupChangeType, boolean primary, int elementChangeType) {
if(groupChangeType == ListEvent.INSERT) {
insertGroupList(groupIndex);
updates.addInsert(groupIndex);
} else if(groupChangeType == ListEvent.DELETE) {
removeGroupList(groupIndex);
updates.addDelete(groupIndex);
} else if(groupChangeType == ListEvent.UPDATE) {
updates.addUpdate(groupIndex);
} else {
throw new IllegalStateException();
}
}
/**
* Creates and inserts a new GroupList at the specified
* index
.
*
* @param index the location at which to insert an empty GroupList
*/
private void insertGroupList(int index) {
final GroupList groupList = new GroupList();
final Element indexedTreeNode = groupLists.add(index, groupList, 1);
groupList.setTreeNode(indexedTreeNode);
}
/**
* Removes the GroupList at the given index
.
*
* @param index the location at which to remove a GroupList
*/
private void removeGroupList(int index) {
final Element indexedTreeNode = groupLists.get(index);
groupLists.remove(indexedTreeNode);
// for safety, null out the GroupList's reference to its now defunct indexedTreeNode
indexedTreeNode.get().setTreeNode(null);
}
}
/** {@inheritDoc} */
public int size() {
return grouper.getBarcode().colourSize(Grouper.UNIQUE);
}
/** {@inheritDoc} */
protected int getSourceIndex(int index) {
return grouper.getBarcode().getIndex(index, Grouper.UNIQUE);
}
/** {@inheritDoc} */
protected boolean isWritable() {
return true;
}
/** {@inheritDoc} */
public void listChanged(ListEvent listChanges) {
updates.beginEvent(true);
grouper.listChanged(listChanges);
updates.commitEvent();
}
public List get(int index) {
return this.groupLists.get(index).get();
}
/** {@inheritDoc} */
public List remove(int index) {
if(index < 0 || index >= size()) throw new IndexOutOfBoundsException("Cannot remove at " + index + " on list of size " + size());
final List removed = (List)this.get(index);
// make a copy of the list to return
final List result = new ArrayList(removed);
removed.clear();
return result;
}
/** {@inheritDoc} */
public List set(int index, List value) {
if(index < 0 || index >= size()) throw new IndexOutOfBoundsException("Cannot set at " + index + " on list of size " + size());
updates.beginEvent(true);
final List result = (List)this.remove(index);
this.add(index, value);
updates.commitEvent();
return result;
}
/**
* This version of add will distribute all elements within the given
* value
List into groups. Existing groups will be reused and
* new groups will be created as needed. As such, the index
* argument is meaningless.
*
* Warning: This method
* breaks the contract required by {@link List#add(int, Object)}.
*/
public void add(int index, List value) {
source.addAll(value);
}
/** {@inheritDoc} */
public void dispose() {
((SortedList) this.source).dispose();
super.dispose();
}
/**
* This is the List implementation used to store groups created by this
* GroupingList. It defines all mutator methods by mapping them to mutations
* on the source list of GroupList. Thus, writes to this GroupList effect
* all Lists sitting under GroupList.
*/
private class GroupList extends AbstractList {
/** The node within {@link groupLists} that records the index of this GroupList within the GroupingList. */
private Element treeNode;
/**
* Attach the IndexedTreeNode that tracks this GroupLists position to the
* GroupList itself so it can look up its own position.
*/
private void setTreeNode(Element treeNode) {
this.treeNode = treeNode;
}
/**
* Returns the inclusive index of the start of this {@link GroupList}
* within the source {@link SortedList}.
*/
private int getStartIndex() {
if (treeNode == null) return -1;
final int groupIndex = groupLists.indexOfNode(treeNode, (byte)1);
return GroupingList.this.getSourceIndex(groupIndex);
}
/**
* Returns the exclusive index of the end of this {@link GroupList}
* within the source {@link SortedList}.
*/
private int getEndIndex() {
if (treeNode == null) return -1;
final int groupIndex = groupLists.indexOfNode(treeNode, (byte)1);
// if this is before the end, its everything up to the first different element
if(groupIndex < grouper.getBarcode().blackSize() - 1) {
return grouper.getBarcode().getIndex(groupIndex + 1, Grouper.UNIQUE);
// if this is at the end, its everything after
} else {
return grouper.getBarcode().size();
}
}
private int getSourceIndex(int index) {
return this.getStartIndex() + index;
}
/** {@inheritDoc} */
public E set(int index, E element) {
return source.set(this.getSourceIndex(index), element);
}
/** {@inheritDoc} */
public E get(int index) {
return source.get(this.getSourceIndex(index));
}
/** {@inheritDoc} */
public int size() {
return this.getEndIndex() - this.getStartIndex();
}
/** {@inheritDoc} */
public void clear() {
source.subList(this.getStartIndex(), this.getEndIndex()).clear();
}
/** {@inheritDoc} */
public E remove(int index) {
return source.remove(this.getSourceIndex(index));
}
/** {@inheritDoc} */
public void add(int index, E element) {
source.add(this.getSourceIndex(index), element);
}
}
}