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