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

ca.odell.glazedlists.UniqueList 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.BarcodeIterator;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

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

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:
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 static > UniqueList create(EventList source) { return new UniqueList(source); } /** * Creates a {@link UniqueList} that determines uniqueness via the * {@link Comparable} interface. All elements of the source {@link EventList} * must implement {@link Comparable}. *

Usage of factory method {@link #create(EventList)} is preferable. * * @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), (Void) 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 dummyParameter dummy parameter to differentiate between the different * {@link GroupingList} constructors. */ private UniqueList(SortedList source, Void dummyParameter) { super(source); // the grouper handles changes to the SortedList this.grouper = new Grouper(source, new 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, E oldValue, E newValue) { switch (groupChangeType) { case ListEvent.INSERT: updates.elementInserted(groupIndex, newValue); break; case ListEvent.UPDATE: updates.elementUpdated(groupIndex, oldValue, newValue); break; case ListEvent.DELETE: updates.elementDeleted(groupIndex, oldValue); break; default: throw new IllegalStateException("Unrecognized groupChangeType: " + groupChangeType); } } } /** * Change the {@link Comparator} which determines the unique elements * of 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) this.source).setComparator(comparator); } /** {@inheritDoc} */ @Override public int size() { return grouper.getBarcode().colourSize(Grouper.UNIQUE); } /** {@inheritDoc} */ @Override 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} */ @Override 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} */ @Override 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 */ @Override 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} */ @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 defines uniqueness final SortedList sortedSource = (SortedList) source; final Comparator sourceComparator = sortedSource.getComparator(); if (sourceComparator != grouper.getComparator()) { if (!listChanges.isReordering()) { throw new IllegalStateException("source comparator changed without reordering!"); } // fire delete events for the existing events. // use the reorder map to find previous values, since everything's moved int[] reorderingMap = listChanges.getReorderMap(); int[] reverseReorderingMap = new int[reorderingMap.length]; for (int r = 0; r < reorderingMap.length; r++) { reverseReorderingMap[reorderingMap[r]] = r; } for (BarcodeIterator b = grouper.getBarcode().iterator(); b.hasNextBlack(); ) { b.nextBlack(); int sourceIndex = b.getIndex(); updates.elementDeleted(0, sortedSource.get(reverseReorderingMap[sourceIndex])); } grouper.getBarcode().clear(); // adjust the Comparator used by the Grouper (which will change the barcode) grouper.setComparator(sourceComparator); // insert all new unique values using the new barcode int uniqueIndex = 0; for (BarcodeIterator b = grouper.getBarcode().iterator(); b.hasNextBlack(); ) { b.nextBlack(); int sourceIndex = b.getIndex(); updates.elementInserted(uniqueIndex, sortedSource.get(sourceIndex)); uniqueIndex++; } } else { 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 ? Collections.emptyList() : this.getAll(index); } /** {@inheritDoc} */ @Override public void dispose() { ((SortedList)source).dispose(); super.dispose(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy