View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.collections4.collection;
18  
19  import java.io.Serializable;
20  import java.lang.reflect.Array;
21  import java.util.ArrayList;
22  import java.util.Collection;
23  import java.util.Iterator;
24  import java.util.List;
25  import java.util.Objects;
26  import java.util.function.Predicate;
27  
28  import org.apache.commons.collections4.CollectionUtils;
29  import org.apache.commons.collections4.iterators.EmptyIterator;
30  import org.apache.commons.collections4.iterators.IteratorChain;
31  import org.apache.commons.collections4.list.UnmodifiableList;
32  
33  /**
34   * Decorates a collection of other collections to provide a single unified view.
35   * <p>
36   * Changes made to this collection will actually be made on the decorated collection.
37   * Add and remove operations require the use of a pluggable strategy. If no
38   * strategy is provided then add and remove are unsupported.
39   * </p>
40   * @param <E> the type of the elements in the collection
41   * @since 3.0
42   */
43  public class CompositeCollection<E> implements Collection<E>, Serializable {
44  
45      /**
46       * Pluggable strategy to handle changes to the composite.
47       *
48       * @param <E> the element being held in the collection
49       */
50      public interface CollectionMutator<E> extends Serializable {
51  
52          /**
53           * Called when an object is to be added to the composite.
54           *
55           * @param composite  the CompositeCollection being changed
56           * @param collections  all of the Collection instances in this CompositeCollection
57           * @param obj  the object being added
58           * @return true if the collection is changed
59           * @throws UnsupportedOperationException if add is unsupported
60           * @throws ClassCastException if the object cannot be added due to its type
61           * @throws NullPointerException if the object cannot be added because its null
62           * @throws IllegalArgumentException if the object cannot be added
63           */
64          boolean add(CompositeCollection<E> composite, List<Collection<E>> collections, E obj);
65  
66          /**
67           * Called when a collection is to be added to the composite.
68           *
69           * @param composite  the CompositeCollection being changed
70           * @param collections  all of the Collection instances in this CompositeCollection
71           * @param coll  the collection being added
72           * @return true if the collection is changed
73           * @throws UnsupportedOperationException if add is unsupported
74           * @throws ClassCastException if the object cannot be added due to its type
75           * @throws NullPointerException if the object cannot be added because its null
76           * @throws IllegalArgumentException if the object cannot be added
77           */
78          boolean addAll(CompositeCollection<E> composite,
79                                List<Collection<E>> collections,
80                                Collection<? extends E> coll);
81  
82          /**
83           * Called when an object is to be removed to the composite.
84           *
85           * @param composite  the CompositeCollection being changed
86           * @param collections  all of the Collection instances in this CompositeCollection
87           * @param obj  the object being removed
88           * @return true if the collection is changed
89           * @throws UnsupportedOperationException if removed is unsupported
90           * @throws ClassCastException if the object cannot be removed due to its type
91           * @throws NullPointerException if the object cannot be removed because its null
92           * @throws IllegalArgumentException if the object cannot be removed
93           */
94          boolean remove(CompositeCollection<E> composite,
95                                List<Collection<E>> collections,
96                                Object obj);
97  
98      }
99  
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