From 40bdd8fe80d882b1b76c2d26d26bb69b032e86a3 Mon Sep 17 00:00:00 2001 From: James Strachan Date: Mon, 17 Sep 2001 16:43:49 +0000 Subject: [PATCH] Added Daniel Rall's SequencedHashMap patch and ported the JUnit test case over to use assertTrue() rather than assert(). git-svn-id: https://svn.apache.org/repos/asf/jakarta/commons/proper/collections/trunk@130501 13f79535-47bb-0310-9956-ffa450edef68 --- .../commons/collections/SequencedHashMap.java | 311 ++++++++++++++++++ .../apache/commons/collections/TestAll.java | 9 +- .../collections/TestSequencedHashMap.java | 153 +++++++++ 3 files changed, 469 insertions(+), 4 deletions(-) create mode 100644 src/java/org/apache/commons/collections/SequencedHashMap.java create mode 100644 src/test/org/apache/commons/collections/TestSequencedHashMap.java diff --git a/src/java/org/apache/commons/collections/SequencedHashMap.java b/src/java/org/apache/commons/collections/SequencedHashMap.java new file mode 100644 index 000000000..f8702f34b --- /dev/null +++ b/src/java/org/apache/commons/collections/SequencedHashMap.java @@ -0,0 +1,311 @@ +package org.apache.commons.collections; + +/* ==================================================================== + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2001 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 acknowledgment: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Apache" and "Apache Software Foundation" and + * "Apache Turbine" must not be used to endorse or promote products + * derived from this software without prior written permission. For + * written permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache", + * "Apache Turbine", nor may "Apache" appear in their name, 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 + * . + */ + +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + *

A {@link java.util.HashMap} whose keys are sequenced. The + * sequencing of the keys allow easy access to the values in the order + * which they were added in. This class is thread safe.

+ * + *

Implementing the List interface is not possible due to a instance + * method name clash between the Collection and the List interface: + * + * + * + * + *
Collectionsboolean remove(Object o)
ListsObject remove(Object o)
+ *

+ * + *

So one cannot implement both interfaces at the same, which is + * unfortunate because the List interface would be very nice in + * conjuction with Velocity.

+ * + *

A slightly more complex implementation and interface could involve + * the use of a list of Map.Entry objects.

