    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 *      https://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 */
    017
    018package org.apache.commons.collections4.collection;
    019
    020import java.io.Serializable;
    021import java.lang.reflect.Array;
    022import java.util.ArrayList;
    023import java.util.Collection;
    024import java.util.Iterator;
    025import java.util.List;
    026import java.util.Objects;
    027import java.util.function.Predicate;
    028import java.util.stream.Stream;
    029
    030import org.apache.commons.collections4.CollectionUtils;
    031import org.apache.commons.collections4.IterableUtils;
    032import org.apache.commons.collections4.iterators.EmptyIterator;
    033import org.apache.commons.collections4.iterators.IteratorChain;
    034import org.apache.commons.collections4.list.UnmodifiableList;
    035
    036/**
    037 * Decorates a collection of other collections to provide a single unified view.
    038 * <p>
    039 * Changes made to this collection will actually be made on the decorated collection. Add and remove operations require the use of a pluggable strategy. If no
    040 * strategy is provided then add and remove are unsupported.
    041 * </p>
    042 *
    043 * @param <E> The type of the elements in the collection.
    044 * @since 3.0
    045 */
    046public class CompositeCollection<E> implements Collection<E>, Serializable {
    047
    048    /**
    049     * Pluggable strategy to handle changes to the composite.
    050     *
    051     * @param <E> The element being held in the collection
    052     */
    053    public interface CollectionMutator<E> extends Serializable {
    054
    055        /**
    056         * Called when an object is to be added to the composite.
    057         *
    058         * @param composite   The CompositeCollection being changed.
    059         * @param collections all of the Collection instances in this CompositeCollection.
    060         * @param obj         The object being added.
    061         * @return true if the collection is changed.
    062         * @throws UnsupportedOperationException if add is unsupported.
    063         * @throws ClassCastException            if the object cannot be added due to its type.
    064         * @throws NullPointerException          if the object cannot be added because its null.
    065         * @throws IllegalArgumentException      if the object cannot be added.
    066         */
    067        boolean add(CompositeCollection<E> composite, List<Collection<E>> collections, E obj);
    068
    069        /**
    070         * Called when a collection is to be added to the composite.
    071         *
    072         * @param composite   The CompositeCollection being changed.
    073         * @param collections all of the Collection instances in this CompositeCollection.
    074         * @param coll        The collection being added.
    075         * @return true if the collection is changed.
    076         * @throws UnsupportedOperationException if add is unsupported.
    077         * @throws ClassCastException            if the object cannot be added due to its type.
    078         * @throws NullPointerException          if the object cannot be added because its null.
    079         * @throws IllegalArgumentException      if the object cannot be added.
    080         */
    081        boolean addAll(CompositeCollection<E> composite, List<Collection<E>> collections, Collection<? extends E> coll);
    082
    083        /**
    084         * Called when an object is to be removed to the composite.
    085         *
    086         * @param composite   The CompositeCollection being changed.
    087         * @param collections all of the Collection instances in this CompositeCollection.
    088         * @param obj         The object being removed.
    089         * @return true if the collection is changed.
    090         * @throws UnsupportedOperationException if removed is unsupported.
    091         * @throws ClassCastException            if the object cannot be removed due to its type.
    092         * @throws NullPointerException          if the object cannot be removed because its null.
    093         * @throws IllegalArgumentException      if the object cannot be removed.
    094         */
    095        boolean remove(CompositeCollection<E> composite, List<Collection<E>> collections, Object obj);
    096    }
    097
    098    /** Serialization version. */
    099    private static final long serialVersionUID = 8417515734108306801L;
    100
    101    /** CollectionMutator to handle changes to the collection. */
    102    private CollectionMutator<E> mutator;
    103
    104    /** Collections in the composite. */
    105    private final List<Collection<E>> all = new ArrayList<>();
    106
    107    /**
    108     * Constructs an empty CompositeCollection.
    109     */
    110    public CompositeCollection() {
    111    }
    112
    113    /**
    114     * Constructs a Composite Collection with one collection.
    115     *
    116     * @param compositeCollection The Collection to be appended to the composite.
    117     */
    118    public CompositeCollection(final Collection<E> compositeCollection) {
    119        addComposited(compositeCollection);
    120    }
    121
    122    /**
    123     * Constructs a Composite Collection with an array of collections.
    124     *
    125     * @param compositeCollections The collections to composite.
    126     */
    127    public CompositeCollection(final Collection<E>... compositeCollections) {
    128        addComposited(compositeCollections);
    129    }
    130
    131    /**
    132     * Constructs a Composite Collection with two collections.
    133     *
    134     * @param compositeCollection1 The Collection to be appended to the composite.
    135     * @param compositeCollection2 The Collection to be appended to the composite.
    136     */
    137    public CompositeCollection(final Collection<E> compositeCollection1, final Collection<E> compositeCollection2) {
    138        addComposited(compositeCollection1, compositeCollection2);
    139    }
    140
    141    /**
    142     * Adds an object to the collection, throwing UnsupportedOperationException unless a CollectionMutator strategy is specified.
    143     *
    144     * @param obj The object to add.
    145     * @return {@code true} if the collection was modified.
    146     * @throws UnsupportedOperationException if CollectionMutator hasn't been set.
    147     * @throws UnsupportedOperationException if add is unsupported.
    148     * @throws ClassCastException            if the object cannot be added due to its type.
    149     * @throws NullPointerException          if the object cannot be added because its null.
    150     * @throws IllegalArgumentException      if the object cannot be added.
    151     */
    152    @Override
    153    public boolean add(final E obj) {
    154        if (mutator == null) {
    155            throw new UnsupportedOperationException("add() is not supported on CompositeCollection without a CollectionMutator strategy");
    156        }
    157        return mutator.add(this, all, obj);
    158    }
    159
    160    /**
    161     * Adds a collection of elements to this collection, throwing UnsupportedOperationException unless a CollectionMutator strategy is specified.
    162     *
    163     * @param coll The collection to add.
    164     * @return true if the collection was modified.
    165     * @throws UnsupportedOperationException if CollectionMutator hasn't been set.
    166     * @throws UnsupportedOperationException if add is unsupported.
    167     * @throws ClassCastException            if the object cannot be added due to its type.
    168     * @throws NullPointerException          if the object cannot be added because its null.
    169     * @throws IllegalArgumentException      if the object cannot be added.
    170     */
    171    @Override
    172    public boolean addAll(final Collection<? extends E> coll) {
    173        if (mutator == null) {
    174            throw new UnsupportedOperationException("addAll() is not supported on CompositeCollection without a CollectionMutator strategy");
    175        }
    176        return mutator.addAll(this, all, coll);
    177    }
    178
    179    /**
    180     * Add these Collections to the list of collections in this composite.
    181     *
    182     * @param compositeCollection The Collection to be appended to the composite.
    183     */
    184    public void addComposited(final Collection<E> compositeCollection) {
    185        if (compositeCollection != null) {
    186            all.add(compositeCollection);
    187        }
    188    }
    189
    190    /**
    191     * Add these Collections to the list of collections in this composite.
    192     *
    193     * @param compositeCollections The Collections to be appended to the composite.
    194     */
    195    public void addComposited(final Collection<E>... compositeCollections) {
    196        Stream.of(compositeCollections).filter(Objects::nonNull).forEach(all::add);
    197    }
    198
    199    /**
    200     * Add these Collections to the list of collections in this composite.
    201     *
    202     * @param compositeCollection1 The Collection to be appended to the composite.
    203     * @param compositeCollection2 The Collection to be appended to the composite.
    204     */
    205    public void addComposited(final Collection<E> compositeCollection1, final Collection<E> compositeCollection2) {
    206        if (compositeCollection1 != null) {
    207            all.add(compositeCollection1);
    208        }
    209        if (compositeCollection2 != null) {
    210            all.add(compositeCollection2);
    211        }
    212    }
    213
    214    /**
    215     * Removes all of the elements from this collection.
    216     * <p>
    217     * This implementation calls {@code clear()} on each collection.
    218     * </p>
    219     *
    220     * @throws UnsupportedOperationException if clear is unsupported.
    221     */
    222    @Override
    223    public void clear() {
    224        all.forEach(Collection::clear);
    225    }
    226
    227    /**
    228     * Checks whether this composite collection contains the object.
    229     * <p>
    230     * This implementation calls {@code contains()} on each collection.
    231     * </p>
    232     *
    233     * @param obj The object to search for.
    234     * @return true if obj is contained in any of the contained collections.
    235     */
    236    @Override
    237    public boolean contains(final Object obj) {
    238        return all.stream().anyMatch(c -> c.contains(obj));
    239    }
    240
    241    /**
    242     * Checks whether this composite contains all the elements in the specified collection.
    243     * <p>
    244     * This implementation calls {@code contains()} for each element in the specified collection.
    245     * </p>
    246     *
    247     * @param coll The collection to check for.
    248     * @return true if all elements contained.
    249     */
    250    @Override
    251    public boolean containsAll(final Collection<?> coll) {
    252        return coll != null && coll.stream().allMatch(this::contains);
    253    }
    254
    255    /**
    256     * Gets the collections being decorated.
    257     *
    258     * @return Unmodifiable list of all collections in this composite.
    259     */
    260    public List<Collection<E>> getCollections() {
    261        return UnmodifiableList.unmodifiableList(all);
    262    }
    263
    264    /**
    265     * Gets the collection mutator to be used for this CompositeCollection.
    266     *
    267     * @return CollectionMutator&lt;E&gt;
    268     */
    269    protected CollectionMutator<E> getMutator() {
    270        return mutator;
    271    }
    272
    273    /**
    274     * Checks whether this composite collection is empty.
    275     * <p>
    276     * This implementation calls {@code isEmpty()} on each collection.
    277     * </p>
    278     *
    279     * @return true if all of the contained collections are empty
    280     */
    281    @Override
    282    public boolean isEmpty() {
    283        return all.stream().allMatch(Collection::isEmpty);
    284    }
    285
    286    /**
    287     * Gets an iterator over all the collections in this composite.
    288     * <p>
    289     * This implementation uses an {@code IteratorChain}.
    290     * </p>
    291     *
    292     * @return An {@code IteratorChain} instance which supports {@code remove()}. Iteration occurs over contained collections in the order they were added, but
    293     *         this behavior should not be relied upon.
    294     * @see IteratorChain
    295     */
    296    @Override
    297    public Iterator<E> iterator() {
    298        if (all.isEmpty()) {
    299            return EmptyIterator.<E>emptyIterator();
    300        }
    301        final IteratorChain<E> chain = new IteratorChain<>();
    302        all.forEach(item -> chain.addIterator(item.iterator()));
    303        return chain;
    304    }
    305
    306    /**
    307     * Removes an object from the collection, throwing UnsupportedOperationException unless a CollectionMutator strategy is specified.
    308     *
    309     * @param obj The object being removed.
    310     * @return true if the collection is changed.
    311     * @throws UnsupportedOperationException if removed is unsupported.
    312     * @throws ClassCastException            if the object cannot be removed due to its type.
    313     * @throws NullPointerException          if the object cannot be removed because its null.
    314     * @throws IllegalArgumentException      if the object cannot be removed.
    315     */
    316    @Override
    317    public boolean remove(final Object obj) {
    318        if (mutator == null) {
    319            throw new UnsupportedOperationException("remove() is not supported on CompositeCollection without a CollectionMutator strategy");
    320        }
    321        return mutator.remove(this, all, obj);
    322    }
    323
    324    /**
    325     * Removes the elements in the specified collection from this composite collection.
    326     * <p>
    327     * This implementation calls {@code removeAll} on each collection.
    328     * </p>
    329     *
    330     * @param coll The collection to remove.
    331     * @return true if the collection was modified.
    332     * @throws UnsupportedOperationException if removeAll is unsupported.
    333     */
    334    @Override
    335    public boolean removeAll(final Collection<?> coll) {
    336        if (CollectionUtils.isEmpty(coll)) {
    337            return false;
    338        }
    339        boolean changed = false;
    340        for (final Collection<E> item : all) {
    341            changed |= item.removeAll(coll);
    342        }
    343        return changed;
    344    }
    345
    346    /**
    347     * Removes a collection from the those being decorated in this composite.
    348     *
    349     * @param coll collection to be removed.
    350     */
    351    public void removeComposited(final Collection<E> coll) {
    352        all.remove(coll);
    353    }
    354
    355    /**
    356     * Removes all of the elements of this collection that satisfy the given predicate from this composite collection.
    357     * <p>
    358     * This implementation calls {@code removeIf} on each collection.
    359     * </p>
    360     *
    361     * @param filter A predicate which returns true for elements to be removed.
    362     * @return true if the collection was modified.
    363     * @throws UnsupportedOperationException if removeIf is unsupported.
    364     * @since 4.4
    365     */
    366    @Override
    367    public boolean removeIf(final Predicate<? super E> filter) {
    368        if (Objects.isNull(filter)) {
    369            return false;
    370        }
    371        boolean changed = false;
    372        for (final Collection<E> item : all) {
    373            changed |= item.removeIf(filter);
    374        }
    375        return changed;
    376    }
    377
    378    /**
    379     * Retains all the elements in the specified collection in this composite collection, removing all others.
    380     * <p>
    381     * This implementation calls {@code retainAll()} on each collection.
    382     * </p>
    383     *
    384     * @param coll The collection to remove.
    385     * @return true if the collection was modified.
    386     * @throws UnsupportedOperationException if retainAll is unsupported.
    387     */
    388    @Override
    389    public boolean retainAll(final Collection<?> coll) {
    390        boolean changed = false;
    391        if (coll != null) {
    392            for (final Collection<E> item : all) {
    393                changed |= item.retainAll(coll);
    394            }
    395        }
    396        return changed;
    397    }
    398
    399    /**
    400     * Specify a CollectionMutator strategy instance to handle changes.
    401     *
    402     * @param mutator The mutator to use
    403     */
    404    public void setMutator(final CollectionMutator<E> mutator) {
    405        this.mutator = mutator;
    406    }
    407
    408    /**
    409     * Gets the size of this composite collection.
    410     * <p>
    411     * This implementation calls {@code size()} on each collection.
    412     * </p>
    413     *
    414     * @return total number of elements in all contained containers, or {@code Integer.MAX_VALUE} if the total exceeds it.
    415     */
    416    @Override
    417    public int size() {
    418        return IterableUtils.sumSizesToInt(all);
    419    }
    420
    421    /**
    422     * Returns an array containing all of the elements in this composite.
    423     *
    424     * @return An object array of all the elements in the collection.
    425     */
    426    @Override
    427    public Object[] toArray() {
    428        final Object[] result = new Object[size()];
    429        int i = 0;
    430        for (final Iterator<E> it = iterator(); it.hasNext(); i++) {
    431            result[i] = it.next();
    432        }
    433        return result;
    434    }
    435
    436    /**
    437     * Returns an object array, populating the supplied array if possible. See {@code Collection} interface for full details.
    438     *
    439     * @param <T>   the type of the elements in the collection.
    440     * @param array The array to use, populating if possible.
    441     * @return An array of all the elements in the collection.
    442     */
    443    @Override
    444    @SuppressWarnings("unchecked")
    445    public <T> T[] toArray(final T[] array) {
    446        final int size = size();
    447        Object[] result = null;
    448        if (array.length >= size) {
    449            result = array;
    450        } else {
    451            result = (Object[]) Array.newInstance(array.getClass().getComponentType(), size);
    452        }
    453        int offset = 0;
    454        for (final Collection<E> item : all) {
    455            for (final E e : item) {
    456                result[offset++] = e;
    457            }
    458        }
    459        if (result.length > size) {
    460            result[size] = null;
    461        }
    462        return (T[]) result;
    463    }
    464
    465    /**
    466     * Returns a new collection containing all of the elements.
    467     *
    468     * @return A new ArrayList containing all of the elements in this composite. The new collection is <em>not</em> backed by this composite.
    469     */
    470    public Collection<E> toCollection() {
    471        return new ArrayList<>(this);
    472    }
    473}