From 4c282328c8462b49b8773481251abbd8443f46dc Mon Sep 17 00:00:00 2001 From: Stephen Colebourne Date: Tue, 22 Mar 2005 22:53:50 +0000 Subject: [PATCH] Add DefaultedMap that returns a default value if the key is not in the map RFE 30911, by Rafael U. C. Afonso git-svn-id: https://svn.apache.org/repos/asf/jakarta/commons/proper/collections/trunk@158692 13f79535-47bb-0310-9956-ffa450edef68 --- RELEASE-NOTES.html | 1 + .../commons/collections/map/DefaultedMap.java | 189 ++++++++++++++++++ .../commons/collections/map/TestAll.java | 3 +- .../collections/map/TestDefaultedMap.java | 154 ++++++++++++++ 4 files changed, 346 insertions(+), 1 deletion(-) create mode 100644 src/java/org/apache/commons/collections/map/DefaultedMap.java create mode 100644 src/test/org/apache/commons/collections/map/TestDefaultedMap.java diff --git a/RELEASE-NOTES.html b/RELEASE-NOTES.html index e513dd179..56f3fec87 100644 --- a/RELEASE-NOTES.html +++ b/RELEASE-NOTES.html @@ -38,6 +38,7 @@ There are no new deprecations.

NEW CLASSES

