View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.collections4.collection;
18  
19  import java.util.ArrayList;
20  import java.util.Collection;
21  import java.util.Collections;
22  import java.util.HashSet;
23  import java.util.LinkedList;
24  import java.util.List;
25  import java.util.Objects;
26  import java.util.Queue;
27  import java.util.Set;
28  
29  import org.apache.commons.collections4.Bag;
30  import org.apache.commons.collections4.MultiSet;
31  import org.apache.commons.collections4.Predicate;
32  import org.apache.commons.collections4.bag.HashBag;
33  import org.apache.commons.collections4.bag.PredicatedBag;
34  import org.apache.commons.collections4.functors.NotNullPredicate;
35  import org.apache.commons.collections4.list.PredicatedList;
36  import org.apache.commons.collections4.multiset.HashMultiSet;
37  import org.apache.commons.collections4.multiset.PredicatedMultiSet;
38  import org.apache.commons.collections4.queue.PredicatedQueue;
39  import org.apache.commons.collections4.set.PredicatedSet;
40  
41  /**
42   * Decorates another {@link Collection} to validate that additions
43   * match a specified predicate.
44   * <p>
45   * This collection exists to provide validation for the decorated collection.
46   * It is normally created to decorate an empty collection.
47   * If an object cannot be added to the collection, an IllegalArgumentException is thrown.
48   * </p>
49   * <p>
50   * One usage would be to ensure that no null entries are added to the collection:
51   * </p>
52   * <pre>
53   * Collection coll = PredicatedCollection.predicatedCollection(new ArrayList(), NotNullPredicate.INSTANCE);
54   * </pre>
55   * <p>
56   * This class is Serializable from Commons Collections 3.1.
57   * </p>
58   *
59   * @param <E> the type of the elements in the collection
60   * @since 3.0
61   */
62  public class PredicatedCollection<E> extends AbstractCollectionDecorator<E> {
63  
64      /**
65       * Builder for creating predicated collections.
66       * <p>
67       * Create a Builder with a predicate to validate elements against, then add any elements
68       * to the builder. Elements that fail the predicate will be added to a rejected list.
69       * Finally, create or decorate a collection using the createPredicated[List,Set,Bag,Queue] methods.
70       * <p>
71       * An example:
72       * <pre>
73       *   Predicate&lt;String&gt; predicate = NotNullPredicate.notNullPredicate();
74       *   PredicatedCollectionBuilder&lt;String&gt; builder = PredicatedCollection.builder(predicate);
75       *   builder.add("item1");
76       *   builder.add(null);
77       *   builder.add("item2");
78       *   List&lt;String&gt; predicatedList = builder.createPredicatedList();
79       * </pre>
80       * <p>
81       * At the end of the code fragment above predicatedList is protected by the predicate supplied
82       * to the builder, and it contains item1 and item2.
83       * <p>
84       * More elements can be added to the builder once a predicated collection has been created,
85       * but these elements will not be reflected in already created collections.
86       *
87       * @param <E>  the element type
88       * @since 4.1
89       */
90      public static class Builder<E> {
91  
92          /** The predicate to use. */
93          private final Predicate<? super E> predicate;
94  
95          /** The buffer containing valid elements. */
96          private final List<E> accepted = new ArrayList<>();
97  
98          /** The buffer containing rejected elements. */
99          private final List<E> rejected = new ArrayList<>();
100 
101         /**
102          * Constructs a PredicatedCollectionBuilder with the specified Predicate.
103          *
104          * @param predicate  the predicate to use
105          * @throws NullPointerException if predicate is null
106          */
107         public Builder(final Predicate<? super E> predicate) {
108             this.predicate = Objects.requireNonNull(predicate, "predicate");
109         }
110 
111         /**
112          * Adds the item to the builder.
113          * <p>
114          * If the predicate is true, it is added to the list of accepted elements,
115          * otherwise it is added to the rejected list.
116          *
117          * @param item  the element to add
118          * @return the PredicatedCollectionBuilder.
119          */
120         public Builder<E> add(final E item) {
121             if (predicate.evaluate(item)) {
122                 accepted.add(item);
123             } else {
124                 rejected.add(item);
125             }
126             return this;
127         }
128 
129         /**
130          * Adds all elements from the given collection to the builder.
131          * <p>
132          * All elements for which the predicate evaluates to true will be added to the
133          * list of accepted elements, otherwise they are added to the rejected list.
134          *
135          * @param items  the elements to add to the builder
136          * @return the PredicatedCollectionBuilder.
137          */
138         public Builder<E> addAll(final Collection<? extends E> items) {
139             if (items != null) {
140                 for (final E item : items) {
141                     add(item);
142                 }
143             }
144             return this;
145         }
146 
147         /**
148          * Create a new predicated bag filled with the accepted elements.
149          * <p>
150          * The builder is not modified by this method, so it is possible to create more collections
151          * or add more elements afterwards. Further changes will not propagate to the returned bag.
152          *
153          * @return a new predicated bag.
154          */
155         public Bag<E> createPredicatedBag() {
156             return createPredicatedBag(new HashBag<>());
157         }
158 
159         /**
160          * Decorates the given bag with validating behavior using the predicate. All accepted elements
161          * are appended to the bag. If the bag already contains elements, they are validated.
162          * <p>
163          * The builder is not modified by this method, so it is possible to create more collections
164          * or add more elements afterwards. Further changes will not propagate to the returned bag.
165          *
166          * @param bag  the bag to decorate, must not be null
167          * @return the decorated bag.
168          * @throws NullPointerException if bag is null
169          * @throws IllegalArgumentException if bag contains invalid elements
170          */
171         public Bag<E> createPredicatedBag(final Bag<E> bag) {
172             Objects.requireNonNull(bag, "bag");
173             final PredicatedBag<E> predicatedBag = PredicatedBag.predicatedBag(bag, predicate);
174             predicatedBag.addAll(accepted);
175             return predicatedBag;
176         }
177 
178         /**
179          * Create a new predicated list filled with the accepted elements.
180          * <p>
181          * The builder is not modified by this method, so it is possible to create more collections
182          * or add more elements afterwards. Further changes will not propagate to the returned list.
183          *
184          * @return a new predicated list.
185          */
186         public List<E> createPredicatedList() {
187             return createPredicatedList(new ArrayList<>());
188         }
189 
190         /**
191          * Decorates the given list with validating behavior using the predicate. All accepted elements
192          * are appended to the list. If the list already contains elements, they are validated.
193          * <p>
194          * The builder is not modified by this method, so it is possible to create more collections
195          * or add more elements afterwards. Further changes will not propagate to the returned list.
196          *
197          * @param list  the List to decorate, must not be null
198          * @return the decorated list.
199          * @throws NullPointerException if list is null
200          * @throws IllegalArgumentException if list contains invalid elements
201          */
202         public List<E> createPredicatedList(final List<E> list) {
203             Objects.requireNonNull(list, "list");
204             final List<E> predicatedList = PredicatedList.predicatedList(list, predicate);
205             predicatedList.addAll(accepted);
206             return predicatedList;
207         }
208 
209         /**
210          * Create a new predicated multiset filled with the accepted elements.
211          * <p>
212          * The builder is not modified by this method, so it is possible to create more collections
213          * or add more elements afterwards. Further changes will not propagate to the returned multiset.
214          *
215          * @return a new predicated multiset.
216          */
217         public MultiSet<E> createPredicatedMultiSet() {
218             return createPredicatedMultiSet(new HashMultiSet<>());
219         }
220 
221         /**
222          * Decorates the given multiset with validating behavior using the predicate. All accepted elements
223          * are appended to the multiset. If the multiset already contains elements, they are validated.
224          * <p>
225          * The builder is not modified by this method, so it is possible to create more collections
226          * or add more elements afterwards. Further changes will not propagate to the returned multiset.
227          *
228          * @param multiset  the multiset to decorate, must not be null
229          * @return the decorated multiset.
230          * @throws NullPointerException if multiset is null
231          * @throws IllegalArgumentException if multiset contains invalid elements
232          */
233         public MultiSet<E> createPredicatedMultiSet(final MultiSet<E> multiset) {
234             Objects.requireNonNull(multiset, "multiset");
235             final PredicatedMultiSet<E> predicatedMultiSet =
236                     PredicatedMultiSet.predicatedMultiSet(multiset, predicate);
237             predicatedMultiSet.addAll(accepted);
238             return predicatedMultiSet;
239         }
240 
241         /**
242          * Create a new predicated queue filled with the accepted elements.
243          * <p>
244          * The builder is not modified by this method, so it is possible to create more collections
245          * or add more elements afterwards. Further changes will not propagate to the returned queue.
246          *
247          * @return a new predicated queue.
248          */
249         public Queue<E> createPredicatedQueue() {
250             return createPredicatedQueue(new LinkedList<>());
251         }
252 
253         /**
254          * Decorates the given queue with validating behavior using the predicate. All accepted elements
255          * are appended to the queue. If the queue already contains elements, they are validated.
256          * <p>
257          * The builder is not modified by this method, so it is possible to create more collections
258          * or add more elements afterwards. Further changes will not propagate to the returned queue.
259          *
260          * @param queue  the queue to decorate, must not be null
261          * @return the decorated queue.
262          * @throws NullPointerException if queue is null
263          * @throws IllegalArgumentException if queue contains invalid elements
264          */
265         public Queue<E> createPredicatedQueue(final Queue<E> queue) {
266             Objects.requireNonNull(queue, "queue");
267             final PredicatedQueue<E> predicatedQueue = PredicatedQueue.predicatedQueue(queue, predicate);
268             predicatedQueue.addAll(accepted);
269             return predicatedQueue;
270         }
271 
272         /**
273          * Create a new predicated set filled with the accepted elements.
274          * <p>
275          * The builder is not modified by this method, so it is possible to create more collections
276          * or add more elements afterwards. Further changes will not propagate to the returned set.
277          *
278          * @return a new predicated set.
279          */
280         public Set<E> createPredicatedSet() {
281             return createPredicatedSet(new HashSet<>());
282         }
283 
284         /**
285          * Decorates the given list with validating behavior using the predicate. All accepted elements
286          * are appended to the set. If the set already contains elements, they are validated.
287          * <p>
288          * The builder is not modified by this method, so it is possible to create more collections
289          * or add more elements afterwards. Further changes will not propagate to the returned set.
290          *
291          * @param set  the set to decorate, must not be null
292          * @return the decorated set.
293          * @throws NullPointerException if set is null
294          * @throws IllegalArgumentException if set contains invalid elements
295          */
296         public Set<E> createPredicatedSet(final Set<E> set) {
297             Objects.requireNonNull(set, "set");
298             final PredicatedSet<E> predicatedSet = PredicatedSet.predicatedSet(set, predicate);
299             predicatedSet.addAll(accepted);
300             return predicatedSet;
301         }
302 
303         /**
304          * Returns an unmodifiable collection containing all rejected elements.
305          *
306          * @return an unmodifiable collection
307          */
308         public Collection<E> rejectedElements() {
309             return Collections.unmodifiableCollection(rejected);
310         }
311 
312     }
313 
314     /** Serialization version */
315     private static final long serialVersionUID = -5259182142076705162L;
316 
317     /**
318      * Returns a Builder with the given predicate.
319      *
320      * @param <E>  the element type
321      * @param predicate  the predicate to use
322      * @return a new Builder for predicated collections
323      * @since 4.1
324      */
325     public static <E> Builder<E> builder(final Predicate<? super E> predicate) {
326         return new Builder<>(predicate);
327     }
328 
329     /**
330      * Returns a Builder with a NotNullPredicate.
331      *
332      * @param <E>  the element type
333      * @return a new Builder for predicated collections that ignores null values.
334      * @since 4.1
335      */
336     public static <E> Builder<E> notNullBuilder() {
337         return new Builder<>(NotNullPredicate.<E>notNullPredicate());
338     }
339 
340     /**
341      * Factory method to create a predicated (validating) collection.
342      * <p>
343      * If there are any elements already in the collection being decorated, they
344      * are validated.
345      *
346      * @param <T> the type of the elements in the collection
347      * @param coll  the collection to decorate, must not be null
348      * @param predicate  the predicate to use for validation, must not be null
349      * @return a new predicated collection
350      * @throws NullPointerException if collection or predicate is null
351      * @throws IllegalArgumentException if the collection contains invalid elements
352      * @since 4.0
353      */
354     public static <T> PredicatedCollection<T> predicatedCollection(final Collection<T> coll,
355                                                                    final Predicate<? super T> predicate) {
356         return new PredicatedCollection<>(coll, predicate);
357     }
358 
359     /** The predicate to use */
360     protected final Predicate<? super E> predicate;
361 
362     /**
363      * Constructor that wraps (not copies).
364      * <p>
365      * If there are any elements already in the collection being decorated, they
366      * are validated.
367      *
368      * @param collection  the collection to decorate, must not be null
369      * @param predicate  the predicate to use for validation, must not be null
370      * @throws NullPointerException if collection or predicate is null
371      * @throws IllegalArgumentException if the collection contains invalid elements
372      */
373     protected PredicatedCollection(final Collection<E> collection, final Predicate<? super E> predicate) {
374         super(collection);
375         this.predicate = Objects.requireNonNull(predicate, "predicate");
376         for (final E item : collection) {
377             validate(item);
378         }
379     }
380 
381     /**
382      * Override to validate the object being added to ensure it matches
383      * the predicate.
384      *
385      * @param object  the object being added
386      * @return the result of adding to the underlying collection
387      * @throws IllegalArgumentException if the add is invalid
388      */
389     @Override
390     public boolean add(final E object) {
391         validate(object);
392         return decorated().add(object);
393     }
394 
395     /**
396      * Override to validate the objects being added to ensure they match
397      * the predicate. If anyone fails, no update is made to the underlying
398      * collection.
399      *
400      * @param coll  the collection being added
401      * @return the result of adding to the underlying collection
402      * @throws IllegalArgumentException if the add is invalid
403      */
404     @Override
405     public boolean addAll(final Collection<? extends E> coll) {
406         for (final E item : coll) {
407             validate(item);
408         }
409         return decorated().addAll(coll);
410     }
411 
412     /**
413      * Validates the object being added to ensure it matches the predicate.
414      * <p>
415      * The predicate itself should not throw an exception, but return false to
416      * indicate that the object cannot be added.
417      *
418      * @param object  the object being added
419      * @throws IllegalArgumentException if the add is invalid
420      */
421     protected void validate(final E object) {
422         if (!predicate.evaluate(object)) {
423             throw new IllegalArgumentException("Cannot add Object '" + object + "' - Predicate '" +
424                                                predicate + "' rejected it");
425         }
426     }
427 
428 }