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 * http://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 */ 017package org.apache.commons.collections4.collection; 018 019import java.io.Serializable; 020import java.lang.reflect.Array; 021import java.util.ArrayList; 022import java.util.Collection; 023import java.util.Iterator; 024import java.util.List; 025import java.util.Objects; 026import java.util.function.Predicate; 027 028import org.apache.commons.collections4.CollectionUtils; 029import org.apache.commons.collections4.iterators.EmptyIterator; 030import org.apache.commons.collections4.iterators.IteratorChain; 031import org.apache.commons.collections4.list.UnmodifiableList; 032 033/** 034 * Decorates a collection of other collections to provide a single unified view. 035 * <p> 036 * Changes made to this collection will actually be made on the decorated collection. 037 * Add and remove operations require the use of a pluggable strategy. If no 038 * strategy is provided then add and remove are unsupported. 039 * </p> 040 * @param <E> the type of the elements in the collection 041 * @since 3.0 042 */ 043public class CompositeCollection<E> implements Collection<E>, Serializable { 044 045 /** 046 * Pluggable strategy to handle changes to the composite. 047 * 048 * @param <E> the element being held in the collection 049 */ 050 public interface CollectionMutator<E> extends Serializable { 051 052 /** 053 * Called when an object is to be added to the composite. 054 * 055 * @param composite the CompositeCollection being changed 056 * @param collections all of the Collection instances in this CompositeCollection 057 * @param obj the object being added 058 * @return true if the collection is changed 059 * @throws UnsupportedOperationException if add is unsupported 060 * @throws ClassCastException if the object cannot be added due to its type 061 * @throws NullPointerException if the object cannot be added because its null 062 * @throws IllegalArgumentException if the object cannot be added 063 */ 064 boolean add(CompositeCollection<E> composite, List<Collection<E>> collections, E obj); 065 066 /** 067 * Called when a collection is to be added to the composite. 068 * 069 * @param composite the CompositeCollection being changed 070 * @param collections all of the Collection instances in this CompositeCollection 071 * @param coll the collection being added 072 * @return true if the collection is changed 073 * @throws UnsupportedOperationException if add is unsupported 074 * @throws ClassCastException if the object cannot be added due to its type 075 * @throws NullPointerException if the object cannot be added because its null 076 * @throws IllegalArgumentException if the object cannot be added 077 */ 078 boolean addAll(CompositeCollection<E> composite, 079 List<Collection<E>> collections, 080 Collection<? extends E> coll); 081 082 /** 083 * Called when an object is to be removed to the composite. 084 * 085 * @param composite the CompositeCollection being changed 086 * @param collections all of the Collection instances in this CompositeCollection 087 * @param obj the object being removed 088 * @return true if the collection is changed 089 * @throws UnsupportedOperationException if removed is unsupported 090 * @throws ClassCastException if the object cannot be removed due to its type 091 * @throws NullPointerException if the object cannot be removed because its null 092 * @throws IllegalArgumentException if the object cannot be removed 093 */ 094 boolean remove(CompositeCollection<E> composite, 095 List<Collection<E>> collections, 096 Object obj); 097 098 } 099 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