diff --git a/src/java/org/apache/commons/collections/map/DefaultedMap.java b/src/java/org/apache/commons/collections/map/DefaultedMap.java new file mode 100644 index 000000000..3a3f754da --- /dev/null +++ b/src/java/org/apache/commons/collections/map/DefaultedMap.java @@ -0,0 +1,189 @@ +/* + * Copyright 2005 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.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +import org.apache.commons.collections.Factory; +import org.apache.commons.collections.Transformer; +import org.apache.commons.collections.functors.ConstantTransformer; +import org.apache.commons.collections.functors.FactoryTransformer; + +/** + * Decorates another Map returning a default value if the map + * does not contain the requested key. + *

+ * When the {@link #get(Object)} method is called with a key that does not + * exist in the map, this map will return the default value specified in + * the constructor/factory. Only the get method is altered, so the + * {@link Map#containsKey(Object)} can be used to determine if a key really + * is in the map or not. + *

+ * The defaulted value is not added to the map. + * Compare this behaviour with {@link LazyMap}, which does add the value + * to the map (via a Transformer). + *

+ * For instance: + *

+ * Map map = new DefaultedMap("NULL");
+ * Object obj = map.get("Surname");
+ * // obj == "NULL"
+ * 
+ * After the above code is executed the map is still empty. + * + * @since Commons Collections 3.2 + * @version $Revision: 1.7 $ $Date$ + * + * @author Stephen Colebourne + * @author Rafael U.C. Afonso + * @see LazyMap + */ +public class DefaultedMap + extends AbstractMapDecorator + implements Map, Serializable { + + /** Serialization version */ + private static final long serialVersionUID = 19698628745827L; + + /** The transformer to use if the map does not contain a key */ + protected final Object value; + + //----------------------------------------------------------------------- + /** + * Factory method to create a defaulting map. + *

+ * The value specified is returned when a missing key is found. + * + * @param map the map to decorate, must not be null + * @param defaultValue the default value to return when the key is not found + * @throws IllegalArgumentException if map is null + */ + public static Map decorate(Map map, Object defaultValue) { + if (defaultValue instanceof Transformer) { + defaultValue = ConstantTransformer.getInstance(defaultValue); + } + return new DefaultedMap(map, defaultValue); + } + + /** + * Factory method to create a defaulting map. + *

+ * The factory specified is called when a missing key is found. + * The result will be returned as the result of the map get(key) method. + * + * @param map the map to decorate, must not be null + * @param factory the factory to use, must not be null + * @throws IllegalArgumentException if map or factory is null + */ + public static Map decorate(Map map, Factory factory) { + if (factory == null) { + throw new IllegalArgumentException("Factory must not be null"); + } + return new DefaultedMap(map, FactoryTransformer.getInstance(factory)); + } + + /** + * Factory method to create a defaulting map. + *

+ * The transformer specified is called when a missing key is found. + * The key is passed to the transformer as the input, and the result + * will be returned as the result of the map get(key) method. + * + * @param map the map to decorate, must not be null + * @param factory the factory to use, must not be null + * @throws IllegalArgumentException if map or factory is null + */ + public static Map decorate(Map map, Transformer factory) { + if (factory == null) { + throw new IllegalArgumentException("Transformer must not be null"); + } + return new DefaultedMap(map, factory); + } + + //----------------------------------------------------------------------- + /** + * Constructs a new empty DefaultedMap that decorates + * a HashMap. + *

+ * The object passed in will be returned by the map whenever an + * unknown key is requested. + * + * @param defaultValue the default value to return when the key is not found + */ + public DefaultedMap(Object defaultValue) { + super(new HashMap()); + if (defaultValue instanceof Transformer) { + defaultValue = ConstantTransformer.getInstance(defaultValue); + } + this.value = defaultValue; + } + + /** + * Constructor that wraps (not copies). + * + * @param map the map to decorate, must not be null + * @param value the value to use + * @throws IllegalArgumentException if map or transformer is null + */ + protected DefaultedMap(Map map, Object value) { + super(map); + this.value = value; + } + + //----------------------------------------------------------------------- + /** + * Write the map out using a custom routine. + * + * @param out the output stream + * @throws IOException + */ + private void writeObject(ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + out.writeObject(map); + } + + /** + * Read the map in using a custom routine. + * + * @param in the input stream + * @throws IOException + * @throws ClassNotFoundException + */ + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + map = (Map) in.readObject(); + } + + //----------------------------------------------------------------------- + public Object get(Object key) { + // create value for key if key is not currently in the map + if (map.containsKey(key) == false) { + if (value instanceof Transformer) { + return ((Transformer) value).transform(key); + } + return value; + } + return map.get(key); + } + + // no need to wrap keySet, entrySet or values as they are views of + // existing map entries - you can't do a map-style get on them. +} diff --git a/src/test/org/apache/commons/collections/map/TestAll.java b/src/test/org/apache/commons/collections/map/TestAll.java index 91237d26e..9745d2a3a 100644 --- a/src/test/org/apache/commons/collections/map/TestAll.java +++ b/src/test/org/apache/commons/collections/map/TestAll.java @@ -1,5 +1,5 @@ /* - * Copyright 2003-2004 The Apache Software Foundation + * Copyright 2003-2005 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. @@ -43,6 +43,7 @@ public class TestAll extends TestCase { suite.addTest(TestCaseInsensitiveMap.suite()); suite.addTest(TestCompositeMap.suite()); + suite.addTest(TestDefaultedMap.suite()); suite.addTest(TestFlat3Map.suite()); suite.addTest(TestHashedMap.suite()); suite.addTest(TestIdentityMap.suite()); diff --git a/src/test/org/apache/commons/collections/map/TestDefaultedMap.java b/src/test/org/apache/commons/collections/map/TestDefaultedMap.java new file mode 100644 index 000000000..40e885fa8 --- /dev/null +++ b/src/test/org/apache/commons/collections/map/TestDefaultedMap.java @@ -0,0 +1,154 @@ +/* + * Copyright 2005 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.framework.TestSuite; + +import org.apache.commons.collections.Factory; +import org.apache.commons.collections.FactoryUtils; +import org.apache.commons.collections.Transformer; +import org.apache.commons.collections.functors.ConstantFactory; + +/** + * Extension of {@link TestMap} for exercising the + * {@link DefaultedMap} implementation. + * + * @since Commons Collections 3.2 + * @version $Revision: 155406 $ $Date$ + * + * @author Stephen Colebourne + */ +public class TestDefaultedMap extends AbstractTestMap { + + protected static final Factory nullFactory = FactoryUtils.nullFactory(); + + public TestDefaultedMap(String testName) { + super(testName); + } + + public static Test suite() { + return new TestSuite(TestDefaultedMap.class); + } + + public static void main(String args[]) { + String[] testCaseName = { TestDefaultedMap.class.getName()}; + junit.textui.TestRunner.main(testCaseName); + } + + //----------------------------------------------------------------------- + public Map makeEmptyMap() { + return DefaultedMap.decorate(new HashMap(), nullFactory); + } + + //----------------------------------------------------------------------- + public void testMapGet() { + Map map = new DefaultedMap("NULL"); + + assertEquals(0, map.size()); + assertEquals(false, map.containsKey("NotInMap")); + assertEquals("NULL", map.get("NotInMap")); + + map.put("Key", "Value"); + assertEquals(1, map.size()); + assertEquals(true, map.containsKey("Key")); + assertEquals("Value", map.get("Key")); + assertEquals(false, map.containsKey("NotInMap")); + assertEquals("NULL", map.get("NotInMap")); + } + + public void testMapGet2() { + HashMap base = new HashMap(); + Map map = DefaultedMap.decorate(base, "NULL"); + + assertEquals(0, map.size()); + assertEquals(0, base.size()); + assertEquals(false, map.containsKey("NotInMap")); + assertEquals("NULL", map.get("NotInMap")); + + map.put("Key", "Value"); + assertEquals(1, map.size()); + assertEquals(1, base.size()); + assertEquals(true, map.containsKey("Key")); + assertEquals("Value", map.get("Key")); + assertEquals(false, map.containsKey("NotInMap")); + assertEquals("NULL", map.get("NotInMap")); + } + + public void testMapGet3() { + HashMap base = new HashMap(); + Map map = DefaultedMap.decorate(base, ConstantFactory.getInstance("NULL")); + + assertEquals(0, map.size()); + assertEquals(0, base.size()); + assertEquals(false, map.containsKey("NotInMap")); + assertEquals("NULL", map.get("NotInMap")); + + map.put("Key", "Value"); + assertEquals(1, map.size()); + assertEquals(1, base.size()); + assertEquals(true, map.containsKey("Key")); + assertEquals("Value", map.get("Key")); + assertEquals(false, map.containsKey("NotInMap")); + assertEquals("NULL", map.get("NotInMap")); + } + + public void testMapGet4() { + HashMap base = new HashMap(); + Map map = DefaultedMap.decorate(base, new Transformer() { + public Object transform(Object input) { + if (input instanceof String) { + return "NULL"; + } + return "NULL_OBJECT"; + } + }); + + assertEquals(0, map.size()); + assertEquals(0, base.size()); + assertEquals(false, map.containsKey("NotInMap")); + assertEquals("NULL", map.get("NotInMap")); + assertEquals("NULL_OBJECT", map.get(new Integer(0))); + + map.put("Key", "Value"); + assertEquals(1, map.size()); + assertEquals(1, base.size()); + assertEquals(true, map.containsKey("Key")); + assertEquals("Value", map.get("Key")); + assertEquals(false, map.containsKey("NotInMap")); + assertEquals("NULL", map.get("NotInMap")); + assertEquals("NULL_OBJECT", map.get(new Integer(0))); + } + + public String getCompatibilityVersion() { + return "3.2"; + } + +// public void testCreate() throws Exception { +// resetEmpty(); +// writeExternalFormToDisk( +// (java.io.Serializable) map, +// "c:/commons/collections/data/test/DefaultedMap.emptyCollection.version3.2.obj"); +// resetFull(); +// writeExternalFormToDisk( +// (java.io.Serializable) map, +// "c:/commons/collections/data/test/DefaultedMap.fullCollection.version3.2.obj"); +// } + +}