COLLECTIONS-427 patch applied.

git-svn-id: https://svn.apache.org/repos/asf/commons/proper/collections/trunk@1377485 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Brent Worden 2012-08-26 19:01:51 +00:00
parent dec0641e2f
commit 8338b9a252
3 changed files with 429 additions and 316 deletions

View File

@ -75,6 +75,9 @@
<action issue="COLLECTIONS-405" dev="brentworden" type="add" due-to="Adam Dyga">
Added "ListUtils#select" and "ListUtils#selectRejected" methods.
</action>
<action issue="COLLECTIONS-427" dev="brentworden" type="fix" due-to="Mert Guldur">
Fixed performance issue in "SetUniqueList#retainAll" method for large collections.
</action>
</release>
</body>
</document>

View File

@ -29,372 +29,409 @@ import org.apache.commons.collections.iterators.AbstractListIteratorDecorator;
import org.apache.commons.collections.set.UnmodifiableSet;
/**
* Decorates a <code>List</code> to ensure that no duplicates are present
* much like a <code>Set</code>.
* Decorates a <code>List</code> to ensure that no duplicates are present much
* like a <code>Set</code>.
* <p>
* The <code>List</code> interface makes certain assumptions/requirements.
* This implementation breaks these in certain ways, but this is merely the
* result of rejecting duplicates.
* Each violation is explained in the method, but it should not affect you.
* Bear in mind that Sets require immutable objects to function correctly.
* The <code>List</code> interface makes certain assumptions/requirements. This
* implementation breaks these in certain ways, but this is merely the result of
* rejecting duplicates. Each violation is explained in the method, but it
* should not affect you. Bear in mind that Sets require immutable objects to
* function correctly.
* <p>
* The {@link org.apache.commons.collections.set.ListOrderedSet ListOrderedSet}
* class provides an alternative approach, by wrapping an existing Set and
* retaining insertion order in the iterator.
* <p>
* This class is Serializable from Commons Collections 3.1.
*
*
* @since 3.0
* @version $Id$
*/
public class SetUniqueList<E> extends AbstractSerializableListDecorator<E> {
/** Serialization version */
private static final long serialVersionUID = 7196982186153478694L;
/** Serialization version */
private static final long serialVersionUID = 7196982186153478694L;
/**
* Internal Set to maintain uniqueness.
*/
protected final Set<E> set;
/**
* Internal Set to maintain uniqueness.
*/
protected final Set<E> set;
/**
* Factory method to create a SetList using the supplied list to retain order.
* <p>
* If the list contains duplicates, these are removed (first indexed one kept).
* A <code>HashSet</code> is used for the set behaviour.
*
* @param <E> the element type
* @param list the list to decorate, must not be null
* @return a new {@link SetUniqueList}
* @throws IllegalArgumentException if list is null
*/
public static <E> SetUniqueList<E> setUniqueList(List<E> list) {
if (list == null) {
throw new IllegalArgumentException("List must not be null");
}
if (list.isEmpty()) {
return new SetUniqueList<E>(list, new HashSet<E>());
}
List<E> temp = new ArrayList<E>(list);
list.clear();
SetUniqueList<E> sl = new SetUniqueList<E>(list, new HashSet<E>());
sl.addAll(temp);
return sl;
}
/**
* Factory method to create a SetList using the supplied list to retain
* order.
* <p>
* If the list contains duplicates, these are removed (first indexed one
* kept). A <code>HashSet</code> is used for the set behaviour.
*
* @param <E>
* the element type
* @param list
* the list to decorate, must not be null
* @return a new {@link SetUniqueList}
* @throws IllegalArgumentException
* if list is null
*/
public static <E> SetUniqueList<E> setUniqueList(List<E> list) {
if (list == null) {
throw new IllegalArgumentException("List must not be null");
}
if (list.isEmpty()) {
return new SetUniqueList<E>(list, new HashSet<E>());
}
List<E> temp = new ArrayList<E>(list);
list.clear();
SetUniqueList<E> sl = new SetUniqueList<E>(list, new HashSet<E>());
sl.addAll(temp);
return sl;
}
//-----------------------------------------------------------------------
/**
* Constructor that wraps (not copies) the List and specifies the set to use.
* <p>
* The set and list must both be correctly initialised to the same elements.
*
* @param set the set to decorate, must not be null
* @param list the list to decorate, must not be null
* @throws IllegalArgumentException if set or list is null
*/
protected SetUniqueList(List<E> list, Set<E> set) {
super(list);
if (set == null) {
throw new IllegalArgumentException("Set must not be null");
}
this.set = set;
}
// -----------------------------------------------------------------------
/**
* Constructor that wraps (not copies) the List and specifies the set to
* use.
* <p>
* The set and list must both be correctly initialised to the same elements.
*
* @param set
* the set to decorate, must not be null
* @param list
* the list to decorate, must not be null
* @throws IllegalArgumentException
* if set or list is null
*/
protected SetUniqueList(List<E> list, Set<E> set) {
super(list);
if (set == null) {
throw new IllegalArgumentException("Set must not be null");
}
this.set = set;
}
//-----------------------------------------------------------------------
/**
* Gets an unmodifiable view as a Set.
*
* @return an unmodifiable set view
*/
public Set<E> asSet() {
return UnmodifiableSet.unmodifiableSet(set);
}
// -----------------------------------------------------------------------
/**
* Gets an unmodifiable view as a Set.
*
* @return an unmodifiable set view
*/
public Set<E> asSet() {
return UnmodifiableSet.unmodifiableSet(set);
}
//-----------------------------------------------------------------------
/**
* Adds an element to the list if it is not already present.
* <p>
* <i>(Violation)</i>
* The <code>List</code> interface requires that this method returns
* <code>true</code> always. However this class may return <code>false</code>
* because of the <code>Set</code> behaviour.
*
* @param object the object to add
* @return true if object was added
*/
@Override
public boolean add(E object) {
// gets initial size
final int sizeBefore = size();
// -----------------------------------------------------------------------
/**
* Adds an element to the list if it is not already present.
* <p>
* <i>(Violation)</i> The <code>List</code> interface requires that this
* method returns <code>true</code> always. However this class may return
* <code>false</code> because of the <code>Set</code> behaviour.
*
* @param object
* the object to add
* @return true if object was added
*/
@Override
public boolean add(E object) {
// gets initial size
final int sizeBefore = size();
// adds element if unique
add(size(), object);
// adds element if unique
add(size(), object);
// compares sizes to detect if collection changed
return (sizeBefore != size());
}
// compares sizes to detect if collection changed
return (sizeBefore != size());
}
/**
* Adds an element to a specific index in the list if it is not already present.
* <p>
* <i>(Violation)</i>
* The <code>List</code> interface makes the assumption that the element is
* always inserted. This may not happen with this implementation.
*
* @param index the index to insert at
* @param object the object to add
*/
@Override
public void add(int index, E object) {
// adds element if it is not contained already
if (set.contains(object) == false) {
super.add(index, object);
set.add(object);
}
}
/**
* Adds an element to a specific index in the list if it is not already
* present.
* <p>
* <i>(Violation)</i> The <code>List</code> interface makes the assumption
* that the element is always inserted. This may not happen with this
* implementation.
*
* @param index
* the index to insert at
* @param object
* the object to add
*/
@Override
public void add(int index, E object) {
// adds element if it is not contained already
if (set.contains(object) == false) {
super.add(index, object);
set.add(object);
}
}
/**
* Adds a collection of objects to the end of the list avoiding duplicates.
* <p>
* Only elements that are not already in this list will be added, and
* duplicates from the specified collection will be ignored.
* <p>
* <i>(Violation)</i>
* The <code>List</code> interface makes the assumption that the elements
* are always inserted. This may not happen with this implementation.
*
* @param coll the collection to add in iterator order
* @return true if this collection changed
*/
@Override
public boolean addAll(Collection<? extends E> coll) {
return addAll(size(), coll);
}
/**
* Adds a collection of objects to the end of the list avoiding duplicates.
* <p>
* Only elements that are not already in this list will be added, and
* duplicates from the specified collection will be ignored.
* <p>
* <i>(Violation)</i> The <code>List</code> interface makes the assumption
* that the elements are always inserted. This may not happen with this
* implementation.
*
* @param coll
* the collection to add in iterator order
* @return true if this collection changed
*/
@Override
public boolean addAll(Collection<? extends E> coll) {
return addAll(size(), coll);
}
/**
* Adds a collection of objects a specific index in the list avoiding
* duplicates.
* <p>
* Only elements that are not already in this list will be added, and
* duplicates from the specified collection will be ignored.
* <p>
* <i>(Violation)</i>
* The <code>List</code> interface makes the assumption that the elements
* are always inserted. This may not happen with this implementation.
*
* @param index the index to insert at
* @param coll the collection to add in iterator order
* @return true if this collection changed
*/
@Override
public boolean addAll(int index, Collection<? extends E> coll) {
final List<E> temp = new ArrayList<E>();
for (E e : coll) {
if (set.add(e)) {
temp.add(e);
}
}
return super.addAll(index, temp);
}
/**
* Adds a collection of objects a specific index in the list avoiding
* duplicates.
* <p>
* Only elements that are not already in this list will be added, and
* duplicates from the specified collection will be ignored.
* <p>
* <i>(Violation)</i> The <code>List</code> interface makes the assumption
* that the elements are always inserted. This may not happen with this
* implementation.
*
* @param index
* the index to insert at
* @param coll
* the collection to add in iterator order
* @return true if this collection changed
*/
@Override
public boolean addAll(int index, Collection<? extends E> coll) {
final List<E> temp = new ArrayList<E>();
for (E e : coll) {
if (set.add(e)) {
temp.add(e);
}
}
return super.addAll(index, temp);
}
//-----------------------------------------------------------------------
/**
* Sets the value at the specified index avoiding duplicates.
* <p>
* The object is set into the specified index.
* Afterwards, any previous duplicate is removed
* If the object is not already in the list then a normal set occurs.
* If it is present, then the old version is removed.
*
* @param index the index to insert at
* @param object the object to set
* @return the previous object
*/
@Override
public E set(int index, E object) {
int pos = indexOf(object);
E removed = super.set(index, object);
// -----------------------------------------------------------------------
/**
* Sets the value at the specified index avoiding duplicates.
* <p>
* The object is set into the specified index. Afterwards, any previous
* duplicate is removed If the object is not already in the list then a
* normal set occurs. If it is present, then the old version is removed.
*
* @param index
* the index to insert at
* @param object
* the object to set
* @return the previous object
*/
@Override
public E set(int index, E object) {
int pos = indexOf(object);
E removed = super.set(index, object);
if (pos != -1 && pos != index) {
// the object is already in the uniq list
// (and it hasn't been swapped with itself)
super.remove(pos); // remove the duplicate by index
}
if (pos != -1 && pos != index) {
// the object is already in the uniq list
// (and it hasn't been swapped with itself)
super.remove(pos); // remove the duplicate by index
}
set.add(object); // add the new item to the unique set
set.remove(removed); // remove the item deleted by the set
set.add(object); // add the new item to the unique set
set.remove(removed); // remove the item deleted by the set
return removed; // return the item deleted by the set
}
return removed; // return the item deleted by the set
}
@Override
public boolean remove(Object object) {
boolean result = set.remove(object);
if (result) {
super.remove(object);
}
return result;
}
@Override
public boolean remove(Object object) {
boolean result = set.remove(object);
if (result) {
super.remove(object);
}
return result;
}
@Override
public E remove(int index) {
E result = super.remove(index);
set.remove(result);
return result;
}
@Override
public E remove(int index) {
E result = super.remove(index);
set.remove(result);
return result;
}
@Override
public boolean removeAll(Collection<?> coll) {
boolean result = false;
for (Iterator<?> it = coll.iterator(); it.hasNext();) {
result |= remove(it.next());
}
return result;
}
@Override
public boolean removeAll(Collection<?> coll) {
boolean result = false;
for (Iterator<?> it = coll.iterator(); it.hasNext();) {
result |= remove(it.next());
}
return result;
}
@Override
public boolean retainAll(Collection<?> coll) {
boolean result = super.retainAll(coll);
set.retainAll(coll);
return result;
}
@Override
public boolean retainAll(Collection<?> coll) {
Set<Object> setRetainAll = new HashSet<Object>();
for (Iterator<?> it = coll.iterator(); it.hasNext();) {
Object next = it.next();
if (set.contains(next)) {
setRetainAll.add(next);
}
}
if (setRetainAll.size() == set.size()) {
return false;
}
if (setRetainAll.size() == 0) {
clear();
} else {
for (Iterator<E> it = iterator(); it.hasNext();) {
if (!setRetainAll.contains(it.next())) {
it.remove();
}
}
}
return true;
}
@Override
public void clear() {
super.clear();
set.clear();
}
@Override
public void clear() {
super.clear();
set.clear();
}
@Override
public boolean contains(Object object) {
return set.contains(object);
}
@Override
public boolean contains(Object object) {
return set.contains(object);
}
@Override
public boolean containsAll(Collection<?> coll) {
return set.containsAll(coll);
}
@Override
public boolean containsAll(Collection<?> coll) {
return set.containsAll(coll);
}
@Override
public Iterator<E> iterator() {
return new SetListIterator<E>(super.iterator(), set);
}
@Override
public Iterator<E> iterator() {
return new SetListIterator<E>(super.iterator(), set);
}
@Override
public ListIterator<E> listIterator() {
return new SetListListIterator<E>(super.listIterator(), set);
}
@Override
public ListIterator<E> listIterator() {
return new SetListListIterator<E>(super.listIterator(), set);
}
@Override
public ListIterator<E> listIterator(int index) {
return new SetListListIterator<E>(super.listIterator(index), set);
}
@Override
public ListIterator<E> listIterator(int index) {
return new SetListListIterator<E>(super.listIterator(index), set);
}
@Override
public List<E> subList(int fromIndex, int toIndex) {
List<E> superSubList = super.subList(fromIndex, toIndex);
Set<E> subSet = createSetBasedOnList(set, superSubList);
return new SetUniqueList<E>(superSubList, subSet);
}
@Override
public List<E> subList(int fromIndex, int toIndex) {
List<E> superSubList = super.subList(fromIndex, toIndex);
Set<E> subSet = createSetBasedOnList(set, superSubList);
return new SetUniqueList<E>(superSubList, subSet);
}
/**
* Create a new {@link Set} with the same type as the provided {@code set}
* and populate it with all elements of {@code list}.
*
* @param set the {@link Set} to be used as return type, must not be null
* @param list the {@link List} to populate the {@link Set}
* @return a new {@link Set} populated with all elements of the provided {@link List}
*/
@SuppressWarnings("unchecked")
protected Set<E> createSetBasedOnList(Set<E> set, List<E> list) {
Set<E> subSet;
if (set.getClass().equals(HashSet.class)) {
subSet = new HashSet<E>(list.size());
} else {
try {
subSet = set.getClass().newInstance();
} catch (InstantiationException ie) {
subSet = new HashSet<E>();
} catch (IllegalAccessException iae) {
subSet = new HashSet<E>();
}
}
subSet.addAll(list);
return subSet;
}
/**
* Create a new {@link Set} with the same type as the provided {@code set}
* and populate it with all elements of {@code list}.
*
* @param set
* the {@link Set} to be used as return type, must not be null
* @param list
* the {@link List} to populate the {@link Set}
* @return a new {@link Set} populated with all elements of the provided
* {@link List}
*/
@SuppressWarnings("unchecked")
protected Set<E> createSetBasedOnList(Set<E> set, List<E> list) {
Set<E> subSet;
if (set.getClass().equals(HashSet.class)) {
subSet = new HashSet<E>(list.size());
} else {
try {
subSet = set.getClass().newInstance();
} catch (InstantiationException ie) {
subSet = new HashSet<E>();
} catch (IllegalAccessException iae) {
subSet = new HashSet<E>();
}
}
subSet.addAll(list);
return subSet;
}
//-----------------------------------------------------------------------
/**
* Inner class iterator.
*/
static class SetListIterator<E> extends AbstractIteratorDecorator<E> {
// -----------------------------------------------------------------------
/**
* Inner class iterator.
*/
static class SetListIterator<E> extends AbstractIteratorDecorator<E> {
protected final Set<E> set;
protected E last = null;
protected final Set<E> set;
protected E last = null;
protected SetListIterator(Iterator<E> it, Set<E> set) {
super(it);
this.set = set;
}
protected SetListIterator(Iterator<E> it, Set<E> set) {
super(it);
this.set = set;
}
@Override
public E next() {
last = super.next();
return last;
}
@Override
public E next() {
last = super.next();
return last;
}
@Override
public void remove() {
super.remove();
set.remove(last);
last = null;
}
}
@Override
public void remove() {
super.remove();
set.remove(last);
last = null;
}
}
/**
* Inner class iterator.
*/
static class SetListListIterator<E> extends AbstractListIteratorDecorator<E> {
/**
* Inner class iterator.
*/
static class SetListListIterator<E> extends
AbstractListIteratorDecorator<E> {
protected final Set<E> set;
protected E last = null;
protected final Set<E> set;
protected E last = null;
protected SetListListIterator(ListIterator<E> it, Set<E> set) {
super(it);
this.set = set;
}
protected SetListListIterator(ListIterator<E> it, Set<E> set) {
super(it);
this.set = set;
}
@Override
public E next() {
last = super.next();
return last;
}
@Override
public E next() {
last = super.next();
return last;
}
@Override
public E previous() {
last = super.previous();
return last;
}
@Override
public E previous() {
last = super.previous();
return last;
}
@Override
public void remove() {
super.remove();
set.remove(last);
last = null;
}
@Override
public void remove() {
super.remove();
set.remove(last);
last = null;
}
@Override
public void add(E object) {
if (set.contains(object) == false) {
super.add(object);
set.add(object);
}
}
@Override
public void add(E object) {
if (set.contains(object) == false) {
super.add(object);
set.add(object);
}
}
@Override
public void set(E object) {
throw new UnsupportedOperationException("ListIterator does not support set");
}
}
@Override
public void set(E object) {
throw new UnsupportedOperationException(
"ListIterator does not support set");
}
}
}

