    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}