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.Objects;
026import java.util.Queue;
027import java.util.Set;
028
029import org.apache.commons.collections4.Bag;
030import org.apache.commons.collections4.MultiSet;
031import org.apache.commons.collections4.Predicate;
032import org.apache.commons.collections4.bag.HashBag;
033import org.apache.commons.collections4.bag.PredicatedBag;
034import org.apache.commons.collections4.functors.NotNullPredicate;
035import org.apache.commons.collections4.list.PredicatedList;
036import org.apache.commons.collections4.multiset.HashMultiSet;
037import org.apache.commons.collections4.multiset.PredicatedMultiSet;
038import org.apache.commons.collections4.queue.PredicatedQueue;
039import org.apache.commons.collections4.set.PredicatedSet;
040
041/**
042 * Decorates another {@link Collection} to validate that additions
043 * match a specified predicate.
044 * <p>
045 * This collection exists to provide validation for the decorated collection.
046 * It is normally created to decorate an empty collection.
047 * If an object cannot be added to the collection, an IllegalArgumentException is thrown.
048 * </p>
049 * <p>
050 * One usage would be to ensure that no null entries are added to the collection:
051 * </p>
052 * <pre>
053 * Collection coll = PredicatedCollection.predicatedCollection(new ArrayList(), NotNullPredicate.INSTANCE);
054 * </pre>
055 * <p>
056 * This class is Serializable from Commons Collections 3.1.
057 * </p>
058 *
059 * @param <E> the type of the elements in the collection
060 * @since 3.0
061 */
062public class PredicatedCollection<E> extends AbstractCollectionDecorator<E> {
063
064    /**
065     * Builder for creating predicated collections.
066     * <p>
067     * Create a Builder with a predicate to validate elements against, then add any elements
068     * to the builder. Elements that fail the predicate will be added to a rejected list.
069     * Finally, create or decorate a collection using the createPredicated[List,Set,Bag,Queue] methods.
070     * <p>
071     * An example:
072     * <pre>
073     *   Predicate&lt;String&gt; predicate = NotNullPredicate.notNullPredicate();
074     *   PredicatedCollectionBuilder&lt;String&gt; builder = PredicatedCollection.builder(predicate);
075     *   builder.add("item1");
076     *   builder.add(null);
077     *   builder.add("item2");
078     *   List&lt;String&gt; predicatedList = builder.createPredicatedList();
079     * </pre>
080     * <p>
081     * At the end of the code fragment above predicatedList is protected by the predicate supplied
082     * to the builder, and it contains item1 and item2.
083     * <p>
084     * More elements can be added to the builder once a predicated collection has been created,
085     * but these elements will not be reflected in already created collections.
086     *
087     * @param <E>  the element type
088     * @since 4.1
089     */
090    public static class Builder<E> {
091
092        /** The predicate to use. */
093        private final Predicate<? super E> predicate;
094
095        /** The buffer containing valid elements. */
096        private final List<E> accepted = new ArrayList<>();
097
098        /** The buffer containing rejected elements. */
099        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.test(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.test(object)) {
423            throw new IllegalArgumentException("Cannot add Object '" + object + "' - Predicate '" +
424                                               predicate + "' rejected it");
425        }
426    }
427
428}