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

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

/* 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.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 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. * *

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)
Memory:
Unit Tests:GroupingListTest
Issues: * 281 * 491 *
* * @author James Lemieux */ public final class GroupingList extends TransformedList> { /** The GroupLists defined by the comparator. They are stored in an SimpleTree so their indices can be quickly updated. */ private SimpleTree groupLists = new SimpleTree(); /** The Grouper 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 static > GroupingList create(EventList source) { return new GroupingList(source); } /** * Creates a {@link GroupingList} that determines groupings via the * {@link Comparable} interface which all elements of the source * are assumed to implement. *

Usage of factory method {@link #create(EventList)} is preferable. */ 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 this.grouper = new Grouper(source, new GrouperClient()); // 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 GrouperClient final GrouperClient grouperClient = (GrouperClient) grouper.getClient(); // build the tree of GroupLists from the barcode for (int i = 0, n = grouper.getBarcode().colourSize(Grouper.UNIQUE); i < n; i++) { grouperClient.insertGroupList(i); } } /** * Return the index of the group to which the groupElement * would belong if it were hypothetically added to the source list. Note * that groupElement does NOT 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 groupElement * if it were added to the source list or -1 if no * currently existing group would contain the groupElement */ public int indexOfGroup(E groupElement) { // determine where the groupElement would be positioned in the source List final int sourceIndex = ((SortedList) source).sortIndex(groupElement); // 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, Grouper.UNIQUE); } /** * 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, 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 * 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); } } /** * Change the {@link Comparator} which determines the groupings presented * by this List * * @param comparator the {@link Comparator} used to determine groupings; * null will be treated as {@link GlazedLists#comparableComparator()} */ public void setComparator(Comparator comparator) { if (comparator == null) comparator = (Comparator) GlazedLists.comparableComparator(); ((SortedList) source).setComparator(comparator); } /** {@inheritDoc} */ @Override protected int getSourceIndex(int index) { return grouper.getBarcode().getIndex(index, Grouper.UNIQUE); } /** {@inheritDoc} */ @Override protected boolean isWritable() { return true; } /** {@inheritDoc} */ @Override public void listChanged(ListEvent listChanges) { updates.beginEvent(true); // check if this ListEvent was caused due to a change in the // Comparator that creates the groups final SortedList sortedSource = (SortedList) source; final Comparator sourceComparator = sortedSource.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 Grouper (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} */ @Override public List get(int index) { return groupLists.get(index).get(); } /** {@inheritDoc} */ @Override 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 = get(index); // make a copy of the list to return final List result = new ArrayList(removed); removed.clear(); return result; } /** {@inheritDoc} */ @Override 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 = remove(index); 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)}. */ @Override public void add(int index, List value) { source.addAll(value); } /** {@inheritDoc} */ @Override public int size() { return grouper.getBarcode().colourSize(Grouper.UNIQUE); } /** {@inheritDoc} */ @Override public void dispose() { ((SortedList) 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 GroupingList#groupLists} that records the * index of this GroupList within the GroupingList. */ private Element 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 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 getStartIndex() + index; } /** {@inheritDoc} */ @Override public E set(int index, E element) { return source.set(getSourceIndex(index), element); } /** {@inheritDoc} */ @Override public E get(int index) { return source.get(getSourceIndex(index)); } /** {@inheritDoc} */ @Override public int size() { return getEndIndex() - getStartIndex(); } /** {@inheritDoc} */ @Override public void clear() { source.subList(getStartIndex(), getEndIndex()).clear(); } /** {@inheritDoc} */ @Override public E remove(int index) { return source.remove(getSourceIndex(index)); } /** {@inheritDoc} */ @Override public void add(int index, E element) { source.add(getSourceIndex(index), element); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy