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}