Author: tn Date: Mon Jun 22 21:49:27 2015 New Revision: 1686948 URL: http://svn.apache.org/r1686948 Log: [COLLECTIONS-572] Add set operations to SetUtils.
Modified: commons/proper/collections/trunk/src/changes/changes.xml commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/SetUtils.java commons/proper/collections/trunk/src/test/java/org/apache/commons/collections4/SetUtilsTest.java Modified: commons/proper/collections/trunk/src/changes/changes.xml URL: http://svn.apache.org/viewvc/commons/proper/collections/trunk/src/changes/changes.xml?rev=1686948&r1=1686947&r2=1686948&view=diff ============================================================================== --- commons/proper/collections/trunk/src/changes/changes.xml (original) +++ commons/proper/collections/trunk/src/changes/changes.xml Mon Jun 22 21:49:27 2015 @@ -22,6 +22,10 @@ <body> <release version="4.1" date="TBD" description=""> + <action issue="COLLECTIONS-572" dev="tn" type="add"> + Added set operations to "SetUtils": union, difference, intersection and disjunction. + The operations return a view of the result that is backed by the input sets. + </action> <action issue="COLLECTIONS-570" dev="tn" type="update"> All constructors and static factory methods will now throw a "NullPointerException" if a required input argument is null. Previously sometimes a "IllegalArgumentException" was used. Modified: commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/SetUtils.java URL: http://svn.apache.org/viewvc/commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/SetUtils.java?rev=1686948&r1=1686947&r2=1686948&view=diff ============================================================================== --- commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/SetUtils.java (original) +++ commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/SetUtils.java Mon Jun 22 21:49:27 2015 @@ -16,9 +16,12 @@ */ package org.apache.commons.collections4; +import java.util.AbstractSet; import java.util.Collection; import java.util.Collections; +import java.util.HashSet; import java.util.IdentityHashMap; +import java.util.Iterator; import java.util.NavigableSet; import java.util.Set; import java.util.SortedSet; @@ -68,7 +71,7 @@ public class SetUtils { */ @SuppressWarnings("unchecked") // empty set is OK for any type public static <E> SortedSet<E> emptySortedSet() { - return (SortedSet<E>) EMPTY_SORTED_SET; + return EMPTY_SORTED_SET; } /** @@ -419,4 +422,223 @@ public class SetUtils { return TransformedNavigableSet.transformingNavigableSet(set, transformer); } + // Set operations + //----------------------------------------------------------------------- + + /** + * Returns a unmodifiable <b>view</b> of the union of the given {@link Set}s. + * <p> + * The returned view contains all elements of {@code a} and {@code b}. + * + * @param <E> the generic type that is able to represent the types contained + * in both input sets. + * @param a the first set, must not be null + * @param b the second set, must not be null + * @return a view of the union of the two set + * @throws NullPointerException if either input set is null + * @since 4.1 + */ + public static <E> SetView<E> union(final Set<? extends E> a, final Set<? extends E> b) { + if (a == null || b == null) { + throw new NullPointerException("Sets must not be null."); + } + + final SetView<E> bMinusA = difference(b, a); + + return new SetView<E>() { + @Override + public boolean contains(Object o) { + return a.contains(o) || b.contains(o); + } + + @Override + public Iterator<E> createIterator() { + return IteratorUtils.chainedIterator(a.iterator(), bMinusA.iterator()); + } + + @Override + public boolean isEmpty() { + return a.isEmpty() && b.isEmpty(); + } + + @Override + public int size() { + return a.size() + bMinusA.size(); + } + }; + } + + /** + * Returns a unmodifiable <b>view</b> containing the difference of the given + * {@link Set}s, denoted by {@code a \ b} (or {@code a - b}). + * <p> + * The returned view contains all elements of {@code a} that are not a member + * of {@code b}. + * + * @param <E> the generic type that is able to represent the types contained + * in both input sets. + * @param a the set to subtract from, must not be null + * @param b the set to subtract, must not be null + * @return a view of the relative complement of of the two sets + * @since 4.1 + */ + public static <E> SetView<E> difference(final Set<? extends E> a, final Set<? extends E> b) { + if (a == null || b == null) { + throw new NullPointerException("Sets must not be null."); + } + + final Predicate<E> notContainedInB = new Predicate<E>() { + @Override + public boolean evaluate(E object) { + return !b.contains(object); + } + }; + + return new SetView<E>() { + @Override + public boolean contains(Object o) { + return a.contains(o) && !b.contains(o); + } + + @Override + public Iterator<E> createIterator() { + return IteratorUtils.filteredIterator(a.iterator(), notContainedInB); + } + }; + } + + /** + * Returns a unmodifiable <b>view</b> of the intersection of the given {@link Set}s. + * <p> + * The returned view contains all elements that are members of both input sets + * ({@code a} and {@code b}). + * + * @param <E> the generic type that is able to represent the types contained + * in both input sets. + * @param a the first set, must not be null + * @param b the second set, must not be null + * @return a view of the intersection of the two sets + * @since 4.1 + */ + public static <E> SetView<E> intersection(final Set<? extends E> a, final Set<? extends E> b) { + if (a == null || b == null) { + throw new NullPointerException("Sets must not be null."); + } + + final Predicate<E> containedInB = new Predicate<E>() { + @Override + public boolean evaluate(E object) { + return b.contains(object); + } + }; + + return new SetView<E>() { + @Override + public boolean contains(Object o) { + return a.contains(o) && b.contains(o); + } + + @Override + public Iterator<E> createIterator() { + return IteratorUtils.filteredIterator(a.iterator(), containedInB); + } + }; + } + + /** + * Returns a unmodifiable <b>view</b> of the symmetric difference of the given + * {@link Set}s. + * <p> + * The returned view contains all elements of {@code a} and {@code b} that are + * not a member of the other set. + * <p> + * This is equivalent to {@code union(difference(a, b), difference(b, a))}. + * + * @param <E> the generic type that is able to represent the types contained + * in both input sets. + * @param a the first set, must not be null + * @param b the second set, must not be null + * @return a view of the symmetric difference of the two sets + * @since 4.1 + */ + public static <E> SetView<E> disjunction(final Set<? extends E> a, final Set<? extends E> b) { + if (a == null || b == null) { + throw new NullPointerException("Sets must not be null."); + } + + final SetView<E> aMinusB = difference(a, b); + final SetView<E> bMinusA = difference(b, a); + + return new SetView<E>() { + @Override + public boolean contains(Object o) { + return a.contains(o) ^ b.contains(o); + } + + @Override + public Iterator<E> createIterator() { + return IteratorUtils.chainedIterator(aMinusB.iterator(), bMinusA.iterator()); + } + + @Override + public boolean isEmpty() { + return aMinusB.isEmpty() && bMinusA.isEmpty(); + } + + @Override + public int size() { + return aMinusB.size() + bMinusA.size(); + } + }; + } + + /** + * An unmodifiable <b>view</b> of a set that may be backed by other sets. + * <p> + * If the decorated sets change, this view will change as well. The contents + * of this view can be transferred to another instance via the {@link #copyInto(Set)} + * and {@link #toSet()} methods. + * + * @param <E> the element type + * @since 4.1 + */ + public static abstract class SetView<E> extends AbstractSet<E> { + + @Override + public Iterator<E> iterator() { + return IteratorUtils.unmodifiableIterator(createIterator()); + } + + /** + * Return an iterator for this view; the returned iterator is + * not required to be unmodifiable. + * @return a new iterator for this view + */ + protected abstract Iterator<E> createIterator(); + + @Override + public int size() { + return IteratorUtils.size(iterator()); + } + + /** + * Copies the contents of this view into the provided set. + * + * @param set the set for copying the contents + */ + public <S extends Set<E>> void copyInto(final S set) { + CollectionUtils.addAll(set, this); + } + + /** + * Returns a new set containing the contents of this view. + * + * @return a new set containing all elements of this view + */ + public Set<E> toSet() { + final Set<E> set = new HashSet<E>(size()); + copyInto(set); + return set; + } + } } Modified: commons/proper/collections/trunk/src/test/java/org/apache/commons/collections4/SetUtilsTest.java URL: http://svn.apache.org/viewvc/commons/proper/collections/trunk/src/test/java/org/apache/commons/collections4/SetUtilsTest.java?rev=1686948&r1=1686947&r2=1686948&view=diff ============================================================================== --- commons/proper/collections/trunk/src/test/java/org/apache/commons/collections4/SetUtilsTest.java (original) +++ commons/proper/collections/trunk/src/test/java/org/apache/commons/collections4/SetUtilsTest.java Mon Jun 22 21:49:27 2015 @@ -16,14 +16,20 @@ */ package org.apache.commons.collections4; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.Set; +import org.apache.commons.collections4.SetUtils.SetView; import org.apache.commons.collections4.set.PredicatedSet; +import org.junit.Before; import org.junit.Test; /** @@ -33,9 +39,32 @@ import org.junit.Test; */ public class SetUtilsTest { + private Set<Integer> setA; + private Set<Integer> setB; + + @Before + public void setUp() { + setA = new HashSet<Integer>(); + setA.add(1); + setA.add(2); + setA.add(3); + setA.add(4); + setA.add(5); + + setB = new HashSet<Integer>(); + setB.add(3); + setB.add(4); + setB.add(5); + setB.add(6); + setB.add(7); + } + + //----------------------------------------------------------------------- + @Test public void testpredicatedSet() { final Predicate<Object> predicate = new Predicate<Object>() { + @Override public boolean evaluate(final Object o) { return o instanceof String; } @@ -112,4 +141,118 @@ public class SetUtilsTest { set.remove(a); assertEquals(2, set.size()); } + + @Test + public void union() { + final SetView<Integer> set = SetUtils.union(setA, setB); + assertEquals(7, set.size()); + assertTrue(set.containsAll(setA)); + assertTrue(set.containsAll(setB)); + + final Set<Integer> set2 = SetUtils.union(setA, SetUtils.<Integer>emptySet()); + assertEquals(setA, set2); + + try { + SetUtils.union(setA, null); + fail("Expecting NullPointerException"); + } catch (NullPointerException npe) { + // expected + } + + try { + SetUtils.union(null, setA); + fail("Expecting NullPointerException"); + } catch (NullPointerException npe) { + // expected + } + } + + @Test + public void difference() { + final SetView<Integer> set = SetUtils.difference(setA, setB); + assertEquals(2, set.size()); + assertTrue(set.contains(1)); + assertTrue(set.contains(2)); + for (Integer i : setB) { + assertFalse(set.contains(i)); + } + + final Set<Integer> set2 = SetUtils.difference(setA, SetUtils.<Integer>emptySet()); + assertEquals(setA, set2); + + try { + SetUtils.difference(setA, null); + fail("Expecting NullPointerException"); + } catch (NullPointerException npe) { + // expected + } + + try { + SetUtils.difference(null, setA); + fail("Expecting NullPointerException"); + } catch (NullPointerException npe) { + // expected + } + } + + @Test + public void intersection() { + final SetView<Integer> set = SetUtils.intersection(setA, setB); + assertEquals(3, set.size()); + assertTrue(set.contains(3)); + assertTrue(set.contains(4)); + assertTrue(set.contains(5)); + assertFalse(set.contains(1)); + assertFalse(set.contains(2)); + assertFalse(set.contains(6)); + assertFalse(set.contains(7)); + + final Set<Integer> set2 = SetUtils.intersection(setA, SetUtils.<Integer>emptySet()); + assertEquals(SetUtils.<Integer>emptySet(), set2); + + try { + SetUtils.intersection(setA, null); + fail("Expecting NullPointerException"); + } catch (NullPointerException npe) { + // expected + } + + try { + SetUtils.intersection(null, setA); + fail("Expecting NullPointerException"); + } catch (NullPointerException npe) { + // expected + } + } + + @Test + public void disjunction() { + final SetView<Integer> set = SetUtils.disjunction(setA, setB); + assertEquals(4, set.size()); + assertTrue(set.contains(1)); + assertTrue(set.contains(2)); + assertTrue(set.contains(6)); + assertTrue(set.contains(7)); + assertFalse(set.contains(3)); + assertFalse(set.contains(4)); + assertFalse(set.contains(5)); + + final Set<Integer> set2 = SetUtils.disjunction(setA, SetUtils.<Integer>emptySet()); + assertEquals(setA, set2); + + try { + SetUtils.disjunction(setA, null); + fail("Expecting NullPointerException"); + } catch (NullPointerException npe) { + // expected + } + + try { + SetUtils.disjunction(null, setA); + fail("Expecting NullPointerException"); + } catch (NullPointerException npe) { + // expected + } + } + }