[COLLECTIONS-530] Added Builder for PredicatedCollection. Thanks to Erik.

git-svn-id: https://svn.apache.org/repos/asf/commons/proper/collections/trunk@1654518 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Thomas Neidhart 2015-01-24 14:03:40 +00:00
parent 5d42c95c33
commit dd5e51e5b3
3 changed files with 416 additions and 2 deletions

View File

@ -22,6 +22,11 @@
<body> <body>
<release version="4.1" date="TBD" description=""> <release version="4.1" date="TBD" description="">
<action issue="COLLECTIONS-530" dev="tn" type="fix" due-to="Erik">
Added a Builder for "PredicatedCollection". Elements added to the builder
that fail the predicate will not throw an IllegalArgumentException. The builder
supports creating predicated lists, bags, sets and queues.
</action>
<action issue="COLLECTIONS-545" dev="tn" type="fix" due-to="Oswaldo Olivo"> <action issue="COLLECTIONS-545" dev="tn" type="fix" due-to="Oswaldo Olivo">
Documented runtime complexity of "CollectionUtils#removeAll(Collection, Collection). Documented runtime complexity of "CollectionUtils#removeAll(Collection, Collection).
</action> </action>

View File

@ -16,9 +16,23 @@
*/ */
package org.apache.commons.collections4.collection; package org.apache.commons.collections4.collection;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.Set;
import org.apache.commons.collections4.Bag;
import org.apache.commons.collections4.Predicate; import org.apache.commons.collections4.Predicate;
import org.apache.commons.collections4.bag.HashBag;
import org.apache.commons.collections4.bag.PredicatedBag;
import org.apache.commons.collections4.functors.NotNullPredicate;
import org.apache.commons.collections4.list.PredicatedList;
import org.apache.commons.collections4.queue.PredicatedQueue;
import org.apache.commons.collections4.set.PredicatedSet;
/** /**
* Decorates another {@link Collection} to validate that additions * Decorates another {@link Collection} to validate that additions
@ -28,8 +42,10 @@ import org.apache.commons.collections4.Predicate;
* It is normally created to decorate an empty collection. * It is normally created to decorate an empty collection.
* If an object cannot be added to the collection, an IllegalArgumentException is thrown. * If an object cannot be added to the collection, an IllegalArgumentException is thrown.
* <p> * <p>
* One usage would be to ensure that no null entries are added to the collection. * One usage would be to ensure that no null entries are added to the collection:
* <pre>Collection coll = PredicatedCollection.decorate(new ArrayList(), NotNullPredicate.INSTANCE);</pre> * <pre>
* Collection coll = PredicatedCollection.predicatedCollection(new ArrayList(), NotNullPredicate.INSTANCE);
* </pre>
* <p> * <p>
* This class is Serializable from Commons Collections 3.1. * This class is Serializable from Commons Collections 3.1.
* *
@ -45,6 +61,29 @@ public class PredicatedCollection<E> extends AbstractCollectionDecorator<E> {
/** The predicate to use */ /** The predicate to use */
protected final Predicate<? super E> predicate; protected final Predicate<? super E> predicate;
/**
* Returns a Builder with the given predicate.
*
* @param <E> the element type
* @param predicate the predicate to use
* @return a new Builder for predicated collections
* @since 4.1
*/
public static <E> Builder<E> builder(final Predicate<? super E> predicate) {
return new Builder<E>(predicate);
}
/**
* Returns a Builder with a NotNullPredicate.
*
* @param <E> the element type
* @return a new Builder for predicated collections that ignores null values.
* @since 4.1
*/
public static <E> Builder<E> notNullBuilder() {
return new Builder<E>(NotNullPredicate.<E>notNullPredicate());
}
/** /**
* Factory method to create a predicated (validating) collection. * Factory method to create a predicated (validating) collection.
* <p> * <p>
@ -135,4 +174,230 @@ public class PredicatedCollection<E> extends AbstractCollectionDecorator<E> {
return decorated().addAll(coll); return decorated().addAll(coll);
} }
/**
* Builder for creating predicated collections.
* <p>
* Create a Builder with a predicate to validate elements against, then add any elements
* to the builder. Elements that fail the predicate will be added to a rejected list.
* Finally create or decorate a collection using the createPredicated[List,Set,Bag,Queue] methods.
* <p>
* An example:
* <pre>
* Predicate&lt;String&gt; predicate = NotNullPredicate.notNullPredicate();
* PredicatedCollectionBuilder&lt;String&gt; builder = PredicatedCollection.builder(predicate);
* builder.add("item1");
* builder.add(null);
* builder.add("item2");
* List&lt;String&gt; predicatedList = builder.createPredicatedList();
* </pre>
* <p>
* At the end of the code fragment above predicatedList is protected by the predicate supplied
* to the builder and it contains item1 and item2.
* <p>
* More elements can be added to the builder once a predicated collection has been created,
* but these elements will not be reflected in already created collections.
*
* @param <E> the element type
* @since 4.1
*/
public static class Builder<E> {
/** The predicate to use. */
private final Predicate<? super E> predicate;
/** The buffer containing valid elements. */
private final List<E> accepted = new ArrayList<E>();
/** The buffer containing rejected elements. */
private final List<E> rejected = new ArrayList<E>();
// -----------------------------------------------------------------------
/**
* Constructs a PredicatedCollectionBuilder with the specified Predicate.
*
* @param predicate the predicate to use
* @throws IllegalArgumentException if predicate is null
*/
public Builder(final Predicate<? super E> predicate) {
if (predicate == null) {
throw new IllegalArgumentException("Predicate must not be null");
}
this.predicate = predicate;
}
/**
* Adds the item to the builder.
* <p>
* If the predicate is true, it is added to the list of accepted elements,
* otherwise it is added to the rejected list.
*
* @param item the element to add
* @return the PredicatedCollectionBuilder.
*/
public Builder<E> add(final E item) {
if (predicate.evaluate(item)) {
accepted.add(item);
} else {
rejected.add(item);
}
return this;
}
/**
* Adds all elements from the given collection to the builder.
* <p>
* All elements for which the predicate evaluates to true will be added to the
* list of accepted elements, otherwise they are added to the rejected list.
*
* @param items the elements to add to the builder
* @return the PredicatedCollectionBuilder.
*/
public Builder<E> addAll(final Collection<? extends E> items) {
if (items != null) {
for (E item : items) {
add(item);
}
}
return this;
}
/**
* Create a new predicated list filled with the accepted elements.
* <p>
* The builder is not modified by this method, so it is possible to create more collections
* or add more elements afterwards. Further changes will not propagate to the returned list.
*
* @return a new predicated list.
*/
public List<E> createPredicatedList() {
return createPredicatedList(new ArrayList<E>());
}
/**
* Decorates the given list with validating behavior using the predicate. All accepted elements
* are appended to the list. If the list already contains elements, they are validated.
* <p>
* The builder is not modified by this method, so it is possible to create more collections
* or add more elements afterwards. Further changes will not propagate to the returned list.
*
* @param list the List to decorate, must not be null
* @return the decorated list.
* @throws IllegalArgumentException if list is null or contains invalid elements
*/
public List<E> createPredicatedList(final List<E> list) {
if (list == null) {
throw new IllegalArgumentException("list must not be null");
}
final List<E> predicatedList = PredicatedList.predicatedList(list, predicate);
predicatedList.addAll(accepted);
return predicatedList;
}
/**
* Create a new predicated set filled with the accepted elements.
* <p>
* The builder is not modified by this method, so it is possible to create more collections
* or add more elements afterwards. Further changes will not propagate to the returned set.
*
* @return a new predicated set.
*/
public Set<E> createPredicatedSet() {
return createPredicatedSet(new HashSet<E>());
}
/**
* Decorates the given list with validating behavior using the predicate. All accepted elements
* are appended to the set. If the set already contains elements, they are validated.
* <p>
* The builder is not modified by this method, so it is possible to create more collections
* or add more elements afterwards. Further changes will not propagate to the returned set.
*
* @param set the set to decorate, must not be null
* @return the decorated set.
* @throws IllegalArgumentException if set is null or contains invalid elements
*/
public Set<E> createPredicatedSet(final Set<E> set) {
if (set == null) {
throw new IllegalArgumentException("set must not be null");
}
final PredicatedSet<E> predicatedSet = PredicatedSet.predicatedSet(set, predicate);
predicatedSet.addAll(accepted);
return predicatedSet;
}
/**
* Create a new predicated bag filled with the accepted elements.
* <p>
* The builder is not modified by this method, so it is possible to create more collections
* or add more elements afterwards. Further changes will not propagate to the returned bag.
*
* @return a new predicated bag.
*/
public Bag<E> createPredicatedBag() {
return createPredicatedBag(new HashBag<E>());
}
/**
* Decorates the given bag with validating behavior using the predicate. All accepted elements
* are appended to the bag. If the bag already contains elements, they are validated.
* <p>
* The builder is not modified by this method, so it is possible to create more collections
* or add more elements afterwards. Further changes will not propagate to the returned bag.
*
* @param bag the bag to decorate, must not be null
* @return the decorated bag.
* @throws IllegalArgumentException if bag is null or contains invalid elements
*/
public Bag<E> createPredicatedBag(final Bag<E> bag) {
if (bag == null) {
throw new IllegalArgumentException("bag must not be null");
}
final PredicatedBag<E> predicatedBag = PredicatedBag.predicatedBag(bag, predicate);
predicatedBag.addAll(accepted);
return predicatedBag;
}
/**
* Create a new predicated queue filled with the accepted elements.
* <p>
* The builder is not modified by this method, so it is possible to create more collections
* or add more elements afterwards. Further changes will not propagate to the returned queue.
*
* @return a new predicated queue.
*/
public Queue<E> createPredicatedQueue() {
return createPredicatedQueue(new LinkedList<E>());
}
/**
* Decorates the given queue with validating behavior using the predicate. All accepted elements
* are appended to the queue. If the queue already contains elements, they are validated.
* <p>
* The builder is not modified by this method, so it is possible to create more collections
* or add more elements afterwards. Further changes will not propagate to the returned queue.
*
* @param queue the queue to decorate, must not be null
* @return the decorated queue.
* @throws IllegalArgumentException if queue is null or contains invalid elements
*/
public Queue<E> createPredicatedQueue(final Queue<E> queue) {
if (queue == null) {
throw new IllegalArgumentException("queue must not be null");
}
final PredicatedQueue<E> predicatedQueue = PredicatedQueue.predicatedQueue(queue, predicate);
predicatedQueue.addAll(accepted);
return predicatedQueue;
}
/**
* Returns an unmodifiable collection containing all rejected elements.
*
* @return an unmodifiable collection
*/
public Collection<E> rejectedElements() {
return Collections.unmodifiableCollection(rejected);
}
}
} }

