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