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