Attached is a CompositeCollection class which allows a single Collection instance to behave correctly as a composite for N collections, and unit tests for that class.

All operations (including iterator removal operations) which are deterministic per the Collection spec are implemented directly. Nondeterministic operations (add, addAll) are pluggable and throw an UnsupportedOperationException if no behavior has been specified.

Implementations of Set and List (which are dependent upon CompositeCollection) are forthcoming as soon as I have a chance.

-Brian


/* The Apache Software License, Version 1.1
 *
 * Copyright (c) 2001-2003 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution,
 *    if any, must include the following acknowledgement:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowledgement may appear in the software itself,
 *    if and wherever such third-party acknowledgements normally appear.
 *
 * 4. The names "Apache", "The Jakarta Project", "Commons", and "Apache Software
 *    Foundation" must not be used to endorse or promote products derived
 *    from this software without prior written permission. For written
 *    permission, please contact [EMAIL PROTECTED]
 *
 * 5. Products derived from this software may not be called "Apache",
 *    "Apache" nor may "Apache" appear in their names without prior
 *    written permission of the Apache Software Foundation.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 *
 */
package org.apache.commons.collections;

import org.apache.commons.collections.iterators.IteratorChain;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

/**
 * Represents a composite collection of collections. Changes to contained
 * collections will be reflected in this class. Changes to this class
 * will be propogated to contained collections. See individual methods
 * for specific behaviors in regard to mutators.
 *
 * @author <a href="mailto:[EMAIL PROTECTED]">Brian McCallister</a>
 */
public class CompositeCollection implements Collection
{
    /** Mutator to handle calls to add(Object) */
    private Mutator addStrat;

    /** Mutator to handle calls to addAll(Collection c) */
    private Mutator addAllStrat;

    /** Collections in the composite */
    protected List all;

    /**
     * Initialize a new, empty, CompositeCollection
     */
    public CompositeCollection()
    {
        this.all = new ArrayList();
    }

    /**
     * @return number of elements in all contained containers
     */
    public int size()
    {
        final Holder size = new Holder();
        size.value = 0;
        this.doToAll(new Closure()
        {
            public void execute(Object o)
            {
                size.value += ((Collection) o).size();
            }
        });
        return size.value;
    }

    /**
     * @return true if all of the contained collections are empty
     */
    public boolean isEmpty()
    {
        final Holder h = new Holder();
        h.bool = true;
        this.doToAll(new Closure()
        {
            public void execute(Object o)
            {
                if (h.bool == false) return;
                h.bool = ((Collection) o).isEmpty();
            }
        });
        return h.bool;
    }

    /**
     * @return true if o is contained in any of the contained collections
     */
    public boolean contains(Object o)
    {
        final Holder h = new Holder();
        h.bool = false;
        h.thing = o;
        this.doToAll(new Closure()
        {
            public void execute(Object o)
            {
                if (h.bool == true) return;
                h.bool = ((Collection) o).contains(h.thing);
            }
        });
        return h.bool;
    }

    /**
     * @return a <code>IteratorChain</code> instance which supports
     *         <code>remove()</code> correctly
     * @see IteratorChain
     */
    public Iterator iterator()
    {
        final Holder holder = new Holder();
        holder.thing = new ArrayList();
        this.doToAll(new Closure()
        {
            public void execute(Object o)
            {
                ((List) holder.thing).add(((Collection) o).iterator());
            }
        });
        return new IteratorChain((Collection) holder.thing);
    }

    /**
     * Returns an array containing all of the elements in the composite
     */
    public Object[] toArray()
    {
        final Object[] ary = new Object[this.size()];
        int i = 0;
        for (Iterator itty = this.iterator(); itty.hasNext(); i++)
        {
            ary[i] = itty.next();
        }
        return ary;
    }

