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