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