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