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<E>
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 <em>not</em> backed by this composite.
503 */
504 public Collection<E> toCollection() {
505 return new ArrayList<>(this);
506 }
507
508 }
509