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
This commit is contained in:
Stephen Colebourne 2005-03-22 22:53:50 +00:00
parent d6b6fe90bc
commit 4c282328c8
4 changed files with 346 additions and 1 deletions

View File

@ -38,6 +38,7 @@ There are no new deprecations.
<center><h3>NEW CLASSES</h3></center>
<ul>
<li>DefaultedMap - Returns a default value when the key is not found, without adding the default value to the map itself [30911]</li>
<li>LoopingListIterator - When the end of the list is reached the iteration continues from the start [30166]</li>
</ul>

View File

@ -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 <code>Map</code> returning a default value if the map
* does not contain the requested key.
* <p>
* 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.
* <p>
* 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).
* <p>
* For instance:
* <pre>
* Map map = new DefaultedMap("NULL");
* Object obj = map.get("Surname");
* // obj == "NULL"
* </pre>
* 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.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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 <code>DefaultedMap</code> that decorates
* a <code>HashMap</code>.
* <p>
* 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.
}

View File

@ -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());

View File

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