From dd5e51e5b31f36b66431ea6e4b5a2a6bc4dcab34 Mon Sep 17 00:00:00 2001 From: Thomas Neidhart Date: Sat, 24 Jan 2015 14:03:40 +0000 Subject: [PATCH] [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 --- src/changes/changes.xml | 5 + .../collection/PredicatedCollection.java | 269 +++++++++++++++++- .../PredicatedCollectionBuilderTest.java | 144 ++++++++++ 3 files changed, 416 insertions(+), 2 deletions(-) create mode 100644 src/test/java/org/apache/commons/collections4/collection/PredicatedCollectionBuilderTest.java diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 67c2030f5..df4fe22ed 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -22,6 +22,11 @@ + + 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. + Documented runtime complexity of "CollectionUtils#removeAll(Collection, Collection). diff --git a/src/main/java/org/apache/commons/collections4/collection/PredicatedCollection.java b/src/main/java/org/apache/commons/collections4/collection/PredicatedCollection.java index 71d80e8b3..7df27fc21 100644 --- a/src/main/java/org/apache/commons/collections4/collection/PredicatedCollection.java +++ b/src/main/java/org/apache/commons/collections4/collection/PredicatedCollection.java @@ -16,9 +16,23 @@ */ package org.apache.commons.collections4.collection; +import java.util.ArrayList; 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.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 @@ -28,8 +42,10 @@ import org.apache.commons.collections4.Predicate; * It is normally created to decorate an empty collection. * If an object cannot be added to the collection, an IllegalArgumentException is thrown. *

- * One usage would be to ensure that no null entries are added to the collection. - *

Collection coll = PredicatedCollection.decorate(new ArrayList(), NotNullPredicate.INSTANCE);
+ * One usage would be to ensure that no null entries are added to the collection: + *
+ * Collection coll = PredicatedCollection.predicatedCollection(new ArrayList(), NotNullPredicate.INSTANCE);
+ * 
*

* This class is Serializable from Commons Collections 3.1. * @@ -45,6 +61,29 @@ public class PredicatedCollection extends AbstractCollectionDecorator { /** The predicate to use */ protected final Predicate predicate; + /** + * Returns a Builder with the given predicate. + * + * @param the element type + * @param predicate the predicate to use + * @return a new Builder for predicated collections + * @since 4.1 + */ + public static Builder builder(final Predicate predicate) { + return new Builder(predicate); + } + + /** + * Returns a Builder with a NotNullPredicate. + * + * @param the element type + * @return a new Builder for predicated collections that ignores null values. + * @since 4.1 + */ + public static Builder notNullBuilder() { + return new Builder(NotNullPredicate.notNullPredicate()); + } + /** * Factory method to create a predicated (validating) collection. *

@@ -135,4 +174,230 @@ public class PredicatedCollection extends AbstractCollectionDecorator { return decorated().addAll(coll); } + /** + * Builder for creating predicated collections. + *

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

+ * An example: + *

+     *   Predicate<String> predicate = NotNullPredicate.notNullPredicate();
+     *   PredicatedCollectionBuilder<String> builder = PredicatedCollection.builder(predicate);
+     *   builder.add("item1");
+     *   builder.add(null);
+     *   builder.add("item2");
+     *   List<String> predicatedList = builder.createPredicatedList();
+     * 
+ *

+ * At the end of the code fragment above predicatedList is protected by the predicate supplied + * to the builder and it contains item1 and item2. + *

+ * 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 the element type + * @since 4.1 + */ + public static class Builder { + + /** The predicate to use. */ + private final Predicate predicate; + + /** The buffer containing valid elements. */ + private final List accepted = new ArrayList(); + + /** The buffer containing rejected elements. */ + private final List rejected = new ArrayList(); + + // ----------------------------------------------------------------------- + /** + * Constructs a PredicatedCollectionBuilder with the specified Predicate. + * + * @param predicate the predicate to use + * @throws IllegalArgumentException if predicate is null + */ + public Builder(final Predicate predicate) { + if (predicate == null) { + throw new IllegalArgumentException("Predicate must not be null"); + } + this.predicate = predicate; + } + + /** + * Adds the item to the builder. + *

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

+ * 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 addAll(final Collection items) { + if (items != null) { + for (E item : items) { + add(item); + } + } + return this; + } + + /** + * Create a new predicated list filled with the accepted elements. + *

+ * 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 createPredicatedList() { + return createPredicatedList(new ArrayList()); + } + + /** + * 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. + *

+ * 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 createPredicatedList(final List list) { + if (list == null) { + throw new IllegalArgumentException("list must not be null"); + } + final List predicatedList = PredicatedList.predicatedList(list, predicate); + predicatedList.addAll(accepted); + return predicatedList; + } + + /** + * Create a new predicated set filled with the accepted elements. + *

+ * 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 createPredicatedSet() { + return createPredicatedSet(new HashSet()); + } + + /** + * 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. + *

+ * 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 createPredicatedSet(final Set set) { + if (set == null) { + throw new IllegalArgumentException("set must not be null"); + } + final PredicatedSet predicatedSet = PredicatedSet.predicatedSet(set, predicate); + predicatedSet.addAll(accepted); + return predicatedSet; + } + + /** + * Create a new predicated bag filled with the accepted elements. + *

+ * 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 createPredicatedBag() { + return createPredicatedBag(new HashBag()); + } + + /** + * 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. + *

+ * 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 createPredicatedBag(final Bag bag) { + if (bag == null) { + throw new IllegalArgumentException("bag must not be null"); + } + final PredicatedBag predicatedBag = PredicatedBag.predicatedBag(bag, predicate); + predicatedBag.addAll(accepted); + return predicatedBag; + } + + /** + * Create a new predicated queue filled with the accepted elements. + *

+ * 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 createPredicatedQueue() { + return createPredicatedQueue(new LinkedList()); + } + + /** + * 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. + *

+ * 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 createPredicatedQueue(final Queue queue) { + if (queue == null) { + throw new IllegalArgumentException("queue must not be null"); + } + final PredicatedQueue predicatedQueue = PredicatedQueue.predicatedQueue(queue, predicate); + predicatedQueue.addAll(accepted); + return predicatedQueue; + } + + /** + * Returns an unmodifiable collection containing all rejected elements. + * + * @return an unmodifiable collection + */ + public Collection rejectedElements() { + return Collections.unmodifiableCollection(rejected); + } + + } + } diff --git a/src/test/java/org/apache/commons/collections4/collection/PredicatedCollectionBuilderTest.java b/src/test/java/org/apache/commons/collections4/collection/PredicatedCollectionBuilderTest.java new file mode 100644 index 000000000..c1196e6f8 --- /dev/null +++ b/src/test/java/org/apache/commons/collections4/collection/PredicatedCollectionBuilderTest.java @@ -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 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 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 builder = PredicatedCollection.notNullBuilder(); + builder.addAll(Arrays.asList("test1", null, "test2")); + Assert.assertEquals(builder.createPredicatedList().size(), 2); + } + + @Test + public void createPredicatedCollectionWithNotNullPredicate() { + PredicatedCollection.Builder builder = PredicatedCollection.notNullBuilder(); + builder.add("test1"); + builder.add((String) null); + + List predicatedList = builder.createPredicatedList(); + checkPredicatedCollection1(predicatedList); + + Set predicatedSet = builder.createPredicatedSet(); + checkPredicatedCollection1(predicatedSet); + + Bag predicatedBag = builder.createPredicatedBag(); + checkPredicatedCollection1(predicatedBag); + + Queue predicatedQueue = builder.createPredicatedQueue(); + checkPredicatedCollection1(predicatedQueue); + } + + private void checkPredicatedCollection1(final Collection 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 builder = PredicatedCollection.builder(p); + + builder.add(1); + builder.add(2); + builder.add(3); + + List predicatedList = builder.createPredicatedList(); + checkPredicatedCollection2(predicatedList); + + Set predicatedSet = builder.createPredicatedSet(); + checkPredicatedCollection2(predicatedSet); + + Bag predicatedBag = builder.createPredicatedBag(); + checkPredicatedCollection2(predicatedBag); + + Queue predicatedQueue = builder.createPredicatedQueue(); + checkPredicatedCollection2(predicatedQueue); + } + + private void checkPredicatedCollection2(final Collection 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 { + public boolean evaluate(Integer value) { + return value % 2 == 1; + } + } +}