    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}