001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.collections4.set;
018
019import java.io.Serializable;
020import java.lang.reflect.Array;
021import java.util.ArrayList;
022import java.util.Collection;
023import java.util.HashSet;
024import java.util.Iterator;
025import java.util.List;
026import java.util.Objects;
027import java.util.Set;
028import java.util.function.Predicate;
029
030import org.apache.commons.collections4.CollectionUtils;
031import org.apache.commons.collections4.iterators.EmptyIterator;
032import org.apache.commons.collections4.iterators.IteratorChain;
033import org.apache.commons.collections4.list.UnmodifiableList;
034
035/**
036 * Decorates a set of other sets to provide a single unified view.
037 * <p>
038 * Changes made to this set will actually be made on the decorated set.
039 * Add operations require the use of a pluggable strategy.
040 * If no strategy is provided then add is unsupported.
041 * </p>
042 * <p>
043 * From version 4.0, this class does not extend
044 * {@link org.apache.commons.collections4.collection.CompositeCollection CompositeCollection}
045 * anymore due to its input restrictions (only accepts Sets).
046 * See <a href="https://issues.apache.org/jira/browse/COLLECTIONS-424">COLLECTIONS-424</a>
047 * for more details.
048 * </p>
049 *
050 * @param <E> the type of the elements in this set
051 * @since 3.0
052 */
053public class CompositeSet<E> implements Set<E>, Serializable {
054
055    /**
056     * Defines callbacks for mutation operations.
057     */
058    public interface SetMutator<E> extends Serializable {
059
060        /**
061         * Called when an object is to be added to the composite.
062         *
063         * @param composite  the CompositeSet being changed
064         * @param sets  all of the Set instances in this CompositeSet
065         * @param obj  the object being added
066         * @return true if the collection is changed
067         * @throws UnsupportedOperationException if add is unsupported
068         * @throws ClassCastException if the object cannot be added due to its type
069         * @throws NullPointerException if the object cannot be added because its null
070         * @throws IllegalArgumentException if the object cannot be added
071         */
072        boolean add(CompositeSet<E> composite, List<Set<E>> sets, E obj);
073
074        /**
075         * Called when a collection is to be added to the composite.
076         *
077         * @param composite  the CompositeSet being changed
078         * @param sets  all of the Set instances in this CompositeSet
079         * @param coll  the collection being added
080         * @return true if the collection is changed
081         * @throws UnsupportedOperationException if add is unsupported
082         * @throws ClassCastException if the object cannot be added due to its type
083         * @throws NullPointerException if the object cannot be added because its null
084         * @throws IllegalArgumentException if the object cannot be added
085         */
086        boolean addAll(CompositeSet<E> composite,
087                              List<Set<E>> sets,
088                              Collection<? extends E> coll);
089
090        /**
091         * Called when a Set is added to the CompositeSet and there is a
092         * collision between existing and added sets.
093         * <p>
094         * If {@code added} and {@code existing} still have any intersects
095         * after this method returns an IllegalArgumentException will be thrown.
096         *
097         * @param comp  the CompositeSet being modified
098         * @param existing  the Set already existing in the composite
099         * @param added  the Set being added to the composite
100         * @param intersects  the intersection of the existing and added sets
101         */
102        void resolveCollision(CompositeSet<E> comp,
103                                     Set<E> existing,
104                                     Set<E> added,
105                                     Collection<E> intersects);
106    }
107
108    /** Serialization version */
109    private static final long serialVersionUID = 5185069727540378940L;
110
111    /** SetMutator to handle changes to the collection */
112    private SetMutator<E> mutator;
113
114    /** Sets in the composite */
115    private final List<Set<E>> all = new ArrayList<>();
116
117    /**
118     * Creates an empty CompositeSet.
119     */
120    public CompositeSet() {
121    }
122
123    /**
124     * Creates a CompositeSet with just {@code set} composited.
125     *
126     * @param set  the initial set in the composite
127     */
128    public CompositeSet(final Set<E> set) {
129        addComposited(set);
130    }
131
132    /**
133     * Creates a composite set with sets as the initial set of composited Sets.
134     *
135     * @param sets  the initial sets in the composite
136     */
137    public CompositeSet(final Set<E>... sets) {
138        addComposited(sets);
139    }
140
141    /**
142     * Adds an object to the collection, throwing UnsupportedOperationException
143     * unless a SetMutator strategy is specified.
144     *
145     * @param obj  the object to add
146     * @return {@code true} if the collection was modified
147     * @throws UnsupportedOperationException if SetMutator hasn't been set or 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(
156                "add() is not supported on CompositeSet without a SetMutator strategy");
157        }
158        return mutator.add(this, all, obj);
159    }
160
161    /**
162     * Adds a collection of elements to this composite, throwing
163     * UnsupportedOperationException unless a SetMutator strategy is specified.
164     *
165     * @param coll  the collection to add
166     * @return true if the composite was modified
167     * @throws UnsupportedOperationException if SetMutator hasn't been set or add is unsupported
168     * @throws ClassCastException if the object cannot be added due to its type
169     * @throws NullPointerException if the object cannot be added because its null
170     * @throws IllegalArgumentException if the object cannot be added
171     */
172    @Override
173    public boolean addAll(final Collection<? extends E> coll) {
174        if (mutator == null) {
175            throw new UnsupportedOperationException(
176                "addAll() is not supported on CompositeSet without a SetMutator strategy");
177        }
178        return mutator.addAll(this, all, coll);
179    }
180
181    /**
182     * Adds a Set to this composite.
183     *
184     * @param set  the set to add
185     * @throws IllegalArgumentException if a SetMutator is set, but fails to resolve a collision
186     * @throws UnsupportedOperationException if there is no SetMutator set
187     * @throws NullPointerException if {@code set} is null
188     * @see SetMutator
189     */
190    public synchronized void addComposited(final Set<E> set) {
191        if (set != null) {
192            for (final Set<E> existingSet : getSets()) {
193                final Collection<E> intersects = CollectionUtils.intersection(existingSet, set);
194                if (!intersects.isEmpty()) {
195                    if (this.mutator == null) {
196                        throw new UnsupportedOperationException(
197                                "Collision adding composited set with no SetMutator set");
198                    }
199                    getMutator().resolveCollision(this, existingSet, set, intersects);
200                    if (!CollectionUtils.intersection(existingSet, set).isEmpty()) {
201                        throw new IllegalArgumentException(
202                                "Attempt to add illegal entry unresolved by SetMutator.resolveCollision()");
203                    }
204                }
205            }
206            all.add(set);
207        }
208    }
209
210    /**
211     * Adds these Sets to the list of sets in this composite
212     *
213     * @param sets  the Sets to be appended to the composite
214     */
215    public void addComposited(final Set<E>... sets) {
216        if (sets != null) {
217            for (final Set<E> set : sets) {
218                addComposited(set);
219            }
220        }
221    }
222
223    /**
224     * Adds these Sets to the list of sets in this composite.
225     *
226     * @param set1  the first Set to be appended to the composite
227     * @param set2  the second Set to be appended to the composite
228     */
229    public void addComposited(final Set<E> set1, final Set<E> set2) {
230        addComposited(set1);
231        addComposited(set2);
232    }
233
234    /**
235     * Removes all of the elements from this composite set.
236     * <p>
237     * This implementation calls {@code clear()} on each set.
238     *
239     * @throws UnsupportedOperationException if clear is unsupported
240     */
241    @Override
242    public void clear() {
243        for (final Collection<E> coll : all) {
244            coll.clear();
245        }
246    }
247
248    /**
249     * Checks whether this composite set contains the object.
250     * <p>
251     * This implementation calls {@code contains()} on each set.
252     *
253     * @param obj  the object to search for
254     * @return true if obj is contained in any of the contained sets
255     */
256    @Override
257    public boolean contains(final Object obj) {
258        for (final Set<E> item : all) {
259            if (item.contains(obj)) {
260                return true;
261            }
262        }
263        return false;
264    }
265
266    /**
267     * Checks whether this composite contains all the elements in the specified collection.
268     * <p>
269     * This implementation calls {@code contains()} for each element in the
270     * specified collection.
271     *
272     * @param coll  the collection to check for
273     * @return true if all elements contained
274     */
275    @Override
276    public boolean containsAll(final Collection<?> coll) {
277        if (coll == null) {
278            return false;
279        }
280        for (final Object item : coll) {
281            if (!contains(item)) {
282                return false;
283            }
284        }
285        return true;
286    }
287
288    /**
289     * {@inheritDoc}
290     * @see java.util.Set#equals
291     */
292    @Override
293    public boolean equals(final Object obj) {
294        if (obj instanceof Set) {
295            final Set<?> set = (Set<?>) obj;
296            return set.size() == this.size() && set.containsAll(this);
297        }
298        return false;
299    }
300
301    /**
302     * Gets the set mutator to be used for this CompositeSet.
303     * @return the set mutator
304     */
305    protected SetMutator<E> getMutator() {
306        return mutator;
307    }
308
309    /**
310     * Gets the sets being decorated.
311     *
312     * @return Unmodifiable list of all sets in this composite.
313     */
314    public List<Set<E>> getSets() {
315        return UnmodifiableList.unmodifiableList(all);
316    }
317
318    /**
319     * {@inheritDoc}
320     * @see java.util.Set#hashCode
321     */
322    @Override
323    public int hashCode() {
324        int code = 0;
325        for (final E e : this) {
326            code += e == null ? 0 : e.hashCode();
327        }
328        return code;
329    }
330
331    /**
332     * Checks whether this composite set is empty.
333     * <p>
334     * This implementation calls {@code isEmpty()} on each set.
335     *
336     * @return true if all of the contained sets are empty
337     */
338    @Override
339    public boolean isEmpty() {
340        for (final Set<E> item : all) {
341            if (!item.isEmpty()) {
342                return false;
343            }
344        }
345        return true;
346    }
347
348    /**
349     * Gets an iterator over all the sets in this composite.
350     * <p>
351     * This implementation uses an {@code IteratorChain}.
352     *
353     * @return an {@code IteratorChain} instance which supports
354     *  {@code remove()}. Iteration occurs over contained collections in
355     *  the order they were added, but this behavior should not be relied upon.
356     * @see IteratorChain
357     */
358    @Override
359    public Iterator<E> iterator() {
360        if (all.isEmpty()) {
361            return EmptyIterator.<E>emptyIterator();
362        }
363        final IteratorChain<E> chain = new IteratorChain<>();
364        all.forEach(item -> chain.addIterator(item.iterator()));
365        return chain;
366    }
367
368    /**
369     * If a {@code CollectionMutator} is defined for this CompositeSet then this
370     * method will be called anyway.
371     *
372     * @param obj  object to be removed
373     * @return true if the object is removed, false otherwise
374     */
375    @Override
376    public boolean remove(final Object obj) {
377        for (final Set<E> set : getSets()) {
378            if (set.contains(obj)) {
379                return set.remove(obj);
380            }
381        }
382        return false;
383    }
384
385    /**
386     * Removes the elements in the specified collection from this composite set.
387     * <p>
388     * This implementation calls {@code removeAll} on each collection.
389     *
390     * @param coll  the collection to remove
391     * @return true if the composite was modified
392     * @throws UnsupportedOperationException if removeAll is unsupported
393     */
394    @Override
395    public boolean removeAll(final Collection<?> coll) {
396        if (CollectionUtils.isEmpty(coll)) {
397            return false;
398        }
399        boolean changed = false;
400        for (final Collection<E> item : all) {
401            changed |= item.removeAll(coll);
402        }
403        return changed;
404    }
405
406    /**
407     * Removes a set from those being decorated in this composite.
408     *
409     * @param set  set to be removed
410     */
411    public void removeComposited(final Set<E> set) {
412        all.remove(set);
413    }
414
415    /**
416     * @since 4.4
417     */
418    @Override
419    public boolean removeIf(final Predicate<? super E> filter) {
420        if (Objects.isNull(filter)) {
421            return false;
422        }
423        boolean changed = false;
424        for (final Collection<E> item : all) {
425            changed |= item.removeIf(filter);
426        }
427        return changed;
428    }
429
430    /**
431     * Retains all the elements in the specified collection in this composite set,
432     * removing all others.
433     * <p>
434     * This implementation calls {@code retainAll()} on each collection.
435     *
436     * @param coll  the collection to remove
437     * @return true if the composite was modified
438     * @throws UnsupportedOperationException if retainAll is unsupported
439     */
440    @Override
441    public boolean retainAll(final Collection<?> coll) {
442        boolean changed = false;
443        for (final Collection<E> item : all) {
444            changed |= item.retainAll(coll);
445        }
446        return changed;
447    }
448
449    /**
450     * Specify a SetMutator strategy instance to handle changes.
451     *
452     * @param mutator  the mutator to use
453     */
454    public void setMutator(final SetMutator<E> mutator) {
455        this.mutator = mutator;
456    }
457
458    /**
459     * Gets the size of this composite set.
460     * <p>
461     * This implementation calls {@code size()} on each set.
462     *
463     * @return total number of elements in all contained containers
464     */
465    @Override
466    public int size() {
467        int size = 0;
468        for (final Set<E> item : all) {
469            size += item.size();
470        }
471        return size;
472    }
473
474    /**
475     * Returns an array containing all of the elements in this composite.
476     *
477     * @return an object array of all the elements in the collection
478     */
479    @Override
480    public Object[] toArray() {
481        final Object[] result = new Object[size()];
482        int i = 0;
483        for (final Iterator<E> it = iterator(); it.hasNext(); i++) {
484            result[i] = it.next();
485        }
486        return result;
487    }
488
489    /**
490     * Returns an object array, populating the supplied array if possible.
491     * See {@code Collection} interface for full details.
492     *
493     * @param <T>  the type of the elements in the collection
494     * @param array  the array to use, populating if possible
495     * @return an array of all the elements in the collection
496     */
497    @Override
498    @SuppressWarnings("unchecked")
499    public <T> T[] toArray(final T[] array) {
500        final int size = size();
501        Object[] result = null;
502        if (array.length >= size) {
503            result = array;
504        } else {
505            result = (Object[]) Array.newInstance(array.getClass().getComponentType(), size);
506        }
507
508        int offset = 0;
509        for (final Collection<E> item : all) {
510            for (final E e : item) {
511                result[offset++] = e;
512            }
513        }
514        if (result.length > size) {
515            result[size] = null;
516        }
517        return (T[]) result;
518    }
519
520    /**
521     * Returns a new Set containing all of the elements.
522     *
523     * @return A new HashSet containing all of the elements in this composite.
524     *   The new collection is <i>not</i> backed by this composite.
525     */
526    public Set<E> toSet() {
527        return new HashSet<>(this);
528    }
529}