+ * + * @author Daniel Rall + * @author Henning P. Schmiedehausen + */ +public class SequencedHashMap extends HashMap +{ + /** + * The index of the eldest element in the collection. + */ + protected static final int ELDEST_INDEX = 0; + + /** + * Indicator for an unknown index. + */ + private static final int UNKNOWN_INDEX = -1; + + /** + * The sequence used to keep track of the hash keys. Younger objects are + * kept towards the end of the list. Does not allow duplicates. + */ + private LinkedList keySequence; + + /** + * Creates a new instance with default storage. + */ + public SequencedHashMap () + { + keySequence = new LinkedList(); + } + + /** + * Creates a new instance with the specified storage. + * + * @param size The storage to allocate up front. + */ + public SequencedHashMap (int size) + { + super(size); + keySequence = new LinkedList(); + } + + /** + * Clears all elements. + */ + public void clear () + { + super.clear(); + keySequence.clear(); + } + + /** + * Creates a shallow copy of this object, preserving the internal + * structure by copying only references. The keys, values, and + * sequence are not clone()'d. + * + * @return A clone of this instance. + */ + public Object clone () + { + SequencedHashMap seqHash = (SequencedHashMap) super.clone(); + seqHash.keySequence = (LinkedList) keySequence.clone(); + return seqHash; + } + + /** + * Returns the key at the specified index. + */ + public Object get (int index) + { + return keySequence.get(index); + } + + /** + * Returns the value at the specified index. + */ + public Object getValue (int index) + { + return get(get(index)); + } + + /** + * Returns the index of the specified key. + */ + public int indexOf (Object key) + { + return keySequence.indexOf(key); + } + + /** + * Returns a key iterator. + */ + public Iterator iterator () + { + return keySequence.iterator(); + } + + /** + * Returns the last index of the specified key. + */ + public int lastIndexOf (Object key) + { + return keySequence.lastIndexOf(key); + } + + /** + * Returns the ordered sequence of keys. + * + * This method is meant to be used for retrieval of Key / Value pairs + * in e.g. Velocity: + *
+     * ## $table contains a sequenced hashtable
+     * #foreach ($key in $table.sequence())
+     * <TR>
+     * <TD>Key: $key</TD>
+     * </TD>Value: $table.get($key)</TD>
+     * </TR>
+     * #end
+     * 
+ * + * @return The ordered list of keys. + */ + public List sequence() + { + return keySequence; + } + + /** + * Stores the provided key/value pair. Freshens the sequence of existing + * elements. + * + * @param key The key to the provided value. + * @param value The value to store. + * @return The previous value for the specified key, or + * null if none. + */ + public Object put (Object key, Object value) + { + Object prevValue = super.put(key, value); + freshenSequence(key, prevValue); + return prevValue; + } + + /** + * Freshens the sequence of the element value if + * value is not null. + * + * @param key The key whose sequence to freshen. + * @param value The value whose existance to check before removing the old + * key sequence. + */ + protected void freshenSequence(Object key, Object value) + { + if (value != null) + { + // Freshening existing element's sequence. + keySequence.remove(key); + } + keySequence.add(key); + } + + /** + * Stores the provided key/value pairs. + * + * @param t The key/value pairs to store. + */ + public void putAll (Map t) + { + Set set = t.entrySet(); + for (Iterator iter = set.iterator(); iter.hasNext(); ) + { + Map.Entry e = (Map.Entry)iter.next(); + put(e.getKey(), e.getValue()); + } + } + + /** + * Removes the element at the specified index. + * + * @param index The index of the object to remove. + * @return The previous value coressponding the key, or + * null if none existed. + */ + public Object remove (int index) + { + return remove(index, null); + } + + /** + * Removes the element with the specified key. + * + * @param key The Map key of the object to remove. + * @return The previous value coressponding the key, or + * null if none existed. + */ + public Object remove (Object key) + { + return remove(UNKNOWN_INDEX, key); + } + + /** + * Removes the element with the specified key or index. + * + * @param index The index of the object to remove, or + * UNKNOWN_INDEX if not known. + * @param key The Map key of the object to remove. + * @return The previous value coressponding the key, or + * null if none existed. + */ + private final Object remove (int index, Object key) + { + if (index == UNKNOWN_INDEX) + { + index = indexOf(key); + } + if (key == null) + { + key = get(index); + } + if (index != UNKNOWN_INDEX) + { + keySequence.remove(index); + } + return super.remove(key); + } +} + diff --git a/src/test/org/apache/commons/collections/TestAll.java b/src/test/org/apache/commons/collections/TestAll.java index 3dea4ec5f..bdc89389b 100644 --- a/src/test/org/apache/commons/collections/TestAll.java +++ b/src/test/org/apache/commons/collections/TestAll.java @@ -1,7 +1,7 @@ /* - * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//collections/src/test/org/apache/commons/collections/TestAll.java,v 1.11 2001/08/29 15:28:07 jstrachan Exp $ - * $Revision: 1.11 $ - * $Date: 2001/08/29 15:28:07 $ + * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//collections/src/test/org/apache/commons/collections/TestAll.java,v 1.12 2001/09/17 16:43:49 jstrachan Exp $ + * $Revision: 1.12 $ + * $Date: 2001/09/17 16:43:49 $ * * ==================================================================== * @@ -66,7 +66,7 @@ import junit.framework.*; /** * Entry point for all Collections tests. * @author Rodney Waldhoff - * @version $Id: TestAll.java,v 1.11 2001/08/29 15:28:07 jstrachan Exp $ + * @version $Id: TestAll.java,v 1.12 2001/09/17 16:43:49 jstrachan Exp $ */ public class TestAll extends TestCase { public TestAll(String testName) { @@ -90,6 +90,7 @@ public class TestAll extends TestCase { suite.addTest(TestFastTreeMap1.suite()); suite.addTest(TestHashBag.suite()); suite.addTest(TestHashMap.suite()); + suite.addTest(TestSequencedHashMap.suite()); suite.addTest(TestSingletonIterator.suite()); suite.addTest(TestTreeBag.suite()); suite.addTest(TestTreeMap.suite()); diff --git a/src/test/org/apache/commons/collections/TestSequencedHashMap.java b/src/test/org/apache/commons/collections/TestSequencedHashMap.java new file mode 100644 index 000000000..071ffa9a2 --- /dev/null +++ b/src/test/org/apache/commons/collections/TestSequencedHashMap.java @@ -0,0 +1,153 @@ +package org.apache.commons.collections; + +/* ==================================================================== + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2001 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 acknowledgment: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Apache" and "Apache Software Foundation" and + * "Apache Turbine" must not be used to endorse or promote products + * derived from this software without prior written permission. For + * written permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache", + * "Apache Turbine", nor may "Apache" appear in their name, 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 + * . + */ + +import java.util.Iterator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; + +/** + * Unit tests {@link org.apache.commons.collections.SequencedHashMap}. + * + * @author Daniel Rall + * @author Henning P. Schmiedehausen + * @author James Strachan + */ +public class TestSequencedHashMap extends TestHashMap +{ + /** + * The instance to experiment on. + */ + protected SequencedHashMap labRat; + + public TestSequencedHashMap(String name) { + super(name); + } + + public static Test suite() { + return new TestSuite(TestSequencedHashMap.class); + } + + public static void main(String[] args[]) { + String[] testCaseName = { TestSequencedHashMap.class.getName() }; + junit.textui.TestRunner.main(testCaseName); + } + + public void setUp() { + super.setUp(); + labRat = new SequencedHashMap(); + } + + public Map makeMap() { + return new SequencedHashMap(); + } + + protected Object[] getKeys() { + return new Object[] { "foo", "baz", "eek" }; + } + + protected Object[] getValues() { + return new Object[] { "bar", "frob", new Object() }; + } + + public void testSequenceMap() throws Throwable { + Object[] keys = getKeys(); + int expectedSize = keys.length; + Object[] values = getValues(); + for (int i = 0; i < expectedSize; i++) { + labRat.put(keys[i], values[i]); + } + + // Test size(). + assertEquals("size() does not match expected size", + expectedSize, labRat.size()); + + // Test clone(), iterator(), and get(Object). + SequencedHashMap clone = (SequencedHashMap) labRat.clone(); + assertEquals("Size of clone does not match original", + labRat.size(), clone.size()); + Iterator origKeys = labRat.keySet().iterator(); + Iterator copiedKeys = clone.keySet().iterator(); + while (origKeys.hasNext()) { + Object origKey = origKeys.next(); + Object copiedKey = copiedKeys.next(); + assertEquals("Cloned key does not match orginal", + origKey, copiedKey); + assertEquals("Cloned value does not match original", + labRat.get(origKey), clone.get(copiedKey)); + } + assertTrue("iterator() returned different number of elements than keys()", + !copiedKeys.hasNext()); + + // Test sequence() + List seq = labRat.sequence(); + assertEquals("sequence() returns more keys than in the Map", + expectedSize, seq.size()); + + for (int i = 0; i < seq.size(); i++) { + assertEquals("Key " + i + " is not the same as the key in the Map", + keys[i], seq.get(i)); + } + } + + protected void tearDown() { + labRat = null; + } +} \ No newline at end of file