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