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