On Sunday, November 2, 2003, at 07:27 PM, Stephen Colebourne wrote:
I haven't tested it, but I suspect the performance gain to be noticable, and
[collections] has to choose the fastest implementation if it has a choice.
Would you consider the alternative implementation I suggest?

Stephen

Performance optimized version attached. I think this actually reads more clearly anyway. Must try to stop thinking in Ruby when writing Java =) I also cleaned up the spec breaking toArray(Object[] array) implementation.

The highly unoptimized part is adding and removing composited collections. I let ArrayList handle the array resizing for me.

-Brian


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 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.addComposited(set);
        assertEquals(set.size(), c.size());
    }

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

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

    public void testContains()
    {
        HashSet empty = new HashSet();
        c.addComposited(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.addComposited(one);
        c.addComposited(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.addComposited(one);
        c.addComposited(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.addComposited(one);
        c.addComposited(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 testToArrayArrayEmpty()
    {
        one.add("1");
        two.add("2");
        c.addComposited(new Collection[] {one, two});
        String[] sa = (String[]) c.toArray(new String[0]);
        assertEquals(2, sa.length);
    }

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

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

    public void testAddAll()
    {
        c.addComposited(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.addComposited(one);
        assertTrue(c.containsAll(two));
    }

    public void testRetainAll()
    {
        one.add("1");
        one.add("2");
        two.add("1");
        c.addComposited(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 (int i = 0; i < collections.length; i++)
                {
                    collections[i].addAll(c);
                }
                return new Boolean(true);
            }
        });

        c.addComposited(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 (int i = 0; i < collections.length; i++)
                {
                    collections[i].add(c);
                }
                return new Boolean(true);
            }
        });

        c.addComposited(one);
        c.add("foo");
        assertTrue(c.contains("foo"));
        assertTrue(one.contains("foo"));
    }
}
/* 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.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.Collections;
import java.lang.reflect.Array;

/**
 * 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) */
    protected Mutator addStrat;

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

    /** Collections in the composite */
    protected Collection[] all;

    /**
     * Create an empty CompositeCollection
     */
    public CompositeCollection()
    {
        this.all = new Collection[0];
    }

    /**
     * Create a Composite Collection with only c composited
     */
    public CompositeCollection(Collection c)
    {
        super();
        this.addComposited(c);
    }

    /**
     * Create a CompositeCollection with c as the initial list of
     * composited collections.
     */
    public CompositeCollection(Collection[] c)
    {
        this.addComposited(c);
    }

    /**
     * @return number of elements in all contained containers
     */
    public int size()
    {
        int size = 0;
        for (int i = 0; i < this.all.length; ++i)
        {
            size += this.all[i].size();
        }
        return size;
    }

    /**
     * @return true if all of the contained collections are empty
     */
    public boolean isEmpty()
    {
        for (int i = 0; i < this.all.length; ++i)
        {
            if (!this.all[i].isEmpty()) return false;
        }
        return true;
    }

    /**
     * @return true if o is contained in any of the contained collections
     */
    public boolean contains(Object o)
    {
        for (int i = 0; i < this.all.length; ++i)
        {
            if (this.all[i].contains(o)) return true;
        }
        return false;
    }

    /**
     * @return a <code>IteratorChain</code> instance which supports
     *         <code>remove()</code> correctly. It does iterate
     *         over contained collections in the order they were added
     *         but this behavior should not be relied upon.
     * @see IteratorChain
     */
    public Iterator iterator()
    {
        IteratorChain chain = new IteratorChain();
        for (int i = 0; i < this.all.length; ++i)
        {
            chain.addIterator(this.all[i].iterator());
        }
        return chain;
    }

    /**
     * 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;
    }

    /**
     * Per Collection spec
     */
    public Object[] toArray(Object a[])
    {
        int size = this.size();
        Object[] ary = null;
        if (a.length >= size)
        {
            ary = a;
        }
        else
        {
            ary = (Object[]) Array.newInstance(a.getClass().getComponentType() , size);
        }

        int offset = 0;
        for (int i = 0; i < this.all.length; ++i)
        {
            for (Iterator itty = this.all[i].iterator(); itty.hasNext();)
            {
                ary[offset++] = itty.next();
            }
        }
        return ary;
    }

    /**
     * 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(Collection c)
    {
        boolean changed = false;
        for (int i = 0; i < this.all.length; ++i)
        {
            changed = (this.all[i].removeAll(c) || changed);
        }
        return changed;
    }

    /**
     * Applies to every contained collection
     */
    public boolean retainAll(final Collection c)
    {
        boolean changed = false;
        for (int i = 0; i < this.all.length; ++i)
        {
            changed = (this.all[i].retainAll(c) || changed);
        }
        return changed;
    }

    /**
     * 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()
    {
        for (int i = 0; i < this.all.length; ++i)
        {
            this.all[i].clear();
        }
    }

    /**
     * Add these Collections to the list of Collections in this Composite
     *
     * @param comps Collections to be appended to the composite
     */
    public void addComposited(Collection[] comps)
    {
        ArrayList list = new ArrayList(Arrays.asList(this.all));
        list.addAll(Arrays.asList(comps));
        all = (Collection[]) list.toArray(new Collection[list.size()]);
    }

    /**
     * Add an additional collection to this composite
     */
    public void addComposited(Collection c)
    {
        this.addComposited(new Collection[]{c});
    }

    /**
     * Add two additional collection to this composite
     */
    public void addComposited(Collection c, Collection d)
    {
        this.addComposited(new Collection[]{c, d});
    }

    /**
     * @param c Collection to be removed from the collection of managed collections
     */
    public void removeComposited(Collection c)
    {
        ArrayList list = new ArrayList(this.all.length);
        list.addAll(Arrays.asList(this.all));
        list.remove(c);
        this.all = (Collection[]) list.toArray(new Collection[list.size()]);
    }

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

    /**
     * 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);
    }
}



Attachment: PGP.sig
Description: Binary data

Attachment: PGP.sig
Description: Binary data

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

Reply via email to