diff --git a/RELEASE-NOTES.html b/RELEASE-NOTES.html index 56858ad1e..5e7a25be0 100644 --- a/RELEASE-NOTES.html +++ b/RELEASE-NOTES.html @@ -25,6 +25,7 @@ No interface changes, or deprecations have occurred.

NEW CLASSES

diff --git a/data/test/SingletonMap.emptyCollection.version3.1.obj b/data/test/SingletonMap.emptyCollection.version3.1.obj new file mode 100644 index 000000000..89dbcbcdf Binary files /dev/null and b/data/test/SingletonMap.emptyCollection.version3.1.obj differ diff --git a/data/test/SingletonMap.fullCollection.version3.1.obj b/data/test/SingletonMap.fullCollection.version3.1.obj new file mode 100644 index 000000000..ef4b96742 Binary files /dev/null and b/data/test/SingletonMap.fullCollection.version3.1.obj differ diff --git a/src/java/org/apache/commons/collections/map/SingletonMap.java b/src/java/org/apache/commons/collections/map/SingletonMap.java new file mode 100644 index 000000000..ad89301aa --- /dev/null +++ b/src/java/org/apache/commons/collections/map/SingletonMap.java @@ -0,0 +1,592 @@ +/* + * Copyright 2003-2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections.map; + +import java.io.Serializable; +import java.util.AbstractSet; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; + +import org.apache.commons.collections.BoundedMap; +import org.apache.commons.collections.KeyValue; +import org.apache.commons.collections.MapIterator; +import org.apache.commons.collections.OrderedMap; +import org.apache.commons.collections.OrderedMapIterator; +import org.apache.commons.collections.ResettableIterator; +import org.apache.commons.collections.iterators.SingletonIterator; +import org.apache.commons.collections.keyvalue.TiedMapEntry; + +/** + * A Map implementation that holds a single item and is fixed size. + *

+ * The single key/value pair is specified at creation. + * The map is fixed size so any action that would change the size is disallowed. + * However, the put or setValue methods can change + * the value associated with the key. + *

+ * If trying to remove or clear the map, an UnsupportedOperationException is thrown. + * If trying to put a new mapping into the map, an IllegalArgumentException is thrown. + * The put method will only suceed if the key specified is the same as the + * singleton key. + *

+ * The key and value can be obtained by: + *

+ * + * @since Commons Collections 3.1 + * @version $Revision: 1.1 $ $Date: 2004/04/09 14:46:35 $ + * + * @author Stephen Colebourne + */ +public class SingletonMap + implements OrderedMap, BoundedMap, KeyValue, Serializable, Cloneable { + + /** Serialization version */ + private static final long serialVersionUID = -8931271118676803261L; + + /** Singleton key */ + private final Object key; + /** Singleton value */ + private Object value; + + /** + * Constructor that creates a map of null to null. + */ + public SingletonMap() { + super(); + this.key = null; + } + + /** + * Constructor specifying the key and value. + * + * @param key the key to use + * @param value the value to use + */ + public SingletonMap(Object key, Object value) { + super(); + this.key = key; + this.value = value; + } + + /** + * Constructor specifying the key and value as a KeyValue. + * + * @param keyValue the key value pair to use + */ + public SingletonMap(KeyValue keyValue) { + super(); + this.key = keyValue.getKey(); + this.value = keyValue.getValue(); + } + + /** + * Constructor specifying the key and value as a MapEntry. + * + * @param keyValue the key value pair to use + */ + public SingletonMap(Map.Entry entry) { + super(); + this.key = entry.getKey(); + this.value = entry.getValue(); + } + + /** + * Constructor copying elements from another map. + * + * @param map the map to copy, must be size 1 + * @throws NullPointerException if the map is null + * @throws IllegalArgumentException if the size is not 1 + */ + public SingletonMap(Map map) { + super(); + if (map.size() != 1) { + throw new IllegalArgumentException("The map size must be 1"); + } + Map.Entry entry = (Map.Entry) map.entrySet().iterator().next(); + this.key = entry.getKey(); + this.value = entry.getValue(); + } + + // KeyValue + //----------------------------------------------------------------------- + /** + * Gets the key. + * + * @return the key + */ + public Object getKey() { + return key; + } + + /** + * Gets the value. + * + * @return the value + */ + public Object getValue() { + return value; + } + + /** + * Sets the value. + * + * @param value the new value to set + * @return the old value + */ + public Object setValue(Object value) { + Object old = this.value; + this.value = value; + return old; + } + + // BoundedMap + //----------------------------------------------------------------------- + /** + * Is the map currently full, always true. + * + * @return true always + */ + public boolean isFull() { + return true; + } + + /** + * Gets the maximum size of the map, always 1. + * + * @return 1 always + */ + public int maxSize() { + return 1; + } + + // Map + //----------------------------------------------------------------------- + /** + * Gets the value mapped to the key specified. + * + * @param key the key + * @return the mapped value, null if no match + */ + public Object get(Object key) { + if (isEqualKey(key)) { + return value; + } + return null; + } + + /** + * Gets the size of the map, always 1. + * + * @return the size of 1 + */ + public int size() { + return 1; + } + + /** + * Checks whether the map is currently empty, which it never is. + * + * @return false always + */ + public boolean isEmpty() { + return false; + } + + //----------------------------------------------------------------------- + /** + * Checks whether the map contains the specified key. + * + * @param key the key to search for + * @return true if the map contains the key + */ + public boolean containsKey(Object key) { + return (isEqualKey(key)); + } + + /** + * Checks whether the map contains the specified value. + * + * @param value the value to search for + * @return true if the map contains the key + */ + public boolean containsValue(Object value) { + return (isEqualValue(value)); + } + + //----------------------------------------------------------------------- + /** + * Puts a key-value mapping into this map where the key must match the existing key. + *

+ * An IllegalArgumentException is thrown if the key does not match as the map + * is fixed size. + * + * @param key the key to set, must be the key of the map + * @param value the value to set + * @return the value previously mapped to this key, null if none + * @throws IllegalArgumentException if the key does not match + */ + public Object put(Object key, Object value) { + if (isEqualKey(key)) { + return setValue(value); + } + throw new IllegalArgumentException("Cannot put new key/value pair - Map is fixed size singleton"); + } + + /** + * Puts the values from the specified map into this map. + *

+ * The map must be of size 0 or size 1. + * If it is size 1, the key must match the key of this map otherwise an + * IllegalArgumentException is thrown. + * + * @param map the map to add, must be size 0 or 1, and the key must match + * @throws NullPointerException if the map is null + * @throws IllegalArgumentException if the key does not match + */ + public void putAll(Map map) { + switch (map.size()) { + case 0: + return; + + case 1: + Map.Entry entry = (Map.Entry) map.entrySet().iterator().next(); + put(entry.getKey(), entry.getValue()); + return; + + default: + throw new IllegalArgumentException("The map size must be 0 or 1"); + } + } + + /** + * Unsupported operation. + * + * @param key the mapping to remove + * @return the value mapped to the removed key, null if key not in map + * @throws UnsupportedOperationException always + */ + public Object remove(Object key) { + throw new UnsupportedOperationException(); + } + + /** + * Unsupported operation. + */ + public void clear() { + throw new UnsupportedOperationException(); + } + + //----------------------------------------------------------------------- + /** + * Gets the entrySet view of the map. + * Changes made via setValue affect this map. + * To simply iterate through the entries, use {@link #mapIterator()}. + * + * @return the entrySet view + */ + public Set entrySet() { + Map.Entry entry = new TiedMapEntry(this, getKey()); + return Collections.singleton(entry); + } + + /** + * Gets the unmodifiable keySet view of the map. + * Changes made to the view affect this map. + * To simply iterate through the keys, use {@link #mapIterator()}. + * + * @return the keySet view + */ + public Set keySet() { + return Collections.singleton(key); + } + + /** + * Gets the unmodifiable values view of the map. + * Changes made to the view affect this map. + * To simply iterate through the values, use {@link #mapIterator()}. + * + * @return the values view + */ + public Collection values() { + return new SingletonValues(this); + } + + /** + * Gets an iterator over the map. + * Changes made to the iterator using setValue affect this map. + * The remove method is unsupported. + *

+ * A MapIterator returns the keys in the map. It also provides convenient + * methods to get the key and value, and set the value. + * It avoids the need to create an entrySet/keySet/values object. + * It also avoids creating the Map Entry object. + * + * @return the map iterator + */ + public MapIterator mapIterator() { + return new SingletonMapIterator(this); + } + + // OrderedMap + //----------------------------------------------------------------------- + /** + * Obtains an OrderedMapIterator over the map. + *

+ * A ordered map iterator is an efficient way of iterating over maps + * in both directions. + * + * @return an ordered map iterator + */ + public OrderedMapIterator orderedMapIterator() { + return new SingletonMapIterator(this); + } + + /** + * Gets the first (and only) key in the map. + * + * @return the key + */ + public Object firstKey() { + return getKey(); + } + + /** + * Gets the last (and only) key in the map. + * + * @return the key + */ + public Object lastKey() { + return getKey(); + } + + /** + * Gets the next key after the key specified, always null. + * + * @param key the next key + * @return null always + */ + public Object nextKey(Object key) { + return null; + } + + /** + * Gets the previous key before the key specified, always null. + * + * @param key the next key + * @return null always + */ + public Object previousKey(Object key) { + return null; + } + + //----------------------------------------------------------------------- + /** + * Compares the specified key to the stored key. + * + * @param key the key to compare + * @return true if equal + */ + protected boolean isEqualKey(Object key) { + return (key == null ? getKey() == null : key.equals(getKey())); + } + + /** + * Compares the specified value to the stored value. + * + * @param value the value to compare + * @return true if equal + */ + protected boolean isEqualValue(Object value) { + return (value == null ? getValue() == null : value.equals(getValue())); + } + + //----------------------------------------------------------------------- + /** + * SingletonMapIterator. + */ + static class SingletonMapIterator implements OrderedMapIterator, ResettableIterator { + private final SingletonMap parent; + private boolean hasNext = true; + private boolean canGetSet = false; + + SingletonMapIterator(SingletonMap parent) { + super(); + this.parent = parent; + } + + public boolean hasNext() { + return hasNext; + } + + public Object next() { + if (hasNext == false) { + throw new NoSuchElementException(AbstractHashedMap.NO_NEXT_ENTRY); + } + hasNext = false; + canGetSet = true; + return parent.getKey(); + } + + public boolean hasPrevious() { + return (hasNext == false); + } + + public Object previous() { + if (hasNext == true) { + throw new NoSuchElementException(AbstractHashedMap.NO_PREVIOUS_ENTRY); + } + hasNext = true; + return parent.getKey(); + } + + public void remove() { + throw new UnsupportedOperationException(); + } + + public Object getKey() { + if (canGetSet == false) { + throw new IllegalStateException(AbstractHashedMap.GETKEY_INVALID); + } + return parent.getKey(); + } + + public Object getValue() { + if (canGetSet == false) { + throw new IllegalStateException(AbstractHashedMap.GETVALUE_INVALID); + } + return parent.getValue(); + } + + public Object setValue(Object value) { + if (canGetSet == false) { + throw new IllegalStateException(AbstractHashedMap.SETVALUE_INVALID); + } + return parent.setValue(value); + } + + public void reset() { + hasNext = true; + } + + public String toString() { + if (hasNext) { + return "Iterator[]"; + } else { + return "Iterator[" + getKey() + "=" + getValue() + "]"; + } + } + } + + /** + * Values implementation for the SingletonMap. + * This class is needed as values is a view that must update as the map updates. + */ + static class SingletonValues extends AbstractSet implements Serializable { + private static final long serialVersionUID = -3689524741863047872L; + private final SingletonMap parent; + + SingletonValues(SingletonMap parent) { + super(); + this.parent = parent; + } + + public int size() { + return 1; + } + public boolean isEmpty() { + return false; + } + public boolean contains(Object object) { + return parent.containsValue(object); + } + public void clear() { + throw new UnsupportedOperationException(); + } + public Iterator iterator() { + return new SingletonIterator(parent.getValue(), false); + } + } + + //----------------------------------------------------------------------- + /** + * Clones the map without cloning the key or value. + * + * @return a shallow clone + */ + public Object clone() { + try { + SingletonMap cloned = (SingletonMap) super.clone(); + return cloned; + } catch (CloneNotSupportedException ex) { + throw new InternalError(); + } + } + + /** + * Compares this map with another. + * + * @param obj the object to compare to + * @return true if equal + */ + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof Map == false) { + return false; + } + Map other = (Map) obj; + if (other.size() != 1) { + return false; + } + Map.Entry entry = (Map.Entry) other.entrySet().iterator().next(); + return isEqualKey(entry.getKey()) && isEqualValue(entry.getValue()); + } + + /** + * Gets the standard Map hashCode. + * + * @return the hash code defined in the Map interface + */ + public int hashCode() { + return (getKey() == null ? 0 : getKey().hashCode()) ^ + (getValue() == null ? 0 : getValue().hashCode()); + } + + /** + * Gets the map as a String. + * + * @return a string version of the map + */ + public String toString() { + return new StringBuffer(128) + .append('{') + .append((getKey() == this ? "(this Map)" : getKey())) + .append('=') + .append((getValue() == this ? "(this Map)" : getValue())) + .append('}') + .toString(); + } + +} diff --git a/src/test/org/apache/commons/collections/map/TestAll.java b/src/test/org/apache/commons/collections/map/TestAll.java index bd0d74ea4..1225a0eee 100644 --- a/src/test/org/apache/commons/collections/map/TestAll.java +++ b/src/test/org/apache/commons/collections/map/TestAll.java @@ -23,7 +23,7 @@ import junit.framework.TestSuite; * Entry point for tests. * * @since Commons Collections 3.0 - * @version $Revision: 1.14 $ $Date: 2004/02/18 01:20:37 $ + * @version $Revision: 1.15 $ $Date: 2004/04/09 14:46:35 $ * * @author Stephen Colebourne */ @@ -60,6 +60,7 @@ public class TestAll extends TestCase { suite.addTest(TestTransformedSortedMap.suite()); suite.addTest(TestPredicatedMap.suite()); suite.addTest(TestPredicatedSortedMap.suite()); + suite.addTest(TestSingletonMap.suite()); suite.addTest(TestUnmodifiableMap.suite()); suite.addTest(TestUnmodifiableOrderedMap.suite()); suite.addTest(TestUnmodifiableSortedMap.suite()); diff --git a/src/test/org/apache/commons/collections/map/TestSingletonMap.java b/src/test/org/apache/commons/collections/map/TestSingletonMap.java new file mode 100644 index 000000000..cf9b303ce --- /dev/null +++ b/src/test/org/apache/commons/collections/map/TestSingletonMap.java @@ -0,0 +1,183 @@ +/* + * Copyright 2001-2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections.map; + +import java.util.HashMap; +import java.util.Map; + +import junit.framework.Test; +import junit.textui.TestRunner; + +import org.apache.commons.collections.BoundedMap; +import org.apache.commons.collections.BulkTest; +import org.apache.commons.collections.KeyValue; + +/** + * JUnit tests. + * + * @version $Revision: 1.1 $ $Date: 2004/04/09 14:46:35 $ + * + * @author Stephen Colebourne + */ +public class TestSingletonMap extends AbstractTestOrderedMap { + + private static final Integer ONE = new Integer(1); + private static final Integer TWO = new Integer(2); + private static final String TEN = "10"; + private static final String TWENTY = "20"; + + public TestSingletonMap(String testName) { + super(testName); + } + + public static void main(String[] args) { + TestRunner.run(suite()); + } + + public static Test suite() { + return BulkTest.makeSuite(TestSingletonMap.class); + } + + //----------------------------------------------------------------------- + public Map makeEmptyMap() { + // need an empty singleton map, but thats not possible + // use a ridiculous fake instead to make the tests pass + return UnmodifiableOrderedMap.decorate(ListOrderedMap.decorate(new HashMap())); + } + + public String[] ignoredTests() { + // the ridiculous map above still doesn't pass these tests + // but its not relevant, so we ignore them + return new String[] { + "TestSingletonMap.bulkTestMapIterator.testEmptyMapIterator", + "TestSingletonMap.bulkTestOrderedMapIterator.testEmptyMapIterator", + }; + } + + + public Map makeFullMap() { + return new SingletonMap(ONE, TWO); + } + + public boolean isPutAddSupported() { + return false; + } + + public boolean isRemoveSupported() { + return false; + } + + public Object[] getSampleKeys() { + return new Object[] {ONE}; + } + + public Object[] getSampleValues() { + return new Object[] {TWO}; + } + + public Object[] getNewSampleValues() { + return new Object[] {TEN}; + } + + //----------------------------------------------------------------------- + public void testClone() { + SingletonMap map = new SingletonMap(ONE, TWO); + assertEquals(1, map.size()); + SingletonMap cloned = (SingletonMap) map.clone(); + assertEquals(1, cloned.size()); + assertEquals(true, cloned.containsKey(ONE)); + assertEquals(true, cloned.containsValue(TWO)); + } + + public void testKeyValue() { + SingletonMap map = new SingletonMap(ONE, TWO); + assertEquals(1, map.size()); + assertEquals(ONE, map.getKey()); + assertEquals(TWO, map.getValue()); + assertTrue(map instanceof KeyValue); + } + + public void testBoundedMap() { + SingletonMap map = new SingletonMap(ONE, TWO); + assertEquals(1, map.size()); + assertEquals(true, map.isFull()); + assertEquals(1, map.maxSize()); + assertTrue(map instanceof BoundedMap); + } + + //----------------------------------------------------------------------- +// public BulkTest bulkTestMapIterator() { +// return new TestFlatMapIterator(); +// } +// +// public class TestFlatMapIterator extends AbstractTestOrderedMapIterator { +// public TestFlatMapIterator() { +// super("TestFlatMapIterator"); +// } +// +// public Object[] addSetValues() { +// return TestSingletonMap.this.getNewSampleValues(); +// } +// +// public boolean supportsRemove() { +// return TestSingletonMap.this.isRemoveSupported(); +// } +// +// public boolean supportsSetValue() { +// return TestSingletonMap.this.isSetValueSupported(); +// } +// +// public MapIterator makeEmptyMapIterator() { +// resetEmpty(); +// return ((Flat3Map) TestSingletonMap.this.map).mapIterator(); +// } +// +// public MapIterator makeFullMapIterator() { +// resetFull(); +// return ((Flat3Map) TestSingletonMap.this.map).mapIterator(); +// } +// +// public Map getMap() { +// // assumes makeFullMapIterator() called first +// return TestSingletonMap.this.map; +// } +// +// public Map getConfirmedMap() { +// // assumes makeFullMapIterator() called first +// return TestSingletonMap.this.confirmed; +// } +// +// public void verify() { +// super.verify(); +// TestSingletonMap.this.verify(); +// } +// } + + public String getCompatibilityVersion() { + return "3.1"; + } + +// public void testCreate() throws Exception { +// resetEmpty(); +// writeExternalFormToDisk( +// (java.io.Serializable) map, +// "D:/dev/collections/data/test/SingletonMap.emptyCollection.version3.1.obj"); +// resetFull(); +// writeExternalFormToDisk( +// (java.io.Serializable) map, +// "D:/dev/collections/data/test/SingletonMap.fullCollection.version3.1.obj"); +// } +}