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.
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: + *
MapIterator
, see {@link #mapIterator()}
+ * KeyValue
interface (just cast - no object creation)
+ * 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"); +// } +}