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 package org.apache.commons.collections4.set;
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.HashSet;
24 import java.util.Iterator;
25 import java.util.List;
26 import java.util.Objects;
27 import java.util.Set;
28 import java.util.function.Predicate;
29
30 import org.apache.commons.collections4.CollectionUtils;
31 import org.apache.commons.collections4.IterableUtils;
32 import org.apache.commons.collections4.collection.CompositeCollection;
33 import org.apache.commons.collections4.iterators.EmptyIterator;
34 import org.apache.commons.collections4.iterators.IteratorChain;
35 import org.apache.commons.collections4.list.UnmodifiableList;
36
37 /**
38 * Decorates a set of other sets to provide a single unified view.
39 * <p>
40 * Changes made to this set will actually be made on the decorated set.
41 * Add operations require the use of a pluggable strategy.
42 * If no strategy is provided then add is unsupported.
43 * </p>
44 * <p>
45 * From version 4.0, this class does not extend
46 * {@link CompositeCollection CompositeCollection}
47 * anymore due to its input restrictions (only accepts Sets).
48 * See <a href="https://issues.apache.org/jira/browse/COLLECTIONS-424">COLLECTIONS-424</a>
49 * for more details.
50 * </p>
51 *
52 * @param <E> The type of the elements in this set
53 * @since 3.0
54 */
55 public class CompositeSet<E> implements Set<E>, Serializable {
56
57 /**
58 * Defines callbacks for mutation operations.
59 *
60 * @param <E> The type of the elements in this instance.
61 */
62 public interface SetMutator<E> extends Serializable {
63
64 /**
65 * Called when an object is to be added to the composite.
66 *
67 * @param composite The CompositeSet being changed
68 * @param sets all of the Set instances in this CompositeSet
69 * @param obj The object being added
70 * @return true if the collection is changed
71 * @throws UnsupportedOperationException if add is unsupported
72 * @throws ClassCastException if the object cannot be added due to its type
73 * @throws NullPointerException if the object cannot be added because its null
74 * @throws IllegalArgumentException if the object cannot be added
75 */
76 boolean add(CompositeSet<E> composite, List<Set<E>> sets, E obj);
77
78 /**
79 * Called when a collection is to be added to the composite.
80 *
81 * @param composite The CompositeSet being changed
82 * @param sets all of the Set instances in this CompositeSet
83 * @param coll The collection being added
84 * @return true if the collection is changed
85 * @throws UnsupportedOperationException if add is unsupported
86 * @throws ClassCastException if the object cannot be added due to its type
87 * @throws NullPointerException if the object cannot be added because its null
88 * @throws IllegalArgumentException if the object cannot be added
89 */
90 boolean addAll(CompositeSet<E> composite,
91 List<Set<E>> sets,
92 Collection<? extends E> coll);
93
94 /**
95 * Called when a Set is added to the CompositeSet and there is a
96 * collision between existing and added sets.
97 * <p>
98 * If {@code added} and {@code existing} still have any intersects
99 * after this method returns an IllegalArgumentException will be thrown.
100 *
101 * @param comp The CompositeSet being modified
102 * @param existing The Set already existing in the composite
103 * @param added The Set being added to the composite
104 * @param intersects The intersection of the existing and added sets
105 */
106 void resolveCollision(CompositeSet<E> comp,
107 Set<E> existing,
108 Set<E> added,
109 Collection<E> intersects);
110 }
111
112 /** Serialization version */
113 private static final long serialVersionUID = 5185069727540378940L;
114
115 /** SetMutator to handle changes to the collection */
116 private SetMutator<E> mutator;
117
118 /** Sets in the composite */
119 private final List<Set<E>> all = new ArrayList<>();
120
121 /**
122 * Creates an empty CompositeSet.
123 */
124 public CompositeSet() {
125 }
126
127 /**
128 * Creates a CompositeSet with just {@code set} composited.
129 *
130 * @param set The initial set in the composite
131 */
132 public CompositeSet(final Set<E> set) {
133 addComposited(set);
134 }
135
136 /**
137 * Creates a composite set with sets as the initial set of composited Sets.
138 *
139 * @param sets The initial sets in the composite
140 */
141 public CompositeSet(final Set<E>... sets) {
142 addComposited(sets);
143 }
144
145 /**
146 * Adds an object to the collection, throwing UnsupportedOperationException
147 * unless a SetMutator strategy is specified.
148 *
149 * @param obj The object to add
150 * @return {@code true} if the collection was modified
151 * @throws UnsupportedOperationException if SetMutator hasn't been set or 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 CompositeSet without a SetMutator strategy");
161 }
162 return mutator.add(this, all, obj);
163 }
164
165 /**
166 * Adds a collection of elements to this composite, throwing
167 * UnsupportedOperationException unless a SetMutator strategy is specified.
168 *
169 * @param coll The collection to add
170 * @return true if the composite was modified
171 * @throws UnsupportedOperationException if SetMutator hasn't been set or add is unsupported
172 * @throws ClassCastException if the object cannot be added due to its type
173 * @throws NullPointerException if the object cannot be added because its null
174 * @throws IllegalArgumentException if the object cannot be added
175 */
176 @Override
177 public boolean addAll(final Collection<? extends E> coll) {
178 if (mutator == null) {
179 throw new UnsupportedOperationException(
180 "addAll() is not supported on CompositeSet without a SetMutator strategy");
181 }
182 return mutator.addAll(this, all, coll);
183 }
184
185 /**
186 * Adds a Set to this composite.
187 *
188 * @param set The set to add
189 * @throws IllegalArgumentException if a SetMutator is set, but fails to resolve a collision
190 * @throws UnsupportedOperationException if there is no SetMutator set
191 * @see SetMutator
192 */
193 public synchronized void addComposited(final Set<E> set) {
194 if (set != null) {
195 for (final Set<E> existingSet : getSets()) {
196 final Collection<E> intersects = CollectionUtils.intersection(existingSet, set);
197 if (!intersects.isEmpty()) {
198 if (mutator == null) {
199 throw new UnsupportedOperationException(
200 "Collision adding composited set with no SetMutator set");
201 }
202 getMutator().resolveCollision(this, existingSet, set, intersects);
203 if (!CollectionUtils.intersection(existingSet, set).isEmpty()) {
204 throw new IllegalArgumentException(
205 "Attempt to add illegal entry unresolved by SetMutator.resolveCollision()");
206 }
207 }
208 }
209 all.add(set);
210 }
211 }
212
213 /**
214 * Adds these Sets to the list of sets in this composite
215 *
216 * @param sets The Sets to be appended to the composite
217 */
218 public void addComposited(final Set<E>... sets) {
219 if (sets != null) {
220 for (final Set<E> set : sets) {
221 addComposited(set);
222 }
223 }
224 }
225
226 /**
227 * Adds these Sets to the list of sets in this composite.
228 *
229 * @param set1 The first Set to be appended to the composite
230 * @param set2 The second Set to be appended to the composite
231 */
232 public void addComposited(final Set<E> set1, final Set<E> set2) {
233 addComposited(set1);
234 addComposited(set2);
235 }
236
237 /**
238 * Removes all of the elements from this composite set.
239 * <p>
240 * This implementation calls {@code clear()} on each set.
241 *
242 * @throws UnsupportedOperationException if clear is unsupported
243 */
244 @Override
245 public void clear() {
246 for (final Collection<E> coll : all) {
247 coll.clear();
248 }
249 }
250
251 /**
252 * Checks whether this composite set contains the object.
253 * <p>
254 * This implementation calls {@code contains()} on each set.
255 *
256 * @param obj The object to search for
257 * @return true if obj is contained in any of the contained sets
258 */
259 @Override
260 public boolean contains(final Object obj) {
261 for (final Set<E> item : all) {
262 if (item.contains(obj)) {
263 return true;
264 }
265 }
266 return false;
267 }
268
269 /**
270 * Checks whether this composite contains all the elements in the specified collection.
271 * <p>
272 * This implementation calls {@code contains()} for each element in the
273 * specified collection.
274 *
275 * @param coll The collection to check for
276 * @return true if all elements contained
277 */
278 @Override
279 public boolean containsAll(final Collection<?> coll) {
280 if (coll == null) {
281 return false;
282 }
283 for (final Object item : coll) {
284 if (!contains(item)) {
285 return false;
286 }
287 }
288 return true;
289 }
290
291 /**
292 * {@inheritDoc}
293 *
294 * @see java.util.Set#equals
295 */
296 @Override
297 public boolean equals(final Object obj) {
298 if (obj instanceof Set) {
299 final Set<?> set = (Set<?>) obj;
300 return set.size() == this.size() && set.containsAll(this);
301 }
302 return false;
303 }
304
305 /**
306 * Gets the set mutator to be used for this CompositeSet.
307 *
308 * @return The set mutator
309 */
310 protected SetMutator<E> getMutator() {
311 return mutator;
312 }
313
314 /**
315 * Gets the sets being decorated.
316 *
317 * @return Unmodifiable list of all sets in this composite.
318 */
319 public List<Set<E>> getSets() {
320 return UnmodifiableList.unmodifiableList(all);
321 }
322
323 /**
324 * {@inheritDoc}
325 *
326 * @see java.util.Set#hashCode
327 */
328 @Override
329 public int hashCode() {
330 int code = 0;
331 for (final E e : this) {
332 code += e == null ? 0 : e.hashCode();
333 }
334 return code;
335 }
336
337 /**
338 * Checks whether this composite set is empty.
339 * <p>
340 * This implementation calls {@code isEmpty()} on each set.
341 *
342 * @return true if all of the contained sets are empty
343 */
344 @Override
345 public boolean isEmpty() {
346 for (final Set<E> item : all) {
347 if (!item.isEmpty()) {
348 return false;
349 }
350 }
351 return true;
352 }
353
354 /**
355 * Gets an iterator over all the sets in this composite.
356 * <p>
357 * This implementation uses an {@code IteratorChain}.
358 *
359 * @return An {@code IteratorChain} instance which supports
360 * {@code remove()}. Iteration occurs over contained collections in
361 * the order they were added, but this behavior should not be relied upon.
362 * @see IteratorChain
363 */
364 @Override
365 public Iterator<E> iterator() {
366 if (all.isEmpty()) {
367 return EmptyIterator.<E>emptyIterator();
368 }
369 final IteratorChain<E> chain = new IteratorChain<>();
370 all.forEach(item -> chain.addIterator(item.iterator()));
371 return chain;
372 }
373
374 /**
375 * If a {@code CollectionMutator} is defined for this CompositeSet then this
376 * method will be called anyway.
377 *
378 * @param obj object to be removed
379 * @return true if the object is removed, false otherwise
380 */
381 @Override
382 public boolean remove(final Object obj) {
383 for (final Set<E> set : getSets()) {
384 if (set.contains(obj)) {
385 return set.remove(obj);
386 }
387 }
388 return false;
389 }
390
391 /**
392 * Removes the elements in the specified collection from this composite set.
393 * <p>
394 * This implementation calls {@code removeAll} on each collection.
395 *
396 * @param coll The collection to remove
397 * @return true if the composite was modified
398 * @throws UnsupportedOperationException if removeAll is unsupported
399 */
400 @Override
401 public boolean removeAll(final Collection<?> coll) {
402 if (CollectionUtils.isEmpty(coll)) {
403 return false;
404 }
405 boolean changed = false;
406 for (final Collection<E> item : all) {
407 changed |= item.removeAll(coll);
408 }
409 return changed;
410 }
411
412 /**
413 * Removes a set from those being decorated in this composite.
414 *
415 * @param set set to be removed
416 */
417 public void removeComposited(final Set<E> set) {
418 all.remove(set);
419 }
420
421 /**
422 * @since 4.4
423 */
424 @Override
425 public boolean removeIf(final Predicate<? super E> filter) {
426 if (Objects.isNull(filter)) {
427 return false;
428 }
429 boolean changed = false;
430 for (final Collection<E> item : all) {
431 changed |= item.removeIf(filter);
432 }
433 return changed;
434 }
435
436 /**
437 * Retains all the elements in the specified collection in this composite set,
438 * removing all others.
439 * <p>
440 * This implementation calls {@code retainAll()} on each collection.
441 *
442 * @param coll The collection to remove
443 * @return true if the composite was modified
444 * @throws UnsupportedOperationException if retainAll is unsupported
445 */
446 @Override
447 public boolean retainAll(final Collection<?> coll) {
448 boolean changed = false;
449 for (final Collection<E> item : all) {
450 changed |= item.retainAll(coll);
451 }
452 return changed;
453 }
454
455 /**
456 * Specify a SetMutator strategy instance to handle changes.
457 *
458 * @param mutator The mutator to use
459 */
460 public void setMutator(final SetMutator<E> mutator) {
461 this.mutator = mutator;
462 }
463
464 /**
465 * Gets the size of this composite set.
466 * <p>
467 * This implementation calls {@code size()} on each set.
468 *
469 * @return total number of elements in all contained containers, or
470 * {@code Integer.MAX_VALUE} if the total exceeds it
471 */
472 @Override
473 public int size() {
474 return IterableUtils.sumSizesToInt(all);
475 }
476
477 /**
478 * Returns an array containing all of the elements in this composite.
479 *
480 * @return An object array of all the elements in the collection
481 */
482 @Override
483 public Object[] toArray() {
484 final Object[] result = new Object[size()];
485 int i = 0;
486 for (final Iterator<E> it = iterator(); it.hasNext(); i++) {
487 result[i] = it.next();
488 }
489 return result;
490 }
491
492 /**
493 * Returns an object array, populating the supplied array if possible.
494 * See {@code Collection} interface for full details.
495 *
496 * @param <T> the type of the elements in the collection
497 * @param array The array to use, populating if possible
498 * @return An array of all the elements in the collection
499 */
500 @Override
501 @SuppressWarnings("unchecked")
502 public <T> T[] toArray(final T[] array) {
503 final int size = size();
504 Object[] result = null;
505 if (array.length >= size) {
506 result = array;
507 } else {
508 result = (Object[]) Array.newInstance(array.getClass().getComponentType(), size);
509 }
510
511 int offset = 0;
512 for (final Collection<E> item : all) {
513 for (final E e : item) {
514 result[offset++] = e;
515 }
516 }
517 if (result.length > size) {
518 result[size] = null;
519 }
520 return (T[]) result;
521 }
522
523 /**
524 * Returns a new Set containing all of the elements.
525 *
526 * @return A new HashSet containing all of the elements in this composite.
527 * The new collection is <em>not</em> backed by this composite.
528 */
529 public Set<E> toSet() {
530 return new HashSet<>(this);
531 }
532 }