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