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 * https://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 */ 017 018package org.apache.commons.collections4.collection; 019 020import java.io.Serializable; 021import java.lang.reflect.Array; 022import java.util.ArrayList; 023import java.util.Collection; 024import java.util.Iterator; 025import java.util.List; 026import java.util.Objects; 027import java.util.function.Predicate; 028import java.util.stream.Stream; 029 030import org.apache.commons.collections4.CollectionUtils; 031import org.apache.commons.collections4.IterableUtils; 032import org.apache.commons.collections4.iterators.EmptyIterator; 033import org.apache.commons.collections4.iterators.IteratorChain; 034import org.apache.commons.collections4.list.UnmodifiableList; 035 036/** 037 * Decorates a collection of other collections to provide a single unified view. 038 * <p> 039 * 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 040 * strategy is provided then add and remove are unsupported. 041 * </p> 042 * 043 * @param <E> The type of the elements in the collection. 044 * @since 3.0 045 */ 046public class CompositeCollection<E> implements Collection<E>, Serializable { 047 048 /** 049 * Pluggable strategy to handle changes to the composite. 050 * 051 * @param <E> The element being held in the collection 052 */ 053 public interface CollectionMutator<E> extends Serializable { 054 055 /** 056 * Called when an object is to be added to the composite. 057 * 058 * @param composite The CompositeCollection being changed. 059 * @param collections all of the Collection instances in this CompositeCollection. 060 * @param obj The object being added. 061 * @return true if the collection is changed. 062 * @throws UnsupportedOperationException if add is unsupported. 063 * @throws ClassCastException if the object cannot be added due to its type. 064 * @throws NullPointerException if the object cannot be added because its null. 065 * @throws IllegalArgumentException if the object cannot be added. 066 */ 067 boolean add(CompositeCollection<E> composite, List<Collection<E>> collections, E obj); 068 069 /** 070 * Called when a collection is to be added to the composite. 071 * 072 * @param composite The CompositeCollection being changed. 073 * @param collections all of the Collection instances in this CompositeCollection. 074 * @param coll The collection being added. 075 * @return true if the collection is changed. 076 * @throws UnsupportedOperationException if add is unsupported. 077 * @throws ClassCastException if the object cannot be added due to its type. 078 * @throws NullPointerException if the object cannot be added because its null. 079 * @throws IllegalArgumentException if the object cannot be added. 080 */ 081 boolean addAll(CompositeCollection<E> composite, List<Collection<E>> collections, Collection<? extends E> coll); 082 083 /** 084 * Called when an object is to be removed to the composite. 085 * 086 * @param composite The CompositeCollection being changed. 087 * @param collections all of the Collection instances in this CompositeCollection. 088 * @param obj The object being removed. 089 * @return true if the collection is changed. 090 * @throws UnsupportedOperationException if removed is unsupported. 091 * @throws ClassCastException if the object cannot be removed due to its type. 092 * @throws NullPointerException if the object cannot be removed because its null. 093 * @throws IllegalArgumentException if the object cannot be removed. 094 */ 095 boolean remove(CompositeCollection<E> composite, List<Collection<E>> collections, Object obj); 096 } 097 098 /** Serialization version. */ 099 private static final long serialVersionUID = 8417515734108306801L; 100 101 /** CollectionMutator to handle changes to the collection. */ 102 private CollectionMutator<E> mutator; 103 104 /** Collections in the composite. */ 105 private final List<Collection<E>> all = new ArrayList<>(); 106 107 /** 108 * Constructs an empty CompositeCollection. 109 */ 110 public CompositeCollection() { 111 } 112 113 /** 114 * Constructs a Composite Collection with one collection. 115 * 116 * @param compositeCollection The Collection to be appended to the composite. 117 */ 118 public CompositeCollection(final Collection<E> compositeCollection) { 119 addComposited(compositeCollection); 120 } 121 122 /** 123 * Constructs a Composite Collection with an array of collections. 124 * 125 * @param compositeCollections The collections to composite. 126 */ 127 public CompositeCollection(final Collection<E>... compositeCollections) { 128 addComposited(compositeCollections); 129 } 130 131 /** 132 * Constructs a Composite Collection with two collections. 133 * 134 * @param compositeCollection1 The Collection to be appended to the composite. 135 * @param compositeCollection2 The Collection to be appended to the composite. 136 */ 137 public CompositeCollection(final Collection<E> compositeCollection1, final Collection<E> compositeCollection2) { 138 addComposited(compositeCollection1, compositeCollection2); 139 } 140 141 /** 142 * Adds an object to the collection, throwing UnsupportedOperationException unless a CollectionMutator strategy is specified. 143 * 144 * @param obj The object to add. 145 * @return {@code true} if the collection was modified. 146 * @throws UnsupportedOperationException if CollectionMutator hasn't been set. 147 * @throws UnsupportedOperationException if add is unsupported. 148 * @throws ClassCastException if the object cannot be added due to its type. 149 * @throws NullPointerException if the object cannot be added because its null. 150 * @throws IllegalArgumentException if the object cannot be added. 151 */ 152 @Override 153 public boolean add(final E obj) { 154 if (mutator == null) { 155 throw new UnsupportedOperationException("add() is not supported on CompositeCollection without a CollectionMutator strategy"); 156 } 157 return mutator.add(this, all, obj); 158 } 159 160 /** 161 * Adds a collection of elements to this collection, throwing UnsupportedOperationException unless a CollectionMutator strategy is specified. 162 * 163 * @param coll The collection to add. 164 * @return true if the collection was modified. 165 * @throws UnsupportedOperationException if CollectionMutator hasn't been set. 166 * @throws UnsupportedOperationException if add is unsupported. 167 * @throws ClassCastException if the object cannot be added due to its type. 168 * @throws NullPointerException if the object cannot be added because its null. 169 * @throws IllegalArgumentException if the object cannot be added. 170 */ 171 @Override 172 public boolean addAll(final Collection<? extends E> coll) { 173 if (mutator == null) { 174 throw new UnsupportedOperationException("addAll() is not supported on CompositeCollection without a CollectionMutator strategy"); 175 } 176 return mutator.addAll(this, all, coll); 177 } 178 179 /** 180 * Add these Collections to the list of collections in this composite. 181 * 182 * @param compositeCollection The Collection to be appended to the composite. 183 */ 184 public void addComposited(final Collection<E> compositeCollection) { 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 compositeCollections The Collections to be appended to the composite. 194 */ 195 public void addComposited(final Collection<E>... compositeCollections) { 196 Stream.of(compositeCollections).filter(Objects::nonNull).forEach(all::add); 197 } 198 199 /** 200 * Add these Collections to the list of collections in this composite. 201 * 202 * @param compositeCollection1 The Collection to be appended to the composite. 203 * @param compositeCollection2 The Collection to be appended to the composite. 204 */ 205 public void addComposited(final Collection<E> compositeCollection1, final Collection<E> compositeCollection2) { 206 if (compositeCollection1 != null) { 207 all.add(compositeCollection1); 208 } 209 if (compositeCollection2 != null) { 210 all.add(compositeCollection2); 211 } 212 } 213 214 /** 215 * Removes all of the elements from this collection. 216 * <p> 217 * This implementation calls {@code clear()} on each collection. 218 * </p> 219 * 220 * @throws UnsupportedOperationException if clear is unsupported. 221 */ 222 @Override 223 public void clear() { 224 all.forEach(Collection::clear); 225 } 226 227 /** 228 * Checks whether this composite collection contains the object. 229 * <p> 230 * This implementation calls {@code contains()} on each collection. 231 * </p> 232 * 233 * @param obj The object to search for. 234 * @return true if obj is contained in any of the contained collections. 235 */ 236 @Override 237 public boolean contains(final Object obj) { 238 return all.stream().anyMatch(c -> c.contains(obj)); 239 } 240 241 /** 242 * Checks whether this composite contains all the elements in the specified collection. 243 * <p> 244 * This implementation calls {@code contains()} for each element in the specified collection. 245 * </p> 246 * 247 * @param coll The collection to check for. 248 * @return true if all elements contained. 249 */ 250 @Override 251 public boolean containsAll(final Collection<?> coll) { 252 return coll != null && coll.stream().allMatch(this::contains); 253 } 254 255 /** 256 * Gets the collections being decorated. 257 * 258 * @return Unmodifiable list of all collections in this composite. 259 */ 260 public List<Collection<E>> getCollections() { 261 return UnmodifiableList.unmodifiableList(all); 262 } 263 264 /** 265 * Gets the collection mutator to be used for this CompositeCollection. 266 * 267 * @return CollectionMutator<E> 268 */ 269 protected CollectionMutator<E> getMutator() { 270 return mutator; 271 } 272 273 /** 274 * Checks whether this composite collection is empty. 275 * <p> 276 * This implementation calls {@code isEmpty()} on each collection. 277 * </p> 278 * 279 * @return true if all of the contained collections are empty 280 */ 281 @Override 282 public boolean isEmpty() { 283 return all.stream().allMatch(Collection::isEmpty); 284 } 285 286 /** 287 * Gets an iterator over all the collections in this composite. 288 * <p> 289 * This implementation uses an {@code IteratorChain}. 290 * </p> 291 * 292 * @return An {@code IteratorChain} instance which supports {@code remove()}. Iteration occurs over contained collections in the order they were added, but 293 * this behavior should not be relied upon. 294 * @see IteratorChain 295 */ 296 @Override 297 public Iterator<E> iterator() { 298 if (all.isEmpty()) { 299 return EmptyIterator.<E>emptyIterator(); 300 } 301 final IteratorChain<E> chain = new IteratorChain<>(); 302 all.forEach(item -> chain.addIterator(item.iterator())); 303 return chain; 304 } 305 306 /** 307 * Removes an object from the collection, throwing UnsupportedOperationException unless a CollectionMutator strategy is specified. 308 * 309 * @param obj The object being removed. 310 * @return true if the collection is changed. 311 * @throws UnsupportedOperationException if removed is unsupported. 312 * @throws ClassCastException if the object cannot be removed due to its type. 313 * @throws NullPointerException if the object cannot be removed because its null. 314 * @throws IllegalArgumentException if the object cannot be removed. 315 */ 316 @Override 317 public boolean remove(final Object obj) { 318 if (mutator == null) { 319 throw new UnsupportedOperationException("remove() is not supported on CompositeCollection without a CollectionMutator strategy"); 320 } 321 return mutator.remove(this, all, obj); 322 } 323 324 /** 325 * Removes the elements in the specified collection from this composite collection. 326 * <p> 327 * This implementation calls {@code removeAll} on each collection. 328 * </p> 329 * 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 /** 347 * Removes a collection from the those being decorated in this composite. 348 * 349 * @param coll collection to be removed. 350 */ 351 public void removeComposited(final Collection<E> coll) { 352 all.remove(coll); 353 } 354 355 /** 356 * Removes all of the elements of this collection that satisfy the given predicate from this composite collection. 357 * <p> 358 * This implementation calls {@code removeIf} on each collection. 359 * </p> 360 * 361 * @param filter A predicate which returns true for elements to be removed. 362 * @return true if the collection was modified. 363 * @throws UnsupportedOperationException if removeIf is unsupported. 364 * @since 4.4 365 */ 366 @Override 367 public boolean removeIf(final Predicate<? super E> filter) { 368 if (Objects.isNull(filter)) { 369 return false; 370 } 371 boolean changed = false; 372 for (final Collection<E> item : all) { 373 changed |= item.removeIf(filter); 374 } 375 return changed; 376 } 377 378 /** 379 * Retains all the elements in the specified collection in this composite collection, removing all others. 380 * <p> 381 * This implementation calls {@code retainAll()} on each collection. 382 * </p> 383 * 384 * @param coll The collection to remove. 385 * @return true if the collection was modified. 386 * @throws UnsupportedOperationException if retainAll is unsupported. 387 */ 388 @Override 389 public boolean retainAll(final Collection<?> coll) { 390 boolean changed = false; 391 if (coll != null) { 392 for (final Collection<E> item : all) { 393 changed |= item.retainAll(coll); 394 } 395 } 396 return changed; 397 } 398 399 /** 400 * Specify a CollectionMutator strategy instance to handle changes. 401 * 402 * @param mutator The mutator to use 403 */ 404 public void setMutator(final CollectionMutator<E> mutator) { 405 this.mutator = mutator; 406 } 407 408 /** 409 * Gets the size of this composite collection. 410 * <p> 411 * This implementation calls {@code size()} on each collection. 412 * </p> 413 * 414 * @return total number of elements in all contained containers, or {@code Integer.MAX_VALUE} if the total exceeds it. 415 */ 416 @Override 417 public int size() { 418 return IterableUtils.sumSizesToInt(all); 419 } 420 421 /** 422 * Returns an array containing all of the elements in this composite. 423 * 424 * @return An object array of all the elements in the collection. 425 */ 426 @Override 427 public Object[] toArray() { 428 final Object[] result = new Object[size()]; 429 int i = 0; 430 for (final Iterator<E> it = iterator(); it.hasNext(); i++) { 431 result[i] = it.next(); 432 } 433 return result; 434 } 435 436 /** 437 * Returns an object array, populating the supplied array if possible. See {@code Collection} interface for full details. 438 * 439 * @param <T> the type of the elements in the collection. 440 * @param array The array to use, populating if possible. 441 * @return An array of all the elements in the collection. 442 */ 443 @Override 444 @SuppressWarnings("unchecked") 445 public <T> T[] toArray(final T[] array) { 446 final int size = size(); 447 Object[] result = null; 448 if (array.length >= size) { 449 result = array; 450 } else { 451 result = (Object[]) Array.newInstance(array.getClass().getComponentType(), size); 452 } 453 int offset = 0; 454 for (final Collection<E> item : all) { 455 for (final E e : item) { 456 result[offset++] = e; 457 } 458 } 459 if (result.length > size) { 460 result[size] = null; 461 } 462 return (T[]) result; 463 } 464 465 /** 466 * Returns a new collection containing all of the elements. 467 * 468 * @return A new ArrayList containing all of the elements in this composite. The new collection is <em>not</em> backed by this composite. 469 */ 470 public Collection<E> toCollection() { 471 return new ArrayList<>(this); 472 } 473}