/* Glazed Lists (c) 2003-2006 */
/* http://publicobject.com/glazedlists/ publicobject.com,*/
/* O'Dell Engineering Ltd.*/
package CPS.Core.TODOLists;
import ca.odell.glazedlists.EventList;
import ca.odell.glazedlists.GlazedLists;
import ca.odell.glazedlists.SortedList;
import ca.odell.glazedlists.TransformedList;
import ca.odell.glazedlists.event.ListEvent;
import ca.odell.glazedlists.impl.adt.barcode2.Element;
import ca.odell.glazedlists.impl.adt.barcode2.SimpleTree;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
/**
* 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 is specified via a
* Comparator. Elements for which the Comparator returns 0 are guaranteed to be
* contained within the same group within this AdjacentGroupingList. This implies
* that source elements may only participate in a single group within this
* AdjacentGroupingList.
*
* <p>Further transformations may be layered on top of this AdjacentGroupingList to
* transform the group lists into any other desirable form.
*
* <p><strong><font color="#FF0000">Warning:</font></strong> This class is
* thread ready but not thread safe. See {@link EventList} for an example
* of thread safe code.
*
* <p><table border="1" width="100%" cellpadding="3" cellspacing="0">
* <tr class="TableHeadingColor"><td colspan=2><font size="+2"><b>EventList Overview</b></font></td></tr>
* <tr><td class="TableSubHeadingColor"><b>Writable:</b></td><td>yes</td></tr>
* <tr><td class="TableSubHeadingColor"><b>Concurrency:</b></td><td>thread ready, not thread safe</td></tr>
* <tr><td class="TableSubHeadingColor"><b>Performance:</b></td><td>reads: O(log N), writes O(log N)</td></tr>
* <tr><td class="TableSubHeadingColor"><b>Memory:</b></td><td></td></tr>
* <tr><td class="TableSubHeadingColor"><b>Unit Tests:</b></td><td>AdjacentGroupingListTest</td></tr>
* <tr><td class="TableSubHeadingColor"><b>Issues:</b></td><td>
* <a href="https://glazedlists.dev.java.net/issues/show_bug.cgi?id=281">281</a>
* </td></tr>
* </table>
*
* @author James Lemieux
*/
public final class AdjacentGroupingList<E> extends TransformedList<E, List<E>> {
/** The GroupLists defined by the comparator. They are stored in an SimpleTree so their indices can be quickly updated. */
private SimpleTree<AdjacentGroupingList.GroupList> groupLists = new SimpleTree<AdjacentGroupingList.GroupList>();
/** The AdjacentGrouper manages creating and deleting groups. */
private final AdjacentGrouper<E> grouper;
/**
* Creates a {@link AdjacentGroupingList} that determines groupings via the
* {@link Comparable} interface which all elements of the <code>source</code>
* are assumed to implement.
*/
public static <E extends Comparable<? super E>> AdjacentGroupingList<E> create(EventList<E> source) {
return new AdjacentGroupingList<E>(source);
}
/**
* Creates a {@link AdjacentGroupingList} that determines groupings via the
* {@link Comparable} interface which all elements of the <code>source</code>
* are assumed to implement.
* <p>Usage of factory method {@link #create(EventList)} is preferable.
*/
public AdjacentGroupingList(EventList<E> source) {
this(source, (Comparator<E>) GlazedLists.comparableComparator());
}
/**
* Creates a {@link AdjacentGroupingList} 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 AdjacentGroupingList(EventList<E> source, Comparator<? super E> comparator) {
this(source, 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 AdjacentGroupingList} constructors.
*/
private AdjacentGroupingList(EventList<E> source, Comparator<? super E> comparator, Void dummyParameter) {
super(source);
// the grouper handles changes to the SortedList
this.grouper = new AdjacentGrouper<E>( source, comparator, new AdjacentGroupingList.AdjacentGrouperClient());
// initialize the tree of GroupLists
rebuildGroupListTreeFromBarcode();
source.addListEventListener(this);
}
/**
* After the barcode has been updated in response to a change in the
* grouping {@link Comparator}, this method is used to rebuild the tree of
* GroupLists which map those GroupLists to their overall indices.
*/
private void rebuildGroupListTreeFromBarcode() {
// clear the contents of the GroupList tree
groupLists.clear();
// fetch our AdjacentGrouperClient
final AdjacentGroupingList.AdjacentGrouperClient grouperClient = (AdjacentGroupingList.AdjacentGrouperClient) grouper.getClient();
// build the tree of GroupLists from the barcode
for (int i = 0, n = grouper.getBarcode().colourSize(AdjacentGrouper.UNIQUE); i < n; i++) {
grouperClient.insertGroupList(i);
}
}
/**
* Return the index of the group to which the <code>groupElement</code>
* would belong if it were hypothetically added to the source list. Note
* that <code>groupElement</code> does <strong>NOT</strong> have to exist
* in a group. This method is essentially a convenient way to locate a
* group based on a prototypical element of that group.
*
* @param groupElement a prototype element of the group to locate
* @return the index of the group that would contain <code>groupElement</code>
* if it were added to the source list or <code>-1</code> if no
* currently existing group would contain the <code>groupElement</code>
*/
public int indexOfGroup(E groupElement) {
// determine where the groupElement would be positioned in the source List
// final int sourceIndex = ((SortedList<E>) source).sortIndex(groupElement);
final int sourceIndex = source.size();
// if the groupElement is not a member of the group, return -1
if (sourceIndex == source.size() || grouper.getComparator().compare(source.get(sourceIndex), groupElement) != 0)
return -1;
// return the index of the group that includes the element at the source index
return grouper.getBarcode().getColourIndex(sourceIndex, AdjacentGrouper.UNIQUE);
}
/**
* Handle changes to the grouping list groups.
*/
private class AdjacentGrouperClient implements AdjacentGrouper.Client<E> {
public void groupChanged(int index, int groupIndex, int groupChangeType, boolean primary, int elementChangeType, E oldValue, E newValue) {
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
* <code>index</code>.
*
* @param index the location at which to insert an empty GroupList
*/
private void insertGroupList(int index) {
final AdjacentGroupingList.GroupList groupList = new AdjacentGroupingList.GroupList();
final Element<AdjacentGroupingList.GroupList> indexedTreeNode = groupLists.add(index, groupList, 1);
groupList.setTreeNode(indexedTreeNode);
}
/**
* Removes the GroupList at the given <code>index</code>.
*
* @param index the location at which to remove a GroupList
*/
private void removeGroupList(int index) {
final Element<AdjacentGroupingList.GroupList> indexedTreeNode = groupLists.get(index);
groupLists.remove(indexedTreeNode);
// for safety, null out the GroupList's reference to its now defunct indexedTreeNode
indexedTreeNode.get().setTreeNode(null);
}
}
/**
* Change the {@link Comparator} which determines the groupings presented
* by this List
*
* @param comparator the {@link Comparator} used to determine groupings;
* <tt>null</tt> will be treated as {@link GlazedLists#comparableComparator()}
*/
public void setComparator(Comparator<? super E> comparator) {
if (comparator == null)
comparator = (Comparator) GlazedLists.comparableComparator();
// ((SortedList<E>) source).setComparator(comparator);
grouper.setComparator(comparator);
}
/** {@inheritDoc} */
protected int getSourceIndex(int index) {
return grouper.getBarcode().getIndex(index, AdjacentGrouper.UNIQUE);
}
/** {@inheritDoc} */
protected boolean isWritable() {
return true;
}
/** {@inheritDoc} */
public void listChanged(ListEvent<E> listChanges) {
updates.beginEvent(true);
// check if this ListEvent was caused due to a change in the
// Comparator that creates the groups
// final SortedList<E> sortedSource = (SortedList<E>) source;
// final Comparator<? super E> sourceComparator = sortedSource.getComparator();
final Comparator<? super E> sourceComparator = grouper.getComparator();
if (sourceComparator != grouper.getComparator()) {
// when the grouping comparator is changed in the source list, let
// the grouper know so we can rebuild our groups from scratch
// record the impending removal of all groups before adjusting the barcode
for (int i = 0, n = size(); i < n; i++)
updates.elementDeleted(0, get(i));
// adjust the Comparator used by the AdjacentGrouper (which will change the barcode)
grouper.setComparator(sourceComparator);
// rebuild the tree which maps GroupLists to indices (so the tree matches the new barcode)
rebuildGroupListTreeFromBarcode();
// insert all new groups (represented by the newly formed barcode)
updates.addInsert(0, size() - 1);
} else {
grouper.listChanged(listChanges);
}
updates.commitEvent();
}
/** {@inheritDoc} */
public List<E> get(int index) {
return groupLists.get(index).get();
}
/** {@inheritDoc} */
public List<E> remove(int index) {
if(index < 0 || index >= size()) throw new IndexOutOfBoundsException("Cannot remove at " + index + " on list of size " + size());
final List<E> removed = (List<E>) get(index);
// make a copy of the list to return
final List<E> result = new ArrayList<E>(removed);
removed.clear();
return result;
}
/** {@inheritDoc} */
public List<E> set(int index, List<E> value) {
if(index < 0 || index >= size()) throw new IndexOutOfBoundsException("Cannot set at " + index + " on list of size " + size());
updates.beginEvent(true);
final List<E> result = (List<E>) remove(index);
add(index, value);
updates.commitEvent();
return result;
}
/**
* This version of add will distribute all elements within the given
* <code>value</code> List into groups. Existing groups will be reused and
* new groups will be created as needed. As such, the <code>index</code>
* argument is meaningless.
*
* <p><strong><font color="#FF0000">Warning:</font></strong> This method
* breaks the contract required by {@link List#add(int, Object)}.
*/
public void add(int index, List<E> value) {
source.addAll(value);
}
/** {@inheritDoc} */
public int size() {
return grouper.getBarcode().colourSize(AdjacentGrouper.UNIQUE);
}
/** {@inheritDoc} */
public void dispose() {
((EventList) source).dispose();
super.dispose();
}
/**
* This is the List implementation used to store groups created by this
* AdjacentGroupingList. 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<E> {
/**
* The node within {@link AdjacentGroupingList#groupLists} that records the
* index of this GroupList within the AdjacentGroupingList.
*/
private Element<AdjacentGroupingList.GroupList> treeNode;
/**
* Attach the Element that tracks this GroupLists position to the
* GroupList itself so it can look up its own position.
*/
private void setTreeNode(Element<AdjacentGroupingList.GroupList> 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 AdjacentGroupingList.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, AdjacentGrouper.UNIQUE);
// if this is at the end, its everything after
} else {
return grouper.getBarcode().size();
}
}
private int getSourceIndex(int index) {
return getStartIndex() + index;
}
/** {@inheritDoc} */
public E set(int index, E element) {
return source.set(getSourceIndex(index), element);
}
/** {@inheritDoc} */
public E get(int index) {
return source.get(getSourceIndex(index));
}
/** {@inheritDoc} */
public int size() {
return getEndIndex() - getStartIndex();
}
/** {@inheritDoc} */
public void clear() {
source.subList(getStartIndex(), getEndIndex()).clear();
}
/** {@inheritDoc} */
public E remove(int index) {
return source.remove(getSourceIndex(index));
}
/** {@inheritDoc} */
public void add(int index, E element) {
source.add(getSourceIndex(index), element);
}
}
}