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