View File

@ -523,6 +523,79 @@ public class SetUniqueListTest<E> extends AbstractListTest<E> {
assertFalse(subUniqueList.contains("World")); // fails
}
@SuppressWarnings("unchecked")
public void testRetainAll() {
List<E> list = new ArrayList<E>(10);
SetUniqueList<E> uniqueList = SetUniqueList.setUniqueList(list);
for (int i = 0; i < 10; ++i) {
uniqueList.add((E)Integer.valueOf(i));
}
Collection<E> retained = new ArrayList<E>(5);
for (int i = 0; i < 5; ++i) {
retained.add((E)Integer.valueOf(i * 2));
}
assertTrue(uniqueList.retainAll(retained));
assertEquals(5, uniqueList.size());
assertTrue(uniqueList.contains(Integer.valueOf(0)));
assertTrue(uniqueList.contains(Integer.valueOf(2)));
assertTrue(uniqueList.contains(Integer.valueOf(4)));
assertTrue(uniqueList.contains(Integer.valueOf(6)));
assertTrue(uniqueList.contains(Integer.valueOf(8)));
}
@SuppressWarnings("unchecked")
public void testRetainAllWithInitialList() {
// initialized with empty list
List<E> list = new ArrayList<E>(10);
for (int i = 0; i < 5; ++i) {
list.add((E)Integer.valueOf(i));
}
SetUniqueList<E> uniqueList = SetUniqueList.setUniqueList(list);
for (int i = 5; i < 10; ++i) {
uniqueList.add((E)Integer.valueOf(i));
}
Collection<E> retained = new ArrayList<E>(5);
for (int i = 0; i < 5; ++i) {
retained.add((E)Integer.valueOf(i * 2));
}
assertTrue(uniqueList.retainAll(retained));
assertEquals(5, uniqueList.size());
assertTrue(uniqueList.contains(Integer.valueOf(0)));
assertTrue(uniqueList.contains(Integer.valueOf(2)));
assertTrue(uniqueList.contains(Integer.valueOf(4)));
assertTrue(uniqueList.contains(Integer.valueOf(6)));
assertTrue(uniqueList.contains(Integer.valueOf(8)));
}
/*
* test case for https://issues.apache.org/jira/browse/COLLECTIONS-427
*/
public void testRetainAllCollections427() {
int size = 50000;
ArrayList<Integer> list = new ArrayList<Integer>();
for (int i = 0; i < size; i++) {
list.add(i);
}
SetUniqueList<Integer> uniqueList = SetUniqueList.setUniqueList(list);
ArrayList<Integer> toRetain = new ArrayList<Integer>();
for (int i = size; i < 2*size; i++) {
toRetain.add(i);
}
long start = System.currentTimeMillis();
uniqueList.retainAll(toRetain);
long stop = System.currentTimeMillis();
// make sure retainAll completes under 5 seconds
// TODO if test is migrated to JUnit 4, add a Timeout rule.
// http://kentbeck.github.com/junit/javadoc/latest/org/junit/rules/Timeout.html
assertTrue((stop - start) < 5000);
}
@SuppressWarnings("serial")
class SetUniqueList307 extends SetUniqueList<E> {
public SetUniqueList307(List<E> list, Set<E> set) {