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 19 import java.io.Serializable; 20 import java.lang.reflect.Array; 21 import java.util.ArrayList; 22 import java.util.Collection; 23 import java.util.Iterator; 24 import java.util.List; 25 import java.util.Objects; 26 import java.util.function.Predicate; 27 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 /** 34 * Decorates a collection of other collections to provide a single unified view. 35 * <p> 36 * Changes made to this collection will actually be made on the decorated collection. 37 * Add and remove operations require the use of a pluggable strategy. If no 38 * strategy is provided then add and remove are unsupported. 39 * </p> 40 * @param <E> the type of the elements in the collection 41 * @since 3.0 42 */ 43 public class CompositeCollection<E> implements Collection<E>, Serializable { 44 45 /** 46 * Pluggable strategy to handle changes to the composite. 47 * 48 * @param <E> the element being held in the collection 49 */ 50 public interface CollectionMutator<E> extends Serializable { 51 52 /** 53 * Called when an object is to be added to the composite. 54 * 55 * @param composite the CompositeCollection being changed 56 * @param collections all of the Collection instances in this CompositeCollection 57 * @param obj the object being added 58 * @return true if the collection is changed 59 * @throws UnsupportedOperationException if add is unsupported 60 * @throws ClassCastException if the object cannot be added due to its type 61 * @throws NullPointerException if the object cannot be added because its null 62 * @throws IllegalArgumentException if the object cannot be added 63 */ 64 boolean add(CompositeCollection<E> composite, List<Collection<E>> collections, E obj); 65 66 /** 67 * Called when a collection is to be added to the composite. 68 * 69 * @param composite the CompositeCollection being changed 70 * @param collections all of the Collection instances in this CompositeCollection 71 * @param coll the collection being added 72 * @return true if the collection is changed 73 * @throws UnsupportedOperationException if add is unsupported 74 * @throws ClassCastException if the object cannot be added due to its type 75 * @throws NullPointerException if the object cannot be added because its null 76 * @throws IllegalArgumentException if the object cannot be added 77 */ 78 boolean addAll(CompositeCollection<E> composite, 79 List<Collection<E>> collections, 80 Collection<? extends E> coll); 81 82 /** 83 * Called when an object is to be removed to the composite. 84 * 85 * @param composite the CompositeCollection being changed 86 * @param collections all of the Collection instances in this CompositeCollection 87 * @param obj the object being removed 88 * @return true if the collection is changed 89 * @throws UnsupportedOperationException if removed is unsupported 90 * @throws ClassCastException if the object cannot be removed due to its type 91 * @throws NullPointerException if the object cannot be removed because its null 92 * @throws IllegalArgumentException if the object cannot be removed 93 */ 94 boolean remove(CompositeCollection<E> composite, 95 List<Collection<E>> collections, 96 Object obj); 97 98 } 99 100 /** Serialization version */ 101 private static final long serialVersionUID = 8417515734108306801L; 102 103 /** CollectionMutator to handle changes to the collection */ 104 private CollectionMutator<E> mutator; 105 106 /** Collections in the composite */ 107 private final List<Collection<E>> all = new ArrayList<>(); 108 109 /** 110 * Create an empty CompositeCollection. 111 */ 112 public CompositeCollection() { 113 } 114 115 /** 116 * Create a Composite Collection with one collection. 117 * 118 * @param compositeCollection the Collection to be appended to the composite 119 */ 120 public CompositeCollection(final Collection<E> compositeCollection) { 121 addComposited(compositeCollection); 122 } 123 124 /** 125 * Create a Composite Collection with an array of collections. 126 * 127 * @param compositeCollections the collections to composite 128 */ 129 public CompositeCollection(final Collection<E>... compositeCollections) { 130 addComposited(compositeCollections); 131 } 132 133 /** 134 * Create a Composite Collection with two collections. 135 * 136 * @param compositeCollection1 the Collection to be appended to the composite 137 * @param compositeCollection2 the Collection to be appended to the composite 138 */ 139 public CompositeCollection(final Collection<E> compositeCollection1, 140 final Collection<E> compositeCollection2) { 141 addComposited(compositeCollection1, compositeCollection2); 142 } 143 144 /** 145 * Adds an object to the collection, throwing UnsupportedOperationException 146 * unless a CollectionMutator strategy is specified. 147 * 148 * @param obj the object to add 149 * @return {@code true} if the collection was modified 150 * @throws UnsupportedOperationException if CollectionMutator hasn't been set 151 * @throws UnsupportedOperationException if add is unsupported 152 * @throws ClassCastException if the object cannot be added due to its type 153 * @throws NullPointerException if the object cannot be added because its null 154 * @throws IllegalArgumentException if the object cannot be added 155 */ 156 @Override 157 public boolean add(final E obj) { 158 if (mutator == null) { 159 throw new UnsupportedOperationException( 160 "add() is not supported on CompositeCollection without a CollectionMutator strategy"); 161 } 162 return mutator.add(this, all, obj); 163 } 164 165 /** 166 * Adds a collection of elements to this collection, throwing 167 * UnsupportedOperationException unless a CollectionMutator strategy is specified. 168 * 169 * @param coll the collection to add 170 * @return true if the collection was modified 171 * @throws UnsupportedOperationException if CollectionMutator hasn't been set 172 * @throws UnsupportedOperationException if add is unsupported 173 * @throws ClassCastException if the object cannot be added due to its type 174 * @throws NullPointerException if the object cannot be added because its null 175 * @throws IllegalArgumentException if the object cannot be added 176 */ 177 @Override 178 public boolean addAll(final Collection<? extends E> coll) { 179 if (mutator == null) { 180 throw new UnsupportedOperationException( 181 "addAll() is not supported on CompositeCollection without a CollectionMutator strategy"); 182 } 183 return mutator.addAll(this, all, coll); 184 } 185 186 /** 187 * Add these Collections to the list of collections in this composite 188 * 189 * @param compositeCollection the Collection to be appended to the composite 190 */ 191 public void addComposited(final Collection<E> compositeCollection) { 192 if (compositeCollection != null) { 193 all.add(compositeCollection); 194 } 195 } 196 197 /** 198 * Add these Collections to the list of collections in this composite 199 * 200 * @param compositeCollections the Collections to be appended to the composite 201 */ 202 public void addComposited(final Collection<E>... compositeCollections) { 203 for (final Collection<E> compositeCollection : compositeCollections) { 204 if (compositeCollection != null) { 205 all.add(compositeCollection); 206 } 207 } 208 } 209 210 /** 211 * Add these Collections to the list of collections in this composite 212 * 213 * @param compositeCollection1 the Collection to be appended to the composite 214 * @param compositeCollection2 the Collection to be appended to the composite 215 */ 216 public void addComposited(final Collection<E> compositeCollection1, 217 final Collection<E> compositeCollection2) { 218 if (compositeCollection1 != null) { 219 all.add(compositeCollection1); 220 } 221 if (compositeCollection2 != null) { 222 all.add(compositeCollection2); 223 } 224 } 225 226 /** 227 * Removes all of the elements from this collection . 228 * <p> 229 * This implementation calls {@code clear()} on each collection. 230 * </p> 231 * @throws UnsupportedOperationException if clear is unsupported 232 */ 233 @Override 234 public void clear() { 235 for (final Collection<E> coll : all) { 236 coll.clear(); 237 } 238 } 239 240 /** 241 * Checks whether this composite collection contains the object. 242 * <p> 243 * This implementation calls {@code contains()} on each collection. 244 * </p> 245 * @param obj the object to search for 246 * @return true if obj is contained in any of the contained collections 247 */ 248 @Override 249 public boolean contains(final Object obj) { 250 for (final Collection<E> item : all) { 251 if (item.contains(obj)) { 252 return true; 253 } 254 } 255 return false; 256 } 257 258 /** 259 * Checks whether this composite contains all the elements in the specified collection. 260 * <p> 261 * This implementation calls {@code contains()} for each element in the 262 * specified collection. 263 * </p> 264 * @param coll the collection to check for 265 * @return true if all elements contained 266 */ 267 @Override 268 public boolean containsAll(final Collection<?> coll) { 269 if (coll == null) { 270 return false; 271 } 272 for (final Object item : coll) { 273 if (!contains(item)) { 274 return false; 275 } 276 } 277 return true; 278 } 279 280 /** 281 * Gets the collections being decorated. 282 * 283 * @return Unmodifiable list of all collections in this composite. 284 */ 285 public List<Collection<E>> getCollections() { 286 return UnmodifiableList.unmodifiableList(all); 287 } 288 289 /** 290 * Gets the collection mutator to be used for this CompositeCollection. 291 * @return CollectionMutator<E> 292 */ 293 protected CollectionMutator<E> getMutator() { 294 return mutator; 295 } 296 297 /** 298 * Checks whether this composite collection is empty. 299 * <p> 300 * This implementation calls {@code isEmpty()} on each collection. 301 * </p> 302 * @return true if all of the contained collections are empty 303 */ 304 @Override 305 public boolean isEmpty() { 306 for (final Collection<E> item : all) { 307 if (!item.isEmpty()) { 308 return false; 309 } 310 } 311 return true; 312 } 313 314 /** 315 * Gets an iterator over all the collections in this composite. 316 * <p> 317 * This implementation uses an {@code IteratorChain}. 318 * </p> 319 * @return an {@code IteratorChain} instance which supports 320 * {@code remove()}. Iteration occurs over contained collections in 321 * the order they were added, but this behavior should not be relied upon. 322 * @see IteratorChain 323 */ 324 @Override 325 public Iterator<E> iterator() { 326 if (all.isEmpty()) { 327 return EmptyIterator.<E>emptyIterator(); 328 } 329 final IteratorChain<E> chain = new IteratorChain<>(); 330 all.forEach(item -> chain.addIterator(item.iterator())); 331 return chain; 332 } 333 334 /** 335 * Removes an object from the collection, throwing UnsupportedOperationException 336 * unless a CollectionMutator strategy is specified. 337 * 338 * @param obj the object being removed 339 * @return true if the collection is changed 340 * @throws UnsupportedOperationException if removed is unsupported 341 * @throws ClassCastException if the object cannot be removed due to its type 342 * @throws NullPointerException if the object cannot be removed because its null 343 * @throws IllegalArgumentException if the object cannot be removed 344 */ 345 @Override 346 public boolean remove(final Object obj) { 347 if (mutator == null) { 348 throw new UnsupportedOperationException( 349 "remove() is not supported on CompositeCollection without a CollectionMutator strategy"); 350 } 351 return mutator.remove(this, all, obj); 352 } 353 354 /** 355 * Removes the elements in the specified collection from this composite collection. 356 * <p> 357 * This implementation calls {@code removeAll} on each collection. 358 * </p> 359 * @param coll the collection to remove 360 * @return true if the collection was modified 361 * @throws UnsupportedOperationException if removeAll is unsupported 362 */ 363 @Override 364 public boolean removeAll(final Collection<?> coll) { 365 if (CollectionUtils.isEmpty(coll)) { 366 return false; 367 } 368 boolean changed = false; 369 for (final Collection<E> item : all) { 370 changed |= item.removeAll(coll); 371 } 372 return changed; 373 } 374 375 /** 376 * Removes a collection from the those being decorated in this composite. 377 * 378 * @param coll collection to be removed 379 */ 380 public void removeComposited(final Collection<E> coll) { 381 all.remove(coll); 382 } 383 384 /** 385 * Removes all of the elements of this collection that satisfy the given predicate from this composite collection. 386 * <p> 387 * This implementation calls {@code removeIf} on each collection. 388 * </p> 389 * @param filter a predicate which returns true for elements to be removed 390 * @return true if the collection was modified 391 * @throws UnsupportedOperationException if removeIf is unsupported 392 * @since 4.4 393 */ 394 @Override 395 public boolean removeIf(final Predicate<? super E> filter) { 396 if (Objects.isNull(filter)) { 397 return false; 398 } 399 boolean changed = false; 400 for (final Collection<E> item : all) { 401 changed |= item.removeIf(filter); 402 } 403 return changed; 404 } 405 406 /** 407 * Retains all the elements in the specified collection in this composite collection, 408 * removing all others. 409 * <p> 410 * This implementation calls {@code retainAll()} on each collection. 411 * </p> 412 * @param coll the collection to remove 413 * @return true if the collection was modified 414 * @throws UnsupportedOperationException if retainAll is unsupported 415 */ 416 @Override 417 public boolean retainAll(final Collection<?> coll) { 418 boolean changed = false; 419 if (coll != null) { 420 for (final Collection<E> item : all) { 421 changed |= item.retainAll(coll); 422 } 423 } 424 return changed; 425 } 426 427 /** 428 * Specify a CollectionMutator strategy instance to handle changes. 429 * 430 * @param mutator the mutator to use 431 */ 432 public void setMutator(final CollectionMutator<E> mutator) { 433 this.mutator = mutator; 434 } 435 436 /** 437 * Gets the size of this composite collection. 438 * <p> 439 * This implementation calls {@code size()} on each collection. 440 * </p> 441 * @return total number of elements in all contained containers 442 */ 443 @Override 444 public int size() { 445 int size = 0; 446 for (final Collection<E> item : all) { 447 size += item.size(); 448 } 449 return size; 450 } 451 452 /** 453 * Returns an array containing all of the elements in this composite. 454 * 455 * @return an object array of all the elements in the collection 456 */ 457 @Override 458 public Object[] toArray() { 459 final Object[] result = new Object[size()]; 460 int i = 0; 461 for (final Iterator<E> it = iterator(); it.hasNext(); i++) { 462 result[i] = it.next(); 463 } 464 return result; 465 } 466 467 /** 468 * Returns an object array, populating the supplied array if possible. 469 * See {@code Collection} interface for full details. 470 * 471 * @param <T> the type of the elements in the collection 472 * @param array the array to use, populating if possible 473 * @return an array of all the elements in the collection 474 */ 475 @Override 476 @SuppressWarnings("unchecked") 477 public <T> T[] toArray(final T[] array) { 478 final int size = size(); 479 Object[] result = null; 480 if (array.length >= size) { 481 result = array; 482 } else { 483 result = (Object[]) Array.newInstance(array.getClass().getComponentType(), size); 484 } 485 486 int offset = 0; 487 for (final Collection<E> item : all) { 488 for (final E e : item) { 489 result[offset++] = e; 490 } 491 } 492 if (result.length > size) { 493 result[size] = null; 494 } 495 return (T[]) result; 496 } 497 498 /** 499 * Returns a new collection containing all of the elements 500 * 501 * @return A new ArrayList containing all of the elements in this composite. 502 * The new collection is <em>not</em> backed by this composite. 503 */ 504 public Collection<E> toCollection() { 505 return new ArrayList<>(this); 506 } 507 508 } 509