001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.collections4.collection;
018
019import java.util.ArrayList;
020import java.util.Collection;
021import java.util.Collections;
022import java.util.HashSet;
023import java.util.LinkedList;
024import java.util.List;
025import java.util.Queue;
026import java.util.Set;
027
028import org.apache.commons.collections4.Bag;
029import org.apache.commons.collections4.MultiSet;
030import org.apache.commons.collections4.Predicate;
031import org.apache.commons.collections4.bag.HashBag;
032import org.apache.commons.collections4.bag.PredicatedBag;
033import org.apache.commons.collections4.functors.NotNullPredicate;
034import org.apache.commons.collections4.list.PredicatedList;
035import org.apache.commons.collections4.multiset.HashMultiSet;
036import org.apache.commons.collections4.multiset.PredicatedMultiSet;
037import org.apache.commons.collections4.queue.PredicatedQueue;
038import org.apache.commons.collections4.set.PredicatedSet;
039
040/**
041 * Decorates another {@link Collection} to validate that additions
042 * match a specified predicate.
043 * <p>
044 * This collection exists to provide validation for the decorated collection.
045 * It is normally created to decorate an empty collection.
046 * If an object cannot be added to the collection, an IllegalArgumentException is thrown.
047 * </p>
048 * <p>
049 * One usage would be to ensure that no null entries are added to the collection:
050 * </p>
051 * <pre>
052 * Collection coll = PredicatedCollection.predicatedCollection(new ArrayList(), NotNullPredicate.INSTANCE);
053 * </pre>
054 * <p>
055 * This class is Serializable from Commons Collections 3.1.
056 * </p>
057 *
058 * @param <E> the type of the elements in the collection
059 * @since 3.0
060 */
061public class PredicatedCollection<E> extends AbstractCollectionDecorator<E> {
062
063    /** Serialization version */
064    private static final long serialVersionUID = -5259182142076705162L;
065
066    /** The predicate to use */
067    protected final Predicate<? super E> predicate;
068
069    /**
070     * Returns a Builder with the given predicate.
071     *
072     * @param <E>  the element type
073     * @param predicate  the predicate to use
074     * @return a new Builder for predicated collections
075     * @since 4.1
076     */
077    public static <E> Builder<E> builder(final Predicate<? super E> predicate) {
078        return new Builder<>(predicate);
079    }
080
081    /**
082     * Returns a Builder with a NotNullPredicate.
083     *
084     * @param <E>  the element type
085     * @return a new Builder for predicated collections that ignores null values.
086     * @since 4.1
087     */
088    public static <E> Builder<E> notNullBuilder() {
089        return new Builder<>(NotNullPredicate.<E>notNullPredicate());
090    }
091
092    /**
093     * Factory method to create a predicated (validating) collection.
094     * <p>
095     * If there are any elements already in the collection being decorated, they
096     * are validated.
097     *
098     * @param <T> the type of the elements in the collection
099     * @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}