    /**
     * Only partially implemented. Will copy into passed in array, but will throw
     * an UnsupportedOperationException if entire collection doesn't fit. This
     * needs to be fixed.
     */
    public Object[] toArray(Object a[])
    {
        if (this.size() <= a.length)
        {
            int c = 0;
            for (Iterator i = this.iterator(); i.hasNext(); c++)
            {
                a[c] = i.next();
            }
            return a;
        }
        else
        {
            throw new UnsupportedOperationException("Implementation only copies into 
passed" +
                    " in array. Please patch me for appropriate behavior if you need 
it.");
        }
    }

    /**
     * Throws Unsupported Operation Exception if no Mutator is specified
     * via <code>CompositeCollection.setAddMutator()</code> otherwise
     * delegates to the Mutator.
     * <p>
     * @throws ClassCastException if Mutator doesn't return a Boolean
     * @throws UnsupportedOperationException if Mutator hasn't been set
     */
    public boolean add(Object o)
    {
        if (this.addStrat == null) throw new UnsupportedOperationException();

        Object ret = this.addStrat.execute(this.all, this, new Object[] {o});
        if (ret instanceof Boolean) return ((Boolean)ret).booleanValue();
        throw new ClassCastException("Mutator for add must return a Boolean");
    }

    /**
     * Specify a Mutator instance to handle addAll calls. The Mutator <b>must</b>
     * return a Boolean.
     */
    public void setAddMutator(Mutator m)
    {
        this.addStrat = m;
    }

    /**
     * Finds and removes the first element equal to o
     */
    public boolean remove(Object o)
    {
        for (Iterator i = this.iterator(); i.hasNext();)
        {
            if (i.equals(o))
            {
                i.remove();
                return true;
            }
        }
        return false;
    }

    /**
     * This is O(n^2) at the moment, be careful using it
     */
    public boolean containsAll(Collection c)
    {
        for (Iterator i = c.iterator(); i.hasNext();)
        {
            if (!this.contains(i.next())) return false;
        }
        return true;
    }

    /**
     * Throws Unsupported Operation Exception if no Mutator is specified
     * via <code>CompositeCollection.setAddAllMutator()</code> otherwise
     * delegates to the Mutator.
     * <p>
     * @throws ClassCastException if Mutator doesn't return a Boolean
     * @throws UnsupportedOperationException if Mutator hasn't been set
     */
    public boolean addAll(Collection c)
    {
        if (this.addAllStrat == null) throw new UnsupportedOperationException("No 
Mutator set");

        Object ret = this.addAllStrat.execute(this.all, this, new Object[] {c});
        if (ret instanceof Boolean) return ((Boolean)ret).booleanValue();
        throw new ClassCastException("Mutator for addAll must return a Boolean");
    }

    /**
     * Specify a Mutator instance to handle addAll calls. The Mutator <b>must</b>
     * return a Boolean.
     */
    public void setAddAllMutator(Mutator m)
    {
        this.addAllStrat = m;
    }

    /**
     * Removes from every contained collection
     */
    public boolean removeAll(final Collection c)
    {
        final Holder h = new Holder();
        h.bool = false;
        this.doToAll(new Closure()
        {
            public void execute(Object o)
            {
                if (((Collection)o).removeAll(c))
                {
                    h.bool = true;
                }
            }
        });
        return h.bool;
    }

    /**
     * Applies to every contained collection
     */
    public boolean retainAll(final Collection c)
    {
        final Holder h = new Holder();
        h.bool = false;
        this.doToAll(new Closure()
        {
            public void execute(Object o)
            {
                if (((Collection)o).retainAll(c))
                {
                    h.bool = true;
                }
            }
        });
        return h.bool;
    }

    /**
     * Removes all of the elements from this collection (optional operation).
     * This collection will be empty after this method returns unless it
     * throws an exception.
     *
     * @throws UnsupportedOperationException if the <tt>clear</tt> method is
     *         not supported by this collection.
     */
    public void clear()
    {
        this.doToAll(new Closure()
        {
            public void execute(Object o)
            {
                ((Collection)o).clear();
            }
        });
    }


    /**
     * Convenience method equivalent to <code>this.all.each { |x| closure.execute(x) 
}</code>
     */
    protected void doToAll(Closure closure)
    {
        CollectionUtils.forAllDo(this.all, closure);
    }

    /**
     * Add an additional collection to this composite
     */
    public void addCollection(Collection c)
    {
        this.all.add(c);
    }

    public void addCollections(Collection c, Collection d)
    {
        this.addCollection(c);
        this.addCollection(d);
    }

    /**
     * @param c Collection to be removed from the collection of managed collections
     * @return Collection.remove()
     */
    public boolean removeCollection(Collection c)
    {
        return this.all.remove(c);
    }

    /**
     * @return Unmodifiable collection of all collections in this composite
     */
    public Collection getCollections()
    {
        return Collections.unmodifiableCollection(this.all);
    }

    /**
     * Provides a hack around immutable final stuff for closures
     */
    protected static class Holder
    {
        int value;
        boolean bool;
        Object thing;
    }

    /**
     * Pluggable class to handle mutators in indeterminate behavior cases.
     */
    public interface Mutator
    {
        /**
         * @param collections All of the Collection instances in this 
CompositeCollection
         * @param composite The CompositeCollection being changed
         * @param args Arguments to the method call, ie for add(Object o)
         *        this would be an Object[] with one element, o
         * @return value to be returned by the method called
         */
        public Object execute(Collection collections, CompositeCollection composite, 
Object[] args);
    }
}
package org.apache.commons.collections;

import junit.framework.TestCase;

import java.util.HashSet;
import java.util.Collection;
import java.util.Iterator;

public class TestCompositeCollection extends TestCase
{

    private CompositeCollection c;
    private Collection one;
    private Collection two;

    public TestCompositeCollection(String name)
    {
        super(name);
    }

    public void setUp()
    {
        c = new CompositeCollection();
        one = new HashSet();
        two = new HashSet();
    }

    public void testSize()
    {
        HashSet set = new HashSet();
        set.add("a");
        set.add("b");
        c.addCollection(set);
        assertEquals(set.size(), c.size());
    }

    public void testMultipleCollectionsSize()
    {
        HashSet set = new HashSet();
        set.add("a");
        set.add("b");
        c.addCollection(set);
        HashSet other = new HashSet();
        other.add("c");
        c.addCollection(other);
        assertEquals(set.size() + other.size(), c.size());
    }

    public void testIsEmpty()
    {
        assertTrue(c.isEmpty());
        HashSet empty = new HashSet();
        c.addCollection(empty);
        assertTrue(c.isEmpty());
        empty.add("a");
        assertFalse(c.isEmpty());
    }

    public void testContains()
    {
        HashSet empty = new HashSet();
        c.addCollection(empty);
        Object foo = "foo";
        empty.add(foo);
        assertTrue(c.contains(foo));
        assertFalse(c.contains("bar"));
    }

    public void testIterator()
    {
        one.add("1");
        two.add("2");
        c.addCollection(one);
        c.addCollection(two);
        Iterator i = c.iterator();
        Object next = i.next();
        assertTrue(c.contains(next));
        assertTrue(one.contains(next));
        next = i.next();
        i.remove();
        assertFalse(c.contains(next));
        assertFalse(two.contains(next));
    }

    public void testToArray()
    {
        one.add("1");
        two.add("2");
        c.addCollection(one);
        c.addCollection(two);
        Object[] arry = c.toArray();
        assertEquals(c.size(), arry.length);
        assertTrue(c.contains(arry[0]));
        assertTrue(c.contains(arry[1]));
    }

    public void testToArrayArray()
    {
        one.add("1");
        two.add("2");
        c.addCollection(one);
        c.addCollection(two);
        String[] foo = new String[2];
        String[] arry = (String[]) c.toArray(foo);
        assertEquals(c.size(), arry.length);
        assertTrue(c.contains(arry[0]));
        assertTrue(c.contains(arry[1]));
    }

    public void testClear()
    {
        one.add("1");
        two.add("2");
        c.addCollections(one, two);
        c.clear();
        assertTrue(one.isEmpty());
        assertTrue(two.isEmpty());
        assertTrue(c.isEmpty());
    }

    public void testAdd()
    {
        c.addCollection(one);
        try
        {
            c.add("two");
            fail("Should have thrown exception add");
        }
        catch (Exception e)
        {
            assertTrue(true);
        }
    }

    public void testAddAll()
    {
        c.addCollection(one);
        try
        {
            c.addAll(two);
            fail("Should have thrown exception add");
        }
        catch (Exception e)
        {
            assertTrue(true);
        }
    }

    public void testContainsAll()
    {
        one.add("1");
        two.add("1");
        c.addCollection(one);
        assertTrue(c.containsAll(two));
    }

    public void testRetainAll()
    {
        one.add("1");
        one.add("2");
        two.add("1");
        c.addCollection(one);
        c.retainAll(two);
        assertFalse(c.contains("2"));
        assertFalse(one.contains("2"));
        assertTrue(c.contains("1"));
        assertTrue(one.contains("1"));
    }

    public void testAddAllMutator()
    {
        c.setAddAllMutator(new CompositeCollection.Mutator()
        {
            public Object execute(Collection collections, CompositeCollection 
composite, Object[] args)
            {
                Collection c = (Collection) args[0];
                for (Iterator i = collections.iterator(); i.hasNext();)
                {
                    Collection co = (Collection) i.next();
                    co.addAll(c);
                }
                return new Boolean(true);
            }
        });

        c.addCollection(one);
        two.add("foo");
        c.addAll(two);
        assertTrue(c.contains("foo"));
        assertTrue(one.contains("foo"));
    }

    public void testAddMutator()
    {
        c.setAddMutator(new CompositeCollection.Mutator()
        {
            public Object execute(Collection collections, CompositeCollection 
composite, Object[] args)
            {
                Object c = args[0];
                for (Iterator i = collections.iterator(); i.hasNext();)
                {
                    Collection co = (Collection) i.next();
                    co.add(c);
                }
                return new Boolean(true);
            }
        });

        c.addCollection(one);
        c.add("foo");
        assertTrue(c.contains("foo"));
        assertTrue(one.contains("foo"));
    }
}


Attachment: PGP.sig
Description: This is a digitally signed message part

Reply via email to