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<E>
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 }