package org.marketcetera.core.position.impl; import java.util.Collection; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import org.marketcetera.util.misc.ClassVersion; import ca.odell.glazedlists.AbstractEventList; import ca.odell.glazedlists.EventList; import ca.odell.glazedlists.GlazedLists; import ca.odell.glazedlists.TransformedList; import ca.odell.glazedlists.event.ListEvent; import ca.odell.glazedlists.event.ListEventAssembler; import ca.odell.glazedlists.impl.adt.Barcode; import ca.odell.glazedlists.impl.adt.barcode2.Element; import ca.odell.glazedlists.impl.adt.barcode2.SimpleTree; import ca.odell.glazedlists.impl.adt.barcode2.SimpleTreeIterator; import ca.odell.glazedlists.matchers.Matcher; /* $License$ */ /** * Replacement for {@link ca.odell.glazedlists.GroupingList} that provides event notifications for * the groups. * <p> * A grouping list is initialized with a {@link GroupMatcherFactory}, which it uses to create a * unique {@link GroupMatcher} to define a group. The groups are ordered by the natural ordering of * the GroupMatchers. * <p> * Each element in the grouping list is itself an event list. These children group lists preserve * the ordering of their respective elements in the source list. * <p> * This class depends on internal classes in the glazed lists library so changing/upgrading the * library should be done with care. * <p> * See <a href="http://www.nabble.com/GroupList-notification-td21879305.html">http://www.nabble.com/ * GroupList-notification-td21879305.html</a> * * @author <a href="mailto:will@marketcetera.com">Will Horn</a> * @version $Id: GroupingList.java 16841 2014-02-20 19:59:04Z colin $ * @since 1.5.0 */ @ClassVersion("$Id: GroupingList.java 16841 2014-02-20 19:59:04Z colin $") public class GroupingList<E> extends TransformedList<E, EventList<E>> { /** * The GroupLists that make up this GroupingList in sorted order (sorted by their * GroupMatchers). */ private SimpleTree<GroupList> groupLists = new SimpleTree<GroupList>(GlazedLists .comparableComparator()); private GroupMatcherFactory<E, GroupMatcher<E>> factory; /** * Constructor. * * @param source * the source list * @param factory * factory to create group matchers */ public GroupingList(EventList<E> source, GroupMatcherFactory<E, GroupMatcher<E>> factory) { super(source); this.factory = factory; initTree(); source.addListEventListener(this); } private void initTree() { // create groups for source elements and build tree int index = 0; for (Iterator<E> i = source.iterator(); i.hasNext(); index++) { processInsert(index, i.next(), false); } } @Override @SuppressWarnings("deprecation") public void listChanged(ListEvent<E> listChanges) { beginEvent(); if (listChanges.isReordering()) { throw new UnsupportedOperationException(); } while (listChanges.next()) { final int changeIndex = listChanges.getIndex(); final int changeType = listChanges.getType(); if (changeType == ListEvent.INSERT) { E inserted = source.get(changeIndex); processInsert(changeIndex, inserted, true); } else if (changeType == ListEvent.UPDATE) { E updated = source.get(changeIndex); E oldValue = listChanges.getOldValue(); GroupMatcher<E> oldMatcher = factory.createGroupMatcher(oldValue); if (oldMatcher.matches(updated)) { // same group GroupList oldGroup = new GroupList(oldMatcher); int oldIndex = groupLists.indexOfValue(oldGroup, true, false, (byte) 1); GroupList oldList = groupLists.get(oldIndex).get(); oldList.getListEventAssembler().elementUpdated( oldList.barcode.getBlackIndex(changeIndex), oldValue, updated); updates.elementUpdated(oldIndex, oldList, oldList); } else { // new group, treat as a delete and insert processDelete(changeIndex, oldValue); processInsert(changeIndex, updated, true); } } else if (changeType == ListEvent.DELETE) { processDelete(changeIndex, listChanges.getOldValue()); } } commitEventAndDelete(); } private void beginEvent() { updates.beginEvent(); for (SimpleTreeIterator<GroupList> i = new SimpleTreeIterator<GroupList>(groupLists); i.hasNext();) { i.next(); GroupList group = i.value(); group.getListEventAssembler().beginEvent(); } } private void processInsert(final int changeIndex, E inserted, boolean notify) { // This is a bit clunky - to find the group for an element, I am // creating a new GroupList and looking for it in the tree. GroupLists // are considered equal if their matchers compare equal. GroupMatcher<E> matcher = factory.createGroupMatcher(inserted); GroupList newGroup = new GroupList(matcher); int groupIndex = groupLists.indexOfValue(newGroup, true, false, (byte) 1); if (groupIndex >= 0) { insertHelper(changeIndex, groupIndex); if (notify) { GroupList groupingList = groupLists.get(groupIndex).get(); // an insert on the existing list groupingList.getListEventAssembler().elementInserted( groupingList.barcode.getBlackIndex(changeIndex), inserted); // an update on the grouping list updates.elementUpdated(groupIndex, groupingList, groupingList); } } else { Element<GroupList> newList = groupLists.addInSortedOrder((byte) 1, newGroup, 1); groupIndex = groupLists.indexOfNode(newList, (byte) 1); insertHelper(changeIndex, groupIndex); if (notify) { // begin event so later inserts, updates, deletes will work newGroup.getListEventAssembler().beginEvent(); // an update on the grouping list updates.elementInserted(groupIndex, newGroup); } } } /** * Update GroupLists barcodes to reflect an insert. * * @param insertIndex * the source index of the insert * @param groupIndex * the index of the matching group for the new element */ private void insertHelper(int insertIndex, int groupIndex) { int index = 0; for (SimpleTreeIterator<GroupList> i = new SimpleTreeIterator<GroupList>(groupLists); i .hasNext(); index++) { i.next(); GroupList group = i.value(); if (index == groupIndex) { group.barcode.addBlack(insertIndex, 1); } else { group.barcode.addWhite(insertIndex, 1); } } } private void processDelete(int changeIndex, E oldValue) { for (SimpleTreeIterator<GroupList> i = new SimpleTreeIterator<GroupList>(groupLists); i .hasNext();) { i.next(); GroupList group = i.value(); int blackIndex = group.barcode.getBlackIndex(changeIndex); if (blackIndex != -1) { // this will happen on one of the groups (the matching group) group.getListEventAssembler().elementDeleted(blackIndex, oldValue); } group.barcode.remove(changeIndex, 1); } } private void commitEventAndDelete() { int index = 0; // parallel lists of index-group pairs for delete notification List<Integer> toRemove = new LinkedList<Integer>(); List<GroupList> toRemoveGroups = new LinkedList<GroupList>(); for (SimpleTreeIterator<GroupList> i = new SimpleTreeIterator<GroupList>(groupLists); i .hasNext(); index++) { i.next(); GroupList group = i.value(); ListEventAssembler<E> listEventAssembler = group.getListEventAssembler(); if (listEventAssembler.isEventEmpty()) { listEventAssembler.discardEvent(); } else { listEventAssembler.commitEvent(); } if (group.barcode.blackSize() == 0) { // don't remove now since we are iterating, // but mark for later toRemove.add(index--); toRemoveGroups.add(group); } } Iterator<Integer> i = toRemove.iterator(); Iterator<GroupList> j = toRemoveGroups.iterator(); while (i.hasNext()) { Integer remIndex = i.next(); groupLists.remove(remIndex, 1); updates.elementDeleted(remIndex, j.next()); } updates.commitEvent(); } @Override public EventList<E> get(int index) { return groupLists.get(index).get(); } @Override protected boolean isWritable() { return false; } @Override protected int getSourceIndex(int index) { throw new UnsupportedOperationException(); } @Override public boolean add(EventList<E> value) { throw new UnsupportedOperationException(); } @Override public void add(int index, EventList<E> value) { throw new UnsupportedOperationException(); } @Override public boolean addAll(Collection<? extends EventList<E>> values) { throw new UnsupportedOperationException(); } @Override public boolean addAll(int index, Collection<? extends EventList<E>> values) { throw new UnsupportedOperationException(); } @Override public EventList<E> remove(int index) { throw new UnsupportedOperationException(); } @Override public boolean remove(Object toRemove) { throw new UnsupportedOperationException(); } @Override public boolean removeAll(Collection<?> collection) { throw new UnsupportedOperationException(); } @Override public boolean retainAll(Collection<?> values) { throw new UnsupportedOperationException(); } @Override public EventList<E> set(int index, EventList<E> value) { throw new UnsupportedOperationException(); } @Override public void clear() { throw new UnsupportedOperationException(); } @Override public int size() { return groupLists.size(); } /** * EventList implementation used for groups. * * @author <a href="mailto:will@marketcetera.com">Will Horn</a> * @version $Id: GroupingList.java 16841 2014-02-20 19:59:04Z colin $ * @since 1.5.0 */ @ClassVersion("$Id: GroupingList.java 16841 2014-02-20 19:59:04Z colin $") private class GroupList extends AbstractEventList<E> implements Comparable<GroupList> { private GroupMatcher<E> matcher; private Barcode barcode = new Barcode(); private ListEventAssembler<E> getListEventAssembler() { return updates; } public GroupList(GroupMatcher<E> matcher) { this.readWriteLock = GroupingList.this.readWriteLock; this.matcher = matcher; } @Override public E get(int index) { return source.get(barcode.getIndex(index, Barcode.BLACK)); } @Override public int size() { return barcode.blackSize(); } @Override public void dispose() { } @Override public int compareTo(GroupList o) { return matcher.compareTo(o.matcher); } } /** * A matcher that determines if an element should be in a group. One of these GroupMatchers * defines a group in an immutable way. It is comparable to impose an ordering on the groups. */ public interface GroupMatcher<T> extends Matcher<T>, Comparable<GroupMatcher<T>> { } /** * Creates GroupMatchers for elements in the GroupingList. This factory must create identical * matchers for elements that belong in the same group. Essentially, this factory is responsible * for extracting the immutable grouping criteria from an element. */ public interface GroupMatcherFactory<S, T extends GroupMatcher<S>> { T createGroupMatcher(S element); } }