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