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 *      https://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.IterableUtils;
032import org.apache.commons.collections4.collection.CompositeCollection;
033import org.apache.commons.collections4.iterators.EmptyIterator;
034import org.apache.commons.collections4.iterators.IteratorChain;
035import org.apache.commons.collections4.list.UnmodifiableList;
036
037/**
038 * Decorates a set of other sets to provide a single unified view.
039 * <p>
040 * Changes made to this set will actually be made on the decorated set.
041 * Add operations require the use of a pluggable strategy.
042 * If no strategy is provided then add is unsupported.
043 * </p>
044 * <p>
045 * From version 4.0, this class does not extend
046 * {@link CompositeCollection CompositeCollection}
047 * anymore due to its input restrictions (only accepts Sets).
048 * See <a href="https://issues.apache.org/jira/browse/COLLECTIONS-424">COLLECTIONS-424</a>
049 * for more details.
050 * </p>
051 *
052 * @param <E> The type of the elements in this set
053 * @since 3.0
054 */
055public class CompositeSet<E> implements Set<E>, Serializable {
056
057    /**
058     * Defines callbacks for mutation operations.
059     *
060     * @param <E> The type of the elements in this instance.
061     */
062    public interface SetMutator<E> extends Serializable {
063
064        /**
065         * Called when an object is to be added to the composite.
066         *
067         * @param composite  The CompositeSet being changed
068         * @param sets  all of the Set instances in this CompositeSet
069         * @param obj  The object being added
070         * @return true if the collection is changed
071         * @throws UnsupportedOperationException if add is unsupported
072         * @throws ClassCastException if the object cannot be added due to its type
073         * @throws NullPointerException if the object cannot be added because its null
074         * @throws IllegalArgumentException if the object cannot be added
075         */
076        boolean add(CompositeSet<E> composite, List<Set<E>> sets, E obj);
077
078        /**
079         * Called when a collection is to be added to the composite.
080         *
081         * @param composite  The CompositeSet being changed
082         * @param sets  all of the Set instances in this CompositeSet
083         * @param coll  The collection being added
084         * @return true if the collection is changed
085         * @throws UnsupportedOperationException if add is unsupported
086         * @throws ClassCastException if the object cannot be added due to its type
087         * @throws NullPointerException if the object cannot be added because its null
088         * @throws IllegalArgumentException if the object cannot be added
089         */
090        boolean addAll(CompositeSet<E> composite,
091                              List<Set<E>> sets,
092                              Collection<? extends E> coll);
093
094        /**
095         * Called when a Set is added to the CompositeSet and there is a
096         * collision between existing and added sets.
097         * <p>
098         * If {@code added} and {@code existing} still have any intersects
099         * 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}