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