| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * https://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.commons.collections4.collection; |
| |
| import java.io.Serializable; |
| import java.lang.reflect.Array; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Objects; |
| import java.util.function.Predicate; |
| import java.util.stream.Stream; |
| |
| import org.apache.commons.collections4.CollectionUtils; |
| import org.apache.commons.collections4.iterators.EmptyIterator; |
| import org.apache.commons.collections4.iterators.IteratorChain; |
| import org.apache.commons.collections4.list.UnmodifiableList; |
| |
| /** |
| * Decorates a collection of other collections to provide a single unified view. |
| * <p> |
| * Changes made to this collection will actually be made on the decorated collection. |
| * Add and remove operations require the use of a pluggable strategy. If no |
| * strategy is provided then add and remove are unsupported. |
| * </p> |
| * |
| * @param <E> The type of the elements in the collection |
| * @since 3.0 |
| */ |
| public class CompositeCollection<E> implements Collection<E>, Serializable { |
| |
| /** |
| * Pluggable strategy to handle changes to the composite. |
| * |
| * @param <E> The element being held in the collection |
| */ |
| public interface CollectionMutator<E> extends Serializable { |
| |
| /** |
| * Called when an object is to be added to the composite. |
| * |
| * @param composite the CompositeCollection being changed. |
| * @param collections all of the Collection instances in this CompositeCollection. |
| * @param obj the object being added. |
| * @return true if the collection is changed. |
| * @throws UnsupportedOperationException if add is unsupported. |
| * @throws ClassCastException if the object cannot be added due to its type. |
| * @throws NullPointerException if the object cannot be added because its null. |
| * @throws IllegalArgumentException if the object cannot be added. |
| */ |
| boolean add(CompositeCollection<E> composite, List<Collection<E>> collections, E obj); |
| |
| /** |
| * Called when a collection is to be added to the composite. |
| * |
| * @param composite the CompositeCollection being changed. |
| * @param collections all of the Collection instances in this CompositeCollection. |
| * @param coll the collection being added. |
| * @return true if the collection is changed. |
| * @throws UnsupportedOperationException if add is unsupported. |
| * @throws ClassCastException if the object cannot be added due to its type. |
| * @throws NullPointerException if the object cannot be added because its null. |
| * @throws IllegalArgumentException if the object cannot be added. |
| */ |
| boolean addAll(CompositeCollection<E> composite, |
| List<Collection<E>> collections, |
| Collection<? extends E> coll); |
| |
| /** |
| * Called when an object is to be removed to the composite. |
| * |
| * @param composite the CompositeCollection being changed. |
| * @param collections all of the Collection instances in this CompositeCollection. |
| * @param obj the object being removed. |
| * @return true if the collection is changed. |
| * @throws UnsupportedOperationException if removed is unsupported. |
| * @throws ClassCastException if the object cannot be removed due to its type. |
| * @throws NullPointerException if the object cannot be removed because its null. |
| * @throws IllegalArgumentException if the object cannot be removed. |
| */ |
| boolean remove(CompositeCollection<E> composite, |
| List<Collection<E>> collections, |
| Object obj); |
| |
| } |
| |
| /** Serialization version. */ |
| private static final long serialVersionUID = 8417515734108306801L; |
| |
| /** CollectionMutator to handle changes to the collection. */ |
| private CollectionMutator<E> mutator; |
| |
| /** Collections in the composite. */ |
| private final List<Collection<E>> all = new ArrayList<>(); |
| |
| /** |
| * Constructs an empty CompositeCollection. |
| */ |
| public CompositeCollection() { |
| } |
| |
| /** |
| * Constructs a Composite Collection with one collection. |
| * |
| * @param compositeCollection the Collection to be appended to the composite. |
| */ |
| public CompositeCollection(final Collection<E> compositeCollection) { |
| addComposited(compositeCollection); |
| } |
| |
| /** |
| * Constructs a Composite Collection with an array of collections. |
| * |
| * @param compositeCollections the collections to composite. |
| */ |
| public CompositeCollection(final Collection<E>... compositeCollections) { |
| addComposited(compositeCollections); |
| } |
| |
| /** |
| * Constructs a Composite Collection with two collections. |
| * |
| * @param compositeCollection1 the Collection to be appended to the composite. |
| * @param compositeCollection2 the Collection to be appended to the composite. |
| */ |
| public CompositeCollection(final Collection<E> compositeCollection1, |
| final Collection<E> compositeCollection2) { |
| addComposited(compositeCollection1, compositeCollection2); |
| } |
| |
| /** |
| * Adds an object to the collection, throwing UnsupportedOperationException |
| * unless a CollectionMutator strategy is specified. |
| * |
| * @param obj the object to add. |
| * @return {@code true} if the collection was modified. |
| * @throws UnsupportedOperationException if CollectionMutator hasn't been set. |
| * @throws UnsupportedOperationException if add is unsupported. |
| * @throws ClassCastException if the object cannot be added due to its type. |
| * @throws NullPointerException if the object cannot be added because its null. |
| * @throws IllegalArgumentException if the object cannot be added. |
| */ |
| @Override |
| public boolean add(final E obj) { |
| if (mutator == null) { |
| throw new UnsupportedOperationException( |
| "add() is not supported on CompositeCollection without a CollectionMutator strategy"); |
| } |
| return mutator.add(this, all, obj); |
| } |
| |
| /** |
| * Adds a collection of elements to this collection, throwing |
| * UnsupportedOperationException unless a CollectionMutator strategy is specified. |
| * |
| * @param coll the collection to add. |
| * @return true if the collection was modified. |
| * @throws UnsupportedOperationException if CollectionMutator hasn't been set. |
| * @throws UnsupportedOperationException if add is unsupported. |
| * @throws ClassCastException if the object cannot be added due to its type. |
| * @throws NullPointerException if the object cannot be added because its null. |
| * @throws IllegalArgumentException if the object cannot be added. |
| */ |
| @Override |
| public boolean addAll(final Collection<? extends E> coll) { |
| if (mutator == null) { |
| throw new UnsupportedOperationException( |
| "addAll() is not supported on CompositeCollection without a CollectionMutator strategy"); |
| } |
| return mutator.addAll(this, all, coll); |
| } |
| |
| /** |
| * Add these Collections to the list of collections in this composite |
| * |
| * @param compositeCollection the Collection to be appended to the composite. |
| */ |
| public void addComposited(final Collection<E> compositeCollection) { |
| if (compositeCollection != null) { |
| all.add(compositeCollection); |
| } |
| } |
| |
| /** |
| * Add these Collections to the list of collections in this composite |
| * |
| * @param compositeCollections the Collections to be appended to the composite. |
| */ |
| public void addComposited(final Collection<E>... compositeCollections) { |
| Stream.of(compositeCollections).filter(Objects::nonNull).forEach(all::add); |
| } |
| |
| /** |
| * Add these Collections to the list of collections in this composite |
| * |
| * @param compositeCollection1 the Collection to be appended to the composite. |
| * @param compositeCollection2 the Collection to be appended to the composite. |
| */ |
| public void addComposited(final Collection<E> compositeCollection1, |
| final Collection<E> compositeCollection2) { |
| if (compositeCollection1 != null) { |
| all.add(compositeCollection1); |
| } |
| if (compositeCollection2 != null) { |
| all.add(compositeCollection2); |
| } |
| } |
| |
| /** |
| * Removes all of the elements from this collection. |
| * <p> |
| * This implementation calls {@code clear()} on each collection. |
| * </p> |
| * |
| * @throws UnsupportedOperationException if clear is unsupported. |
| */ |
| @Override |
| public void clear() { |
| all.forEach(Collection::clear); |
| } |
| |
| /** |
| * Checks whether this composite collection contains the object. |
| * <p> |
| * This implementation calls {@code contains()} on each collection. |
| * </p> |
| * |
| * @param obj the object to search for. |
| * @return true if obj is contained in any of the contained collections. |
| */ |
| @Override |
| public boolean contains(final Object obj) { |
| return all.stream().anyMatch(c -> c.contains(obj)); |
| } |
| |
| /** |
| * Checks whether this composite contains all the elements in the specified collection. |
| * <p> |
| * This implementation calls {@code contains()} for each element in the |
| * specified collection. |
| * </p> |
| * |
| * @param coll the collection to check for. |
| * @return true if all elements contained. |
| */ |
| @Override |
| public boolean containsAll(final Collection<?> coll) { |
| return coll != null && coll.stream().allMatch(this::contains); |
| } |
| |
| /** |
| * Gets the collections being decorated. |
| * |
| * @return Unmodifiable list of all collections in this composite. |
| */ |
| public List<Collection<E>> getCollections() { |
| return UnmodifiableList.unmodifiableList(all); |
| } |
| |
| /** |
| * Gets the collection mutator to be used for this CompositeCollection. |
| * |
| * @return CollectionMutator<E> |
| */ |
| protected CollectionMutator<E> getMutator() { |
| return mutator; |
| } |
| |
| /** |
| * Checks whether this composite collection is empty. |
| * <p> |
| * This implementation calls {@code isEmpty()} on each collection. |
| * </p> |
| * |
| * @return true if all of the contained collections are empty |
| */ |
| @Override |
| public boolean isEmpty() { |
| return all.stream().allMatch(Collection::isEmpty); |
| } |
| |
| /** |
| * Gets an iterator over all the collections in this composite. |
| * <p> |
| * This implementation uses an {@code IteratorChain}. |
| * </p> |
| * |
| * @return an {@code IteratorChain} instance which supports |
| * {@code remove()}. Iteration occurs over contained collections in |
| * the order they were added, but this behavior should not be relied upon. |
| * @see IteratorChain |
| */ |
| @Override |
| public Iterator<E> iterator() { |
| if (all.isEmpty()) { |
| return EmptyIterator.<E>emptyIterator(); |
| } |
| final IteratorChain<E> chain = new IteratorChain<>(); |
| all.forEach(item -> chain.addIterator(item.iterator())); |
| return chain; |
| } |
| |
| /** |
| * Removes an object from the collection, throwing UnsupportedOperationException |
| * unless a CollectionMutator strategy is specified. |
| * |
| * @param obj the object being removed. |
| * @return true if the collection is changed. |
| * @throws UnsupportedOperationException if removed is unsupported. |
| * @throws ClassCastException if the object cannot be removed due to its type. |
| * @throws NullPointerException if the object cannot be removed because its null. |
| * @throws IllegalArgumentException if the object cannot be removed. |
| */ |
| @Override |
| public boolean remove(final Object obj) { |
| if (mutator == null) { |
| throw new UnsupportedOperationException("remove() is not supported on CompositeCollection without a CollectionMutator strategy"); |
| } |
| return mutator.remove(this, all, obj); |
| } |
| |
| /** |
| * Removes the elements in the specified collection from this composite collection. |
| * <p> |
| * This implementation calls {@code removeAll} on each collection. |
| * </p> |
| * |
| * @param coll the collection to remove. |
| * @return true if the collection was modified. |
| * @throws UnsupportedOperationException if removeAll is unsupported. |
| */ |
| @Override |
| public boolean removeAll(final Collection<?> coll) { |
| if (CollectionUtils.isEmpty(coll)) { |
| return false; |
| } |
| boolean changed = false; |
| for (final Collection<E> item : all) { |
| changed |= item.removeAll(coll); |
| } |
| return changed; |
| } |
| |
| /** |
| * Removes a collection from the those being decorated in this composite. |
| * |
| * @param coll collection to be removed. |
| */ |
| public void removeComposited(final Collection<E> coll) { |
| all.remove(coll); |
| } |
| |
| /** |
| * Removes all of the elements of this collection that satisfy the given predicate from this composite collection. |
| * <p> |
| * This implementation calls {@code removeIf} on each collection. |
| * </p> |
| * |
| * @param filter a predicate which returns true for elements to be removed. |
| * @return true if the collection was modified. |
| * @throws UnsupportedOperationException if removeIf is unsupported. |
| * @since 4.4 |
| */ |
| @Override |
| public boolean removeIf(final Predicate<? super E> filter) { |
| if (Objects.isNull(filter)) { |
| return false; |
| } |
| boolean changed = false; |
| for (final Collection<E> item : all) { |
| changed |= item.removeIf(filter); |
| } |
| return changed; |
| } |
| |
| /** |
| * Retains all the elements in the specified collection in this composite collection, |
| * removing all others. |
| * <p> |
| * This implementation calls {@code retainAll()} on each collection. |
| * </p> |
| * |
| * @param coll the collection to remove. |
| * @return true if the collection was modified. |
| * @throws UnsupportedOperationException if retainAll is unsupported. |
| */ |
| @Override |
| public boolean retainAll(final Collection<?> coll) { |
| boolean changed = false; |
| if (coll != null) { |
| for (final Collection<E> item : all) { |
| changed |= item.retainAll(coll); |
| } |
| } |
| return changed; |
| } |
| |
| /** |
| * Specify a CollectionMutator strategy instance to handle changes. |
| * |
| * @param mutator the mutator to use |
| */ |
| public void setMutator(final CollectionMutator<E> mutator) { |
| this.mutator = mutator; |
| } |
| |
| /** |
| * Gets the size of this composite collection. |
| * <p> |
| * This implementation calls {@code size()} on each collection. |
| * </p> |
| * |
| * @return total number of elements in all contained containers, or |
| * {@code Integer.MAX_VALUE} if the total exceeds it. |
| */ |
| @Override |
| public int size() { |
| long size = 0; |
| for (final Collection<E> item : all) { |
| size += item.size(); |
| } |
| return (int) Math.min(size, Integer.MAX_VALUE); |
| } |
| |
| /** |
| * Returns an array containing all of the elements in this composite. |
| * |
| * @return an object array of all the elements in the collection. |
| */ |
| @Override |
| public Object[] toArray() { |
| final Object[] result = new Object[size()]; |
| int i = 0; |
| for (final Iterator<E> it = iterator(); it.hasNext(); i++) { |
| result[i] = it.next(); |
| } |
| return result; |
| } |
| |
| /** |
| * Returns an object array, populating the supplied array if possible. |
| * See {@code Collection} interface for full details. |
| * |
| * @param <T> the type of the elements in the collection. |
| * @param array the array to use, populating if possible. |
| * @return an array of all the elements in the collection. |
| */ |
| @Override |
| @SuppressWarnings("unchecked") |
| public <T> T[] toArray(final T[] array) { |
| final int size = size(); |
| Object[] result = null; |
| if (array.length >= size) { |
| result = array; |
| } else { |
| result = (Object[]) Array.newInstance(array.getClass().getComponentType(), size); |
| } |
| |
| int offset = 0; |
| for (final Collection<E> item : all) { |
| for (final E e : item) { |
| result[offset++] = e; |
| } |
| } |
| if (result.length > size) { |
| result[size] = null; |
| } |
| return (T[]) result; |
| } |
| |
| /** |
| * Returns a new collection containing all of the elements. |
| * |
| * @return A new ArrayList containing all of the elements in this composite. |
| * The new collection is <em>not</em> backed by this composite. |
| */ |
| public Collection<E> toCollection() { |
| return new ArrayList<>(this); |
| } |
| |
| } |
| |