CompositeSet.java

  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. import java.io.Serializable;
  19. import java.lang.reflect.Array;
  20. import java.util.ArrayList;
  21. import java.util.Collection;
  22. import java.util.HashSet;
  23. import java.util.Iterator;
  24. import java.util.List;
  25. import java.util.Objects;
  26. import java.util.Set;
  27. import java.util.function.Predicate;

  28. import org.apache.commons.collections4.CollectionUtils;
  29. import org.apache.commons.collections4.iterators.EmptyIterator;
  30. import org.apache.commons.collections4.iterators.IteratorChain;
  31. import org.apache.commons.collections4.list.UnmodifiableList;

  32. /**
  33.  * Decorates a set of other sets to provide a single unified view.
  34.  * <p>
  35.  * Changes made to this set will actually be made on the decorated set.
  36.  * Add operations require the use of a pluggable strategy.
  37.  * If no strategy is provided then add is unsupported.
  38.  * </p>
  39.  * <p>
  40.  * From version 4.0, this class does not extend
  41.  * {@link org.apache.commons.collections4.collection.CompositeCollection CompositeCollection}
  42.  * anymore due to its input restrictions (only accepts Sets).
  43.  * See <a href="https://issues.apache.org/jira/browse/COLLECTIONS-424">COLLECTIONS-424</a>
  44.  * for more details.
  45.  * </p>
  46.  *
  47.  * @param <E> the type of the elements in this set
  48.  * @since 3.0
  49.  */
  50. public class CompositeSet<E> implements Set<E>, Serializable {

  51.     /**
  52.      * Defines callbacks for mutation operations.
  53.      *
  54.      * @param <E> the type of the elements in this instance.
  55.      */
  56.     public interface SetMutator<E> extends Serializable {

  57.         /**
  58.          * Called when an object is to be added to the composite.
  59.          *
  60.          * @param composite  the CompositeSet being changed
  61.          * @param sets  all of the Set instances in this CompositeSet
  62.          * @param obj  the object being added
  63.          * @return true if the collection is changed
  64.          * @throws UnsupportedOperationException if add is unsupported
  65.          * @throws ClassCastException if the object cannot be added due to its type
  66.          * @throws NullPointerException if the object cannot be added because its null
  67.          * @throws IllegalArgumentException if the object cannot be added
  68.          */
  69.         boolean add(CompositeSet<E> composite, List<Set<E>> sets, E obj);

  70.         /**
  71.          * Called when a collection is to be added to the composite.
  72.          *
  73.          * @param composite  the CompositeSet being changed
  74.          * @param sets  all of the Set instances in this CompositeSet
  75.          * @param coll  the collection being added
  76.          * @return true if the collection is changed
  77.          * @throws UnsupportedOperationException if add is unsupported
  78.          * @throws ClassCastException if the object cannot be added due to its type
  79.          * @throws NullPointerException if the object cannot be added because its null
  80.          * @throws IllegalArgumentException if the object cannot be added
  81.          */
  82.         boolean addAll(CompositeSet<E> composite,
  83.                               List<Set<E>> sets,
  84.                               Collection<? extends E> coll);

  85.         /**
  86.          * Called when a Set is added to the CompositeSet and there is a
  87.          * collision between existing and added sets.
  88.          * <p>
  89.          * If {@code added} and {@code existing} still have any intersects
  90.          * after this method returns an IllegalArgumentException will be thrown.
  91.          *
  92.          * @param comp  the CompositeSet being modified
  93.          * @param existing  the Set already existing in the composite
  94.          * @param added  the Set being added to the composite
  95.          * @param intersects  the intersection of the existing and added sets
  96.          */
  97.         void resolveCollision(CompositeSet<E> comp,
  98.                                      Set<E> existing,
  99.                                      Set<E> added,
  100.                                      Collection<E> intersects);
  101.     }

  102.     /** Serialization version */
  103.     private static final long serialVersionUID = 5185069727540378940L;

  104.     /** SetMutator to handle changes to the collection */
  105.     private SetMutator<E> mutator;

  106.     /** Sets in the composite */
  107.     private final List<Set<E>> all = new ArrayList<>();

  108.     /**
  109.      * Creates an empty CompositeSet.
  110.      */
  111.     public CompositeSet() {
  112.     }

  113.     /**
  114.      * Creates a CompositeSet with just {@code set} composited.
  115.      *
  116.      * @param set  the initial set in the composite
  117.      */
  118.     public CompositeSet(final Set<E> set) {
  119.         addComposited(set);
  120.     }

  121.     /**
  122.      * Creates a composite set with sets as the initial set of composited Sets.
  123.      *
  124.      * @param sets  the initial sets in the composite
  125.      */
  126.     public CompositeSet(final Set<E>... sets) {
  127.         addComposited(sets);
  128.     }

  129.     /**
  130.      * Adds an object to the collection, throwing UnsupportedOperationException
  131.      * unless a SetMutator strategy is specified.
  132.      *
  133.      * @param obj  the object to add
  134.      * @return {@code true} if the collection was modified
  135.      * @throws UnsupportedOperationException if SetMutator hasn't been set or add is unsupported
  136.      * @throws ClassCastException if the object cannot be added due to its type
  137.      * @throws NullPointerException if the object cannot be added because its null
  138.      * @throws IllegalArgumentException if the object cannot be added
  139.      */
  140.     @Override
  141.     public boolean add(final E obj) {
  142.         if (mutator == null) {
  143.             throw new UnsupportedOperationException(
  144.                 "add() is not supported on CompositeSet without a SetMutator strategy");
  145.         }
  146.         return mutator.add(this, all, obj);
  147.     }

  148.     /**
  149.      * Adds a collection of elements to this composite, throwing
  150.      * UnsupportedOperationException unless a SetMutator strategy is specified.
  151.      *
  152.      * @param coll  the collection to add
  153.      * @return true if the composite was modified
  154.      * @throws UnsupportedOperationException if SetMutator hasn't been set or add is unsupported
  155.      * @throws ClassCastException if the object cannot be added due to its type
  156.      * @throws NullPointerException if the object cannot be added because its null
  157.      * @throws IllegalArgumentException if the object cannot be added
  158.      */
  159.     @Override
  160.     public boolean addAll(final Collection<? extends E> coll) {
  161.         if (mutator == null) {
  162.             throw new UnsupportedOperationException(
  163.                 "addAll() is not supported on CompositeSet without a SetMutator strategy");
  164.         }
  165.         return mutator.addAll(this, all, coll);
  166.     }

  167.     /**
  168.      * Adds a Set to this composite.
  169.      *
  170.      * @param set  the set to add
  171.      * @throws IllegalArgumentException if a SetMutator is set, but fails to resolve a collision
  172.      * @throws UnsupportedOperationException if there is no SetMutator set
  173.      * @throws NullPointerException if {@code set} is null
  174.      * @see SetMutator
  175.      */
  176.     public synchronized void addComposited(final Set<E> set) {
  177.         if (set != null) {
  178.             for (final Set<E> existingSet : getSets()) {
  179.                 final Collection<E> intersects = CollectionUtils.intersection(existingSet, set);
  180.                 if (!intersects.isEmpty()) {
  181.                     if (mutator == null) {
  182.                         throw new UnsupportedOperationException(
  183.                                 "Collision adding composited set with no SetMutator set");
  184.                     }
  185.                     getMutator().resolveCollision(this, existingSet, set, intersects);
  186.                     if (!CollectionUtils.intersection(existingSet, set).isEmpty()) {
  187.                         throw new IllegalArgumentException(
  188.                                 "Attempt to add illegal entry unresolved by SetMutator.resolveCollision()");
  189.                     }
  190.                 }
  191.             }
  192.             all.add(set);
  193.         }
  194.     }

  195.     /**
  196.      * Adds these Sets to the list of sets in this composite
  197.      *
  198.      * @param sets  the Sets to be appended to the composite
  199.      */
  200.     public void addComposited(final Set<E>... sets) {
  201.         if (sets != null) {
  202.             for (final Set<E> set : sets) {
  203.                 addComposited(set);
  204.             }
  205.         }
  206.     }

  207.     /**
  208.      * Adds these Sets to the list of sets in this composite.
  209.      *
  210.      * @param set1  the first Set to be appended to the composite
  211.      * @param set2  the second Set to be appended to the composite
  212.      */
  213.     public void addComposited(final Set<E> set1, final Set<E> set2) {
  214.         addComposited(set1);
  215.         addComposited(set2);
  216.     }

  217.     /**
  218.      * Removes all of the elements from this composite set.
  219.      * <p>
  220.      * This implementation calls {@code clear()} on each set.
  221.      *
  222.      * @throws UnsupportedOperationException if clear is unsupported
  223.      */
  224.     @Override
  225.     public void clear() {
  226.         for (final Collection<E> coll : all) {
  227.             coll.clear();
  228.         }
  229.     }

  230.     /**
  231.      * Checks whether this composite set contains the object.
  232.      * <p>
  233.      * This implementation calls {@code contains()} on each set.
  234.      *
  235.      * @param obj  the object to search for
  236.      * @return true if obj is contained in any of the contained sets
  237.      */
  238.     @Override
  239.     public boolean contains(final Object obj) {
  240.         for (final Set<E> item : all) {
  241.             if (item.contains(obj)) {
  242.                 return true;
  243.             }
  244.         }
  245.         return false;
  246.     }

  247.     /**
  248.      * Checks whether this composite contains all the elements in the specified collection.
  249.      * <p>
  250.      * This implementation calls {@code contains()} for each element in the
  251.      * specified collection.
  252.      *
  253.      * @param coll  the collection to check for
  254.      * @return true if all elements contained
  255.      */
  256.     @Override
  257.     public boolean containsAll(final Collection<?> coll) {
  258.         if (coll == null) {
  259.             return false;
  260.         }
  261.         for (final Object item : coll) {
  262.             if (!contains(item)) {
  263.                 return false;
  264.             }
  265.         }
  266.         return true;
  267.     }

  268.     /**
  269.      * {@inheritDoc}
  270.      * @see java.util.Set#equals
  271.      */
  272.     @Override
  273.     public boolean equals(final Object obj) {
  274.         if (obj instanceof Set) {
  275.             final Set<?> set = (Set<?>) obj;
  276.             return set.size() == this.size() && set.containsAll(this);
  277.         }
  278.         return false;
  279.     }

  280.     /**
  281.      * Gets the set mutator to be used for this CompositeSet.
  282.      * @return the set mutator
  283.      */
  284.     protected SetMutator<E> getMutator() {
  285.         return mutator;
  286.     }

  287.     /**
  288.      * Gets the sets being decorated.
  289.      *
  290.      * @return Unmodifiable list of all sets in this composite.
  291.      */
  292.     public List<Set<E>> getSets() {
  293.         return UnmodifiableList.unmodifiableList(all);
  294.     }

  295.     /**
  296.      * {@inheritDoc}
  297.      * @see java.util.Set#hashCode
  298.      */
  299.     @Override
  300.     public int hashCode() {
  301.         int code = 0;
  302.         for (final E e : this) {
  303.             code += e == null ? 0 : e.hashCode();
  304.         }
  305.         return code;
  306.     }

  307.     /**
  308.      * Checks whether this composite set is empty.
  309.      * <p>
  310.      * This implementation calls {@code isEmpty()} on each set.
  311.      *
  312.      * @return true if all of the contained sets are empty
  313.      */
  314.     @Override
  315.     public boolean isEmpty() {
  316.         for (final Set<E> item : all) {
  317.             if (!item.isEmpty()) {
  318.                 return false;
  319.             }
  320.         }
  321.         return true;
  322.     }

  323.     /**
  324.      * Gets an iterator over all the sets in this composite.
  325.      * <p>
  326.      * This implementation uses an {@code IteratorChain}.
  327.      *
  328.      * @return an {@code IteratorChain} instance which supports
  329.      *  {@code remove()}. Iteration occurs over contained collections in
  330.      *  the order they were added, but this behavior should not be relied upon.
  331.      * @see IteratorChain
  332.      */
  333.     @Override
  334.     public Iterator<E> iterator() {
  335.         if (all.isEmpty()) {
  336.             return EmptyIterator.<E>emptyIterator();
  337.         }
  338.         final IteratorChain<E> chain = new IteratorChain<>();
  339.         all.forEach(item -> chain.addIterator(item.iterator()));
  340.         return chain;
  341.     }

  342.     /**
  343.      * If a {@code CollectionMutator} is defined for this CompositeSet then this
  344.      * method will be called anyway.
  345.      *
  346.      * @param obj  object to be removed
  347.      * @return true if the object is removed, false otherwise
  348.      */
  349.     @Override
  350.     public boolean remove(final Object obj) {
  351.         for (final Set<E> set : getSets()) {
  352.             if (set.contains(obj)) {
  353.                 return set.remove(obj);
  354.             }
  355.         }
  356.         return false;
  357.     }

  358.     /**
  359.      * Removes the elements in the specified collection from this composite set.
  360.      * <p>
  361.      * This implementation calls {@code removeAll} on each collection.
  362.      *
  363.      * @param coll  the collection to remove
  364.      * @return true if the composite was modified
  365.      * @throws UnsupportedOperationException if removeAll is unsupported
  366.      */
  367.     @Override
  368.     public boolean removeAll(final Collection<?> coll) {
  369.         if (CollectionUtils.isEmpty(coll)) {
  370.             return false;
  371.         }
  372.         boolean changed = false;
  373.         for (final Collection<E> item : all) {
  374.             changed |= item.removeAll(coll);
  375.         }
  376.         return changed;
  377.     }

  378.     /**
  379.      * Removes a set from those being decorated in this composite.
  380.      *
  381.      * @param set  set to be removed
  382.      */
  383.     public void removeComposited(final Set<E> set) {
  384.         all.remove(set);
  385.     }

  386.     /**
  387.      * @since 4.4
  388.      */
  389.     @Override
  390.     public boolean removeIf(final Predicate<? super E> filter) {
  391.         if (Objects.isNull(filter)) {
  392.             return false;
  393.         }
  394.         boolean changed = false;
  395.         for (final Collection<E> item : all) {
  396.             changed |= item.removeIf(filter);
  397.         }
  398.         return changed;
  399.     }

  400.     /**
  401.      * Retains all the elements in the specified collection in this composite set,
  402.      * removing all others.
  403.      * <p>
  404.      * This implementation calls {@code retainAll()} on each collection.
  405.      *
  406.      * @param coll  the collection to remove
  407.      * @return true if the composite was modified
  408.      * @throws UnsupportedOperationException if retainAll is unsupported
  409.      */
  410.     @Override
  411.     public boolean retainAll(final Collection<?> coll) {
  412.         boolean changed = false;
  413.         for (final Collection<E> item : all) {
  414.             changed |= item.retainAll(coll);
  415.         }
  416.         return changed;
  417.     }

  418.     /**
  419.      * Specify a SetMutator strategy instance to handle changes.
  420.      *
  421.      * @param mutator  the mutator to use
  422.      */
  423.     public void setMutator(final SetMutator<E> mutator) {
  424.         this.mutator = mutator;
  425.     }

  426.     /**
  427.      * Gets the size of this composite set.
  428.      * <p>
  429.      * This implementation calls {@code size()} on each set.
  430.      *
  431.      * @return total number of elements in all contained containers
  432.      */
  433.     @Override
  434.     public int size() {
  435.         int size = 0;
  436.         for (final Set<E> item : all) {
  437.             size += item.size();
  438.         }
  439.         return size;
  440.     }

  441.     /**
  442.      * Returns an array containing all of the elements in this composite.
  443.      *
  444.      * @return an object array of all the elements in the collection
  445.      */
  446.     @Override
  447.     public Object[] toArray() {
  448.         final Object[] result = new Object[size()];
  449.         int i = 0;
  450.         for (final Iterator<E> it = iterator(); it.hasNext(); i++) {
  451.             result[i] = it.next();
  452.         }
  453.         return result;
  454.     }

  455.     /**
  456.      * Returns an object array, populating the supplied array if possible.
  457.      * See {@code Collection} interface for full details.
  458.      *
  459.      * @param <T>  the type of the elements in the collection
  460.      * @param array  the array to use, populating if possible
  461.      * @return an array of all the elements in the collection
  462.      */
  463.     @Override
  464.     @SuppressWarnings("unchecked")
  465.     public <T> T[] toArray(final T[] array) {
  466.         final int size = size();
  467.         Object[] result = null;
  468.         if (array.length >= size) {
  469.             result = array;
  470.         } else {
  471.             result = (Object[]) Array.newInstance(array.getClass().getComponentType(), size);
  472.         }

  473.         int offset = 0;
  474.         for (final Collection<E> item : all) {
  475.             for (final E e : item) {
  476.                 result[offset++] = e;
  477.             }
  478.         }
  479.         if (result.length > size) {
  480.             result[size] = null;
  481.         }
  482.         return (T[]) result;
  483.     }

  484.     /**
  485.      * Returns a new Set containing all of the elements.
  486.      *
  487.      * @return A new HashSet containing all of the elements in this composite.
  488.      *   The new collection is <em>not</em> backed by this composite.
  489.      */
  490.     public Set<E> toSet() {
  491.         return new HashSet<>(this);
  492.     }
  493. }