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