[COLLECTIONS-529] Added methods removeAll and retainAll to CollectionUtils that use a custom Equator for equality checks. Thanks to Alexander Muthmann and Dipanjan Laha.

git-svn-id: https://svn.apache.org/repos/asf/commons/proper/collections/trunk@1598646 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Thomas Neidhart 2014-05-30 15:44:08 +00:00
parent bb0a4b6b03
commit 0a7871a141
3 changed files with 286 additions and 7 deletions

View File

@ -22,6 +22,10 @@
<body>
<release version="4.1" date="TBD" description="">
<action issue="COLLECTIONS-529" dev="tn" type="add" due-to="Alexander Muthmann, Dipanjan Laha">
Added methods "removeAll(...)" and "retainAll(...)" to "CollectionUtils" that perform
equality checks using the provided "Equator" object instead of "Object#equals()".
</action>
<action issue="COLLECTIONS-531" dev="tn" type="fix" due-to="Dipanjan Laha">
Use correct type bounds in
"CollectionUtils#isEqualCollection(Collection, Collection, Equator)" to

View File

@ -579,10 +579,10 @@ public class CollectionUtils {
* @since 4.0
*/
private static class EquatorWrapper<O> {
private final Equator<O> equator;
private final Equator<? super O> equator;
private final O object;
public EquatorWrapper(final Equator<O> equator, final O object) {
public EquatorWrapper(final Equator<? super O> equator, final O object) {
this.equator = equator;
this.object = object;
}
@ -1681,6 +1681,53 @@ public class CollectionUtils {
return ListUtils.retainAll(collection, retain);
}
/**
* Returns a collection containing all the elements in
* <code>collection</code> that are also in <code>retain</code>. The
* cardinality of an element <code>e</code> in the returned collection is
* the same as the cardinality of <code>e</code> in <code>collection</code>
* unless <code>retain</code> does not contain <code>e</code>, in which case
* the cardinality is zero. This method is useful if you do not wish to
* modify the collection <code>c</code> and thus cannot call
* <code>c.retainAll(retain);</code>.
* <p>
* Moreover this method uses an {@link Equator} instead of
* {@link Object#equals(Object)} to determine the equality of the elements
* in <code>collection</code> and <code>retain</code>. Hence this method is
* useful in cases where the equals behavior of an object needs to be
* modified without changing the object itself.
*
* @param <E> the type of object the {@link Collection} contains
* @param collection the collection whose contents are the target of the {@code retainAll} operation
* @param retain the collection containing the elements to be retained in the returned collection
* @param equator the Equator used for testing equality
* @return a <code>Collection</code> containing all the elements of <code>collection</code>
* that occur at least once in <code>retain</code> according to the <code>equator</code>
* @throws NullPointerException if any of the parameters is null
* @since 4.1
*/
public static <E> Collection<E> retainAll(final Iterable<E> collection,
final Iterable<? extends E> retain,
final Equator<? super E> equator) {
final Transformer<E, EquatorWrapper<E>> transformer = new Transformer<E, EquatorWrapper<E>>() {
public EquatorWrapper<E> transform(E input) {
return new EquatorWrapper<E>(equator, input);
}
};
final Set<EquatorWrapper<E>> retainSet =
collect(retain, transformer, new HashSet<EquatorWrapper<E>>());
final List<E> list = new ArrayList<E>();
for (final E element : collection) {
if (retainSet.contains(new EquatorWrapper<E>(equator, element))) {
list.add(element);
}
}
return list;
}
/**
* Removes the elements in <code>remove</code> from <code>collection</code>. That is, this
* method returns a collection containing all the elements in <code>c</code>
@ -1700,6 +1747,80 @@ public class CollectionUtils {
*/
public static <E> Collection<E> removeAll(final Collection<E> collection, final Collection<?> remove) {
return ListUtils.removeAll(collection, remove);
}
/**
* Removes all elements in <code>remove</code> from <code>collection</code>.
* That is, this method returns a collection containing all the elements in
* <code>collection</code> that are not in <code>remove</code>. The
* cardinality of an element <code>e</code> in the returned collection is
* the same as the cardinality of <code>e</code> in <code>collection</code>
* unless <code>remove</code> contains <code>e</code>, in which case the
* cardinality is zero. This method is useful if you do not wish to modify
* the collection <code>c</code> and thus cannot call
* <code>collection.removeAll(remove)</code>.
* <p>
* Moreover this method uses an {@link Equator} instead of
* {@link Object#equals(Object)} to determine the equality of the elements
* in <code>collection</code> and <code>remove</code>. Hence this method is
* useful in cases where the equals behavior of an object needs to be
* modified without changing the object itself.
*
* @param <E> the type of object the {@link Collection} contains
* @param collection the collection from which items are removed (in the returned collection)
* @param remove the items to be removed from the returned collection
* @param equator the Equator used for testing equality
* @return a <code>Collection</code> containing all the elements of <code>collection</code>
* except any element that if equal according to the <code>equator</code>
* @throws NullPointerException if any of the parameters is null
* @since 4.1
*/
public static <E> Collection<E> removeAll(final Iterable<E> collection,
final Iterable<? extends E> remove,
final Equator<? super E> equator) {
final Transformer<E, EquatorWrapper<E>> transformer = new Transformer<E, EquatorWrapper<E>>() {
public EquatorWrapper<E> transform(E input) {
return new EquatorWrapper<E>(equator, input);
}
};
final Set<EquatorWrapper<E>> removeSet =
collect(remove, transformer, new HashSet<EquatorWrapper<E>>());
final List<E> list = new ArrayList<E>();
for (final E element : collection) {
if (!removeSet.contains(new EquatorWrapper<E>(equator, element))) {
list.add(element);
}
}
return list;
}
/**
* This method checks, if any of the elements in <code>collection</code> is
* equal to <code>object</code>. Object equality is tested with an
* <code>equator</code> unlike <code>collection.contains(object)</code>
* which uses {@link Object#equals(Object)}.
*
* @param <E> the type of object the {@link Collection} contains
* @param collection the collection from which items are compared
* @param object the object to compare with the collection's entries
* @param equator the equator to use to check, if the item if equal to any
* of the collection's entries.
* @return true if <code>object</code> is in <code>collection</code>
* according to <code>equator</code>
* @throws NullPointerException if any parameter is null
* @since 4.1
*/
public static <E> boolean contains(final Collection<? extends E> collection, final E object,
final Equator<? super E> equator) {
for (final E obj : collection) {
if (equator.equate(obj, object)) {
return true;
}
}
return false;
}
//-----------------------------------------------------------------------

View File

@ -18,21 +18,38 @@ package org.apache.commons.collections4;
import static org.apache.commons.collections4.functors.EqualPredicate.equalPredicate;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.fail;
import java.util.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.Vector;
import org.apache.commons.collections4.queue.CircularFifoQueue;
import org.apache.commons.collections4.bag.HashBag;
import org.apache.commons.collections4.collection.PredicatedCollection;
import org.apache.commons.collections4.collection.SynchronizedCollection;
import org.apache.commons.collections4.collection.TransformedCollection;
import org.apache.commons.collections4.collection.UnmodifiableCollection;
import org.apache.commons.collections4.functors.DefaultEquator;
import org.apache.commons.collections4.queue.CircularFifoQueue;
import org.junit.Before;
import org.junit.Test;
@ -1719,16 +1736,153 @@ public class CollectionUtilsTest extends MockTestCase {
}
};
assertTrue(CollectionUtils.matchesAll(collectionA, lessThanFive));
Predicate<Integer> lessThanFour = new Predicate<Integer>() {
public boolean evaluate(Integer object) {
return object < 4;
}
};
assertFalse(CollectionUtils.matchesAll(collectionA, lessThanFour));
assertTrue(CollectionUtils.matchesAll(null, lessThanFour));
assertTrue(CollectionUtils.matchesAll(emptyCollection, lessThanFour));
}
@Test
public void testRemoveAllWithEquator() {
final List<String> base = new ArrayList<String>();
base.add("AC");
base.add("BB");
base.add("CA");
final List<String> remove = new ArrayList<String>();
remove.add("AA");
remove.add("CX");
remove.add("XZ");
// use an equator which compares the second letter only
final Collection<String> result = CollectionUtils.removeAll(base, remove, new Equator<String>() {
public boolean equate(String o1, String o2) {
return o1.charAt(1) == o2.charAt(1);
}
public int hash(String o) {
return o.charAt(1);
}
});
assertEquals(2, result.size());
assertTrue(result.contains("AC"));
assertTrue(result.contains("BB"));
assertFalse(result.contains("CA"));
assertEquals(3, base.size());
assertEquals(true, base.contains("AC"));
assertEquals(true, base.contains("BB"));
assertEquals(true, base.contains("CA"));
assertEquals(3, remove.size());
assertEquals(true, remove.contains("AA"));
assertEquals(true, remove.contains("CX"));
assertEquals(true, remove.contains("XZ"));
try {
CollectionUtils.removeAll(null, null, DefaultEquator.defaultEquator());
fail("expecting NullPointerException");
} catch (final NullPointerException npe) {
} // this is what we want
try {
CollectionUtils.removeAll(base, remove, null);
fail("expecting NullPointerException");
} catch (final NullPointerException npe) {
} // this is what we want
}
@Test
public void testRetainAllWithEquator() {
final List<String> base = new ArrayList<String>();
base.add("AC");
base.add("BB");
base.add("CA");
final List<String> retain = new ArrayList<String>();
retain.add("AA");
retain.add("CX");
retain.add("XZ");
// use an equator which compares the second letter only
final Collection<String> result = CollectionUtils.retainAll(base, retain, new Equator<String>() {
public boolean equate(String o1, String o2) {
return o1.charAt(1) == o2.charAt(1);
}
public int hash(String o) {
return o.charAt(1);
}
});
assertEquals(1, result.size());
assertTrue(result.contains("CA"));
assertFalse(result.contains("BB"));
assertFalse(result.contains("AC"));
assertEquals(3, base.size());
assertTrue(base.contains("AC"));
assertTrue(base.contains("BB"));
assertTrue(base.contains("CA"));
assertEquals(3, retain.size());
assertTrue(retain.contains("AA"));
assertTrue(retain.contains("CX"));
assertTrue(retain.contains("XZ"));
try {
CollectionUtils.retainAll(null, null, null);
fail("expecting NullPointerException");
} catch (final NullPointerException npe) {
} // this is what we want
try {
CollectionUtils.retainAll(base, retain, null);
fail("expecting NullPointerException");
} catch (final NullPointerException npe) {
} // this is what we want
}
@Test
public void testContainsWithEquator() {
final List<String> base = new ArrayList<String>();
base.add("AC");
base.add("BB");
base.add("CA");
final Equator<String> secondLetterEquator = new Equator<String>() {
public boolean equate(String o1, String o2) {
return o1.charAt(1) == o2.charAt(1);
}
public int hash(String o) {
return o.charAt(1);
}
};
assertFalse(base.contains("CC"));
assertTrue(CollectionUtils.contains(base, "AC", secondLetterEquator));
assertTrue(CollectionUtils.contains(base, "CC", secondLetterEquator));
assertFalse(CollectionUtils.contains(base, "CX", secondLetterEquator));
try {
CollectionUtils.contains(null, null, secondLetterEquator);
fail("expecting NullPointerException");
} catch (final NullPointerException npe) {
} // this is what we want
try {
CollectionUtils.contains(base, "AC", null);
fail("expecting NullPointerException");
} catch (final NullPointerException npe) {
} // this is what we want
}
}