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