View File

@ -0,0 +1,144 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.collections4.collection;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Queue;
import java.util.Set;
import org.apache.commons.collections4.Bag;
import org.apache.commons.collections4.Predicate;
import org.junit.Assert;
import org.junit.Test;
/**
* Tests the PredicatedCollection.Builder class.
*
* @since 4.1
* @version $Id$
*/
public class PredicatedCollectionBuilderTest {
/**
* Verify that passing the Predicate means ending up in the buffer.
*/
@Test
public void addPass() {
PredicatedCollection.Builder<String> builder = PredicatedCollection.notNullBuilder();
builder.add("test");
Assert.assertEquals(builder.createPredicatedList().size(), 1);
}
/**
* Verify that failing the Predicate means NOT ending up in the buffer.
*/
@Test
public void addFail() {
PredicatedCollection.Builder<String> builder = PredicatedCollection.notNullBuilder();
builder.add((String) null);
Assert.assertTrue(builder.createPredicatedList().isEmpty());
Assert.assertEquals(1, builder.rejectedElements().size());
}
/**
* Verify that only items that pass the Predicate end up in the buffer.
*/
@Test
public void addAllPass() {
PredicatedCollection.Builder<String> builder = PredicatedCollection.notNullBuilder();
builder.addAll(Arrays.asList("test1", null, "test2"));
Assert.assertEquals(builder.createPredicatedList().size(), 2);
}
@Test
public void createPredicatedCollectionWithNotNullPredicate() {
PredicatedCollection.Builder<String> builder = PredicatedCollection.notNullBuilder();
builder.add("test1");
builder.add((String) null);
List<String> predicatedList = builder.createPredicatedList();
checkPredicatedCollection1(predicatedList);
Set<String> predicatedSet = builder.createPredicatedSet();
checkPredicatedCollection1(predicatedSet);
Bag<String> predicatedBag = builder.createPredicatedBag();
checkPredicatedCollection1(predicatedBag);
Queue<String> predicatedQueue = builder.createPredicatedQueue();
checkPredicatedCollection1(predicatedQueue);
}
private void checkPredicatedCollection1(final Collection<String> collection) {
Assert.assertEquals(1, collection.size());
collection.add("test2");
Assert.assertEquals(2, collection.size());
try {
collection.add(null);
Assert.fail("Expecting IllegalArgumentException for failing predicate!");
} catch (IllegalArgumentException iae) {
// expected
}
}
@Test
public void createPredicatedCollectionWithPredicate() {
OddPredicate p = new OddPredicate();
PredicatedCollection.Builder<Integer> builder = PredicatedCollection.builder(p);
builder.add(1);
builder.add(2);
builder.add(3);
List<Integer> predicatedList = builder.createPredicatedList();
checkPredicatedCollection2(predicatedList);
Set<Integer> predicatedSet = builder.createPredicatedSet();
checkPredicatedCollection2(predicatedSet);
Bag<Integer> predicatedBag = builder.createPredicatedBag();
checkPredicatedCollection2(predicatedBag);
Queue<Integer> predicatedQueue = builder.createPredicatedQueue();
checkPredicatedCollection2(predicatedQueue);
}
private void checkPredicatedCollection2(final Collection<Integer> collection) {
Assert.assertEquals(2, collection.size());
try {
collection.add(4);
Assert.fail("Expecting IllegalArgumentException for failing predicate!");
} catch (IllegalArgumentException iae) {
}
Assert.assertEquals(2, collection.size());
collection.add(5);
Assert.assertEquals(3, collection.size());
}
private static class OddPredicate implements Predicate<Integer> {
public boolean evaluate(Integer value) {
return value % 2 == 1;
}
}
}