CompositeCollection.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.collection;

  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.Iterator;
  23. import java.util.List;
  24. import java.util.Objects;
  25. import java.util.function.Predicate;

  26. import org.apache.commons.collections4.CollectionUtils;
  27. import org.apache.commons.collections4.iterators.EmptyIterator;
  28. import org.apache.commons.collections4.iterators.IteratorChain;
  29. import org.apache.commons.collections4.list.UnmodifiableList;

  30. /**
  31.  * Decorates a collection of other collections to provide a single unified view.
  32.  * <p>
  33.  * Changes made to this collection will actually be made on the decorated collection.
  34.  * Add and remove operations require the use of a pluggable strategy. If no
  35.  * strategy is provided then add and remove are unsupported.
  36.  * </p>
  37.  * @param <E> the type of the elements in the collection
  38.  * @since 3.0
  39.  */
  40. public class CompositeCollection<E> implements Collection<E>, Serializable {

  41.     /**
  42.      * Pluggable strategy to handle changes to the composite.
  43.      *
  44.      * @param <E> the element being held in the collection
  45.      */
  46.     public interface CollectionMutator<E> extends Serializable {

  47.         /**
  48.          * Called when an object is to be added to the composite.
  49.          *
  50.          * @param composite  the CompositeCollection being changed
  51.          * @param collections  all of the Collection instances in this CompositeCollection
  52.          * @param obj  the object being added
  53.          * @return true if the collection is changed
  54.          * @throws UnsupportedOperationException if add is unsupported
  55.          * @throws ClassCastException if the object cannot be added due to its type
  56.          * @throws NullPointerException if the object cannot be added because its null
  57.          * @throws IllegalArgumentException if the object cannot be added
  58.          */
  59.         boolean add(CompositeCollection<E> composite, List<Collection<E>> collections, E obj);

  60.         /**
  61.          * Called when a collection is to be added to the composite.
  62.          *
  63.          * @param composite  the CompositeCollection being changed
  64.          * @param collections  all of the Collection instances in this CompositeCollection
  65.          * @param coll  the collection being added
  66.          * @return true if the collection is changed
  67.          * @throws UnsupportedOperationException if add is unsupported
  68.          * @throws ClassCastException if the object cannot be added due to its type
  69.          * @throws NullPointerException if the object cannot be added because its null
  70.          * @throws IllegalArgumentException if the object cannot be added
  71.          */
  72.         boolean addAll(CompositeCollection<E> composite,
  73.                               List<Collection<E>> collections,
  74.                               Collection<? extends E> coll);

  75.         /**
  76.          * Called when an object is to be removed to the composite.
  77.          *
  78.          * @param composite  the CompositeCollection being changed
  79.          * @param collections  all of the Collection instances in this CompositeCollection
  80.          * @param obj  the object being removed
  81.          * @return true if the collection is changed
  82.          * @throws UnsupportedOperationException if removed is unsupported
  83.          * @throws ClassCastException if the object cannot be removed due to its type
  84.          * @throws NullPointerException if the object cannot be removed because its null
  85.          * @throws IllegalArgumentException if the object cannot be removed
  86.          */
  87.         boolean remove(CompositeCollection<E> composite,
  88.                               List<Collection<E>> collections,
  89.                               Object obj);

  90.     }

  91.     /** Serialization version */
  92.     private static final long serialVersionUID = 8417515734108306801L;

  93.     /** CollectionMutator to handle changes to the collection */
  94.     private CollectionMutator<E> mutator;

  95.     /** Collections in the composite */
  96.     private final List<Collection<E>> all = new ArrayList<>();

  97.     /**
  98.      * Create an empty CompositeCollection.
  99.      */
  100.     public CompositeCollection() {
  101.     }

  102.     /**
  103.      * Create a Composite Collection with one collection.
  104.      *
  105.      * @param compositeCollection  the Collection to be appended to the composite
  106.      */
  107.     public CompositeCollection(final Collection<E> compositeCollection) {
  108.         addComposited(compositeCollection);
  109.     }

  110.     /**
  111.      * Create a Composite Collection with an array of collections.
  112.      *
  113.      * @param compositeCollections  the collections to composite
  114.      */
  115.     public CompositeCollection(final Collection<E>... compositeCollections) {
  116.         addComposited(compositeCollections);
  117.     }

  118.     /**
  119.      * Create a Composite Collection with two collections.
  120.      *
  121.      * @param compositeCollection1  the Collection to be appended to the composite
  122.      * @param compositeCollection2  the Collection to be appended to the composite
  123.      */
  124.     public CompositeCollection(final Collection<E> compositeCollection1,
  125.                                final Collection<E> compositeCollection2) {
  126.         addComposited(compositeCollection1, compositeCollection2);
  127.     }

  128.     /**
  129.      * Adds an object to the collection, throwing UnsupportedOperationException
  130.      * unless a CollectionMutator strategy is specified.
  131.      *
  132.      * @param obj  the object to add
  133.      * @return {@code true} if the collection was modified
  134.      * @throws UnsupportedOperationException if CollectionMutator hasn't been set
  135.      * @throws UnsupportedOperationException if 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 CompositeCollection without a CollectionMutator strategy");
  145.         }
  146.         return mutator.add(this, all, obj);
  147.     }

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

  168.     /**
  169.      * Add these Collections to the list of collections in this composite
  170.      *
  171.      * @param compositeCollection  the Collection to be appended to the composite
  172.      */
  173.     public void addComposited(final Collection<E> compositeCollection) {
  174.         if (compositeCollection != null) {
  175.             all.add(compositeCollection);
  176.         }
  177.     }

  178.     /**
  179.      * Add these Collections to the list of collections in this composite
  180.      *
  181.      * @param compositeCollections  the Collections to be appended to the composite
  182.      */
  183.     public void addComposited(final Collection<E>... compositeCollections) {
  184.         for (final Collection<E> compositeCollection : compositeCollections) {
  185.             if (compositeCollection != null) {
  186.                 all.add(compositeCollection);
  187.             }
  188.         }
  189.     }

  190.     /**
  191.      * Add these Collections to the list of collections in this composite
  192.      *
  193.      * @param compositeCollection1  the Collection to be appended to the composite
  194.      * @param compositeCollection2  the Collection to be appended to the composite
  195.      */
  196.     public void addComposited(final Collection<E> compositeCollection1,
  197.                               final Collection<E> compositeCollection2) {
  198.         if (compositeCollection1 != null) {
  199.             all.add(compositeCollection1);
  200.         }
  201.         if (compositeCollection2 != null) {
  202.             all.add(compositeCollection2);
  203.         }
  204.     }

  205.     /**
  206.      * Removes all of the elements from this collection.
  207.      * <p>
  208.      * This implementation calls {@code clear()} on each collection.
  209.      * </p>
  210.      * @throws UnsupportedOperationException if clear is unsupported
  211.      */
  212.     @Override
  213.     public void clear() {
  214.         for (final Collection<E> coll : all) {
  215.             coll.clear();
  216.         }
  217.     }

  218.     /**
  219.      * Checks whether this composite collection contains the object.
  220.      * <p>
  221.      * This implementation calls {@code contains()} on each collection.
  222.      * </p>
  223.      * @param obj  the object to search for
  224.      * @return true if obj is contained in any of the contained collections
  225.      */
  226.     @Override
  227.     public boolean contains(final Object obj) {
  228.         for (final Collection<E> item : all) {
  229.             if (item.contains(obj)) {
  230.                 return true;
  231.             }
  232.         }
  233.         return false;
  234.     }

  235.     /**
  236.      * Checks whether this composite contains all the elements in the specified collection.
  237.      * <p>
  238.      * This implementation calls {@code contains()} for each element in the
  239.      * specified collection.
  240.      * </p>
  241.      * @param coll  the collection to check for
  242.      * @return true if all elements contained
  243.      */
  244.     @Override
  245.     public boolean containsAll(final Collection<?> coll) {
  246.         if (coll == null) {
  247.             return false;
  248.         }
  249.         for (final Object item : coll) {
  250.             if (!contains(item)) {
  251.                 return false;
  252.             }
  253.         }
  254.         return true;
  255.     }

  256.     /**
  257.      * Gets the collections being decorated.
  258.      *
  259.      * @return Unmodifiable list of all collections in this composite.
  260.      */
  261.     public List<Collection<E>> getCollections() {
  262.         return UnmodifiableList.unmodifiableList(all);
  263.     }

  264.     /**
  265.      * Gets the collection mutator to be used for this CompositeCollection.
  266.      * @return CollectionMutator&lt;E&gt;
  267.      */
  268.     protected CollectionMutator<E> getMutator() {
  269.         return mutator;
  270.     }

  271.     /**
  272.      * Checks whether this composite collection is empty.
  273.      * <p>
  274.      * This implementation calls {@code isEmpty()} on each collection.
  275.      * </p>
  276.      * @return true if all of the contained collections are empty
  277.      */
  278.     @Override
  279.     public boolean isEmpty() {
  280.         for (final Collection<E> item : all) {
  281.             if (!item.isEmpty()) {
  282.                 return false;
  283.             }
  284.         }
  285.         return true;
  286.     }

  287.     /**
  288.      * Gets an iterator over all the collections in this composite.
  289.      * <p>
  290.      * This implementation uses an {@code IteratorChain}.
  291.      * </p>
  292.      * @return an {@code IteratorChain} instance which supports
  293.      *  {@code remove()}. Iteration occurs over contained collections in
  294.      *  the order they were added, but this behavior should not be relied upon.
  295.      * @see IteratorChain
  296.      */
  297.     @Override
  298.     public Iterator<E> iterator() {
  299.         if (all.isEmpty()) {
  300.             return EmptyIterator.<E>emptyIterator();
  301.         }
  302.         final IteratorChain<E> chain = new IteratorChain<>();
  303.         all.forEach(item -> chain.addIterator(item.iterator()));
  304.         return chain;
  305.     }

  306.     /**
  307.      * Removes an object from the collection, throwing UnsupportedOperationException
  308.      * unless a CollectionMutator strategy is specified.
  309.      *
  310.      * @param obj  the object being removed
  311.      * @return true if the collection is changed
  312.      * @throws UnsupportedOperationException if removed is unsupported
  313.      * @throws ClassCastException if the object cannot be removed due to its type
  314.      * @throws NullPointerException if the object cannot be removed because its null
  315.      * @throws IllegalArgumentException if the object cannot be removed
  316.      */
  317.     @Override
  318.     public boolean remove(final Object obj) {
  319.         if (mutator == null) {
  320.             throw new UnsupportedOperationException(
  321.                 "remove() is not supported on CompositeCollection without a CollectionMutator strategy");
  322.         }
  323.         return mutator.remove(this, all, obj);
  324.     }

  325.     /**
  326.      * Removes the elements in the specified collection from this composite collection.
  327.      * <p>
  328.      * This implementation calls {@code removeAll} on each collection.
  329.      * </p>
  330.      * @param coll  the collection to remove
  331.      * @return true if the collection was modified
  332.      * @throws UnsupportedOperationException if removeAll is unsupported
  333.      */
  334.     @Override
  335.     public boolean removeAll(final Collection<?> coll) {
  336.         if (CollectionUtils.isEmpty(coll)) {
  337.             return false;
  338.         }
  339.         boolean changed = false;
  340.         for (final Collection<E> item : all) {
  341.             changed |= item.removeAll(coll);
  342.         }
  343.         return changed;
  344.     }

  345.     /**
  346.      * Removes a collection from the those being decorated in this composite.
  347.      *
  348.      * @param coll  collection to be removed
  349.      */
  350.     public void removeComposited(final Collection<E> coll) {
  351.         all.remove(coll);
  352.     }

  353.     /**
  354.      * Removes all of the elements of this collection that satisfy the given predicate from this composite collection.
  355.      * <p>
  356.      * This implementation calls {@code removeIf} on each collection.
  357.      * </p>
  358.      * @param filter  a predicate which returns true for elements to be removed
  359.      * @return true if the collection was modified
  360.      * @throws UnsupportedOperationException if removeIf is unsupported
  361.      * @since 4.4
  362.      */
  363.     @Override
  364.     public boolean removeIf(final Predicate<? super E> filter) {
  365.         if (Objects.isNull(filter)) {
  366.             return false;
  367.         }
  368.         boolean changed = false;
  369.         for (final Collection<E> item : all) {
  370.             changed |= item.removeIf(filter);
  371.         }
  372.         return changed;
  373.     }

  374.     /**
  375.      * Retains all the elements in the specified collection in this composite collection,
  376.      * removing all others.
  377.      * <p>
  378.      * This implementation calls {@code retainAll()} on each collection.
  379.      * </p>
  380.      * @param coll  the collection to remove
  381.      * @return true if the collection was modified
  382.      * @throws UnsupportedOperationException if retainAll is unsupported
  383.      */
  384.     @Override
  385.     public boolean retainAll(final Collection<?> coll) {
  386.         boolean changed = false;
  387.         if (coll != null) {
  388.             for (final Collection<E> item : all) {
  389.                 changed |= item.retainAll(coll);
  390.             }
  391.         }
  392.         return changed;
  393.     }

  394.     /**
  395.      * Specify a CollectionMutator strategy instance to handle changes.
  396.      *
  397.      * @param mutator  the mutator to use
  398.      */
  399.     public void setMutator(final CollectionMutator<E> mutator) {
  400.         this.mutator = mutator;
  401.     }

  402.     /**
  403.      * Gets the size of this composite collection.
  404.      * <p>
  405.      * This implementation calls {@code size()} on each collection.
  406.      * </p>
  407.      * @return total number of elements in all contained containers
  408.      */
  409.     @Override
  410.     public int size() {
  411.         int size = 0;
  412.         for (final Collection<E> item : all) {
  413.             size += item.size();
  414.         }
  415.         return size;
  416.     }

  417.     /**
  418.      * Returns an array containing all of the elements in this composite.
  419.      *
  420.      * @return an object array of all the elements in the collection
  421.      */
  422.     @Override
  423.     public Object[] toArray() {
  424.         final Object[] result = new Object[size()];
  425.         int i = 0;
  426.         for (final Iterator<E> it = iterator(); it.hasNext(); i++) {
  427.             result[i] = it.next();
  428.         }
  429.         return result;
  430.     }

  431.     /**
  432.      * Returns an object array, populating the supplied array if possible.
  433.      * See {@code Collection} interface for full details.
  434.      *
  435.      * @param <T>  the type of the elements in the collection
  436.      * @param array  the array to use, populating if possible
  437.      * @return an array of all the elements in the collection
  438.      */
  439.     @Override
  440.     @SuppressWarnings("unchecked")
  441.     public <T> T[] toArray(final T[] array) {
  442.         final int size = size();
  443.         Object[] result = null;
  444.         if (array.length >= size) {
  445.             result = array;
  446.         } else {
  447.             result = (Object[]) Array.newInstance(array.getClass().getComponentType(), size);
  448.         }

  449.         int offset = 0;
  450.         for (final Collection<E> item : all) {
  451.             for (final E e : item) {
  452.                 result[offset++] = e;
  453.             }
  454.         }
  455.         if (result.length > size) {
  456.             result[size] = null;
  457.         }
  458.         return (T[]) result;
  459.     }

  460.     /**
  461.      * Returns a new collection containing all of the elements
  462.      *
  463.      * @return A new ArrayList containing all of the elements in this composite.
  464.      *         The new collection is <em>not</em> backed by this composite.
  465.      */
  466.     public Collection<E> toCollection() {
  467.         return new ArrayList<>(this);
  468.     }

  469. }