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    *      https://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  
18  package org.apache.commons.collections4.collection;
19  
20  import java.io.Serializable;
21  import java.lang.reflect.Array;
22  import java.util.ArrayList;
23  import java.util.Collection;
24  import java.util.Iterator;
25  import java.util.List;
26  import java.util.Objects;
27  import java.util.function.Predicate;
28  import java.util.stream.Stream;
29  
30  import org.apache.commons.collections4.CollectionUtils;
31  import org.apache.commons.collections4.IterableUtils;
32  import org.apache.commons.collections4.iterators.EmptyIterator;
33  import org.apache.commons.collections4.iterators.IteratorChain;
34  import org.apache.commons.collections4.list.UnmodifiableList;
35  
36  /**
37   * Decorates a collection of other collections to provide a single unified view.
38   * <p>
39   * 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
40   * strategy is provided then add and remove are unsupported.
41   * </p>
42   *
43   * @param <E> The type of the elements in the collection.
44   * @since 3.0
45   */
46  public class CompositeCollection<E> implements Collection<E>, Serializable {
47  
48      /**
49       * Pluggable strategy to handle changes to the composite.
50       *
51       * @param <E> The element being held in the collection
52       */
53      public interface CollectionMutator<E> extends Serializable {
54  
55          /**
56           * Called when an object is to be added to the composite.
57           *
58           * @param composite   The CompositeCollection being changed.
59           * @param collections all of the Collection instances in this CompositeCollection.
60           * @param obj         The object being added.
61           * @return true if the collection is changed.
62           * @throws UnsupportedOperationException if add is unsupported.
63           * @throws ClassCastException            if the object cannot be added due to its type.
64           * @throws NullPointerException          if the object cannot be added because its null.
65           * @throws IllegalArgumentException      if the object cannot be added.
66           */
67          boolean add(CompositeCollection<E> composite, List<Collection<E>> collections, E obj);
68  
69          /**
70           * Called when a collection is to be added to the composite.
71           *
72           * @param composite   The CompositeCollection being changed.
73           * @param collections all of the Collection instances in this CompositeCollection.
74           * @param coll        The collection being added.
75           * @return true if the collection is changed.
76           * @throws UnsupportedOperationException if add is unsupported.
77           * @throws ClassCastException            if the object cannot be added due to its type.
78           * @throws NullPointerException          if the object cannot be added because its null.
79           * @throws IllegalArgumentException      if the object cannot be added.
80           */
81          boolean addAll(CompositeCollection<E> composite, List<Collection<E>> collections, Collection<? extends E> coll);
82  
83          /**
84           * Called when an object is to be removed to the composite.
85           *
86           * @param composite   The CompositeCollection being changed.
87           * @param collections all of the Collection instances in this CompositeCollection.
88           * @param obj         The object being removed.
89           * @return true if the collection is changed.
90           * @throws UnsupportedOperationException if removed is unsupported.
91           * @throws ClassCastException            if the object cannot be removed due to its type.
92           * @throws NullPointerException          if the object cannot be removed because its null.
93           * @throws IllegalArgumentException      if the object cannot be removed.
94           */
95          boolean remove(CompositeCollection<E> composite, List<Collection<E>> collections, Object obj);
96      }
97  
98      /** Serialization version. */
99      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 }