001: /*
002: * Copyright 2005 The Apache Software Foundation
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: package org.apache.commons.collections.map;
017:
018: import java.io.IOException;
019: import java.io.ObjectInputStream;
020: import java.io.ObjectOutputStream;
021: import java.io.Serializable;
022: import java.util.HashMap;
023: import java.util.Map;
024:
025: import org.apache.commons.collections.Factory;
026: import org.apache.commons.collections.Transformer;
027: import org.apache.commons.collections.functors.ConstantTransformer;
028: import org.apache.commons.collections.functors.FactoryTransformer;
029:
030: /**
031: * Decorates another <code>Map</code> returning a default value if the map
032: * does not contain the requested key.
033: * <p>
034: * When the {@link #get(Object)} method is called with a key that does not
035: * exist in the map, this map will return the default value specified in
036: * the constructor/factory. Only the get method is altered, so the
037: * {@link Map#containsKey(Object)} can be used to determine if a key really
038: * is in the map or not.
039: * <p>
040: * The defaulted value is not added to the map.
041: * Compare this behaviour with {@link LazyMap}, which does add the value
042: * to the map (via a Transformer).
043: * <p>
044: * For instance:
045: * <pre>
046: * Map map = new DefaultedMap("NULL");
047: * Object obj = map.get("Surname");
048: * // obj == "NULL"
049: * </pre>
050: * After the above code is executed the map is still empty.
051: * <p>
052: * <strong>Note that DefaultedMap is not synchronized and is not thread-safe.</strong>
053: * If you wish to use this map from multiple threads concurrently, you must use
054: * appropriate synchronization. The simplest approach is to wrap this map
055: * using {@link java.util.Collections#synchronizedMap(Map)}. This class may throw
056: * exceptions when accessed by concurrent threads without synchronization.
057: *
058: * @since Commons Collections 3.2
059: * @version $Revision: 1.7 $ $Date: 2005-11-21 22:52:57 +0000 (Mon, 21 Nov 2005) $
060: *
061: * @author Stephen Colebourne
062: * @author Rafael U.C. Afonso
063: * @see LazyMap
064: */
065: public class DefaultedMap extends AbstractMapDecorator implements Map,
066: Serializable {
067:
068: /** Serialization version */
069: private static final long serialVersionUID = 19698628745827L;
070:
071: /** The transformer to use if the map does not contain a key */
072: protected final Object value;
073:
074: //-----------------------------------------------------------------------
075: /**
076: * Factory method to create a defaulting map.
077: * <p>
078: * The value specified is returned when a missing key is found.
079: *
080: * @param map the map to decorate, must not be null
081: * @param defaultValue the default value to return when the key is not found
082: * @throws IllegalArgumentException if map is null
083: */
084: public static Map decorate(Map map, Object defaultValue) {
085: if (defaultValue instanceof Transformer) {
086: defaultValue = ConstantTransformer
087: .getInstance(defaultValue);
088: }
089: return new DefaultedMap(map, defaultValue);
090: }
091:
092: /**
093: * Factory method to create a defaulting map.
094: * <p>
095: * The factory specified is called when a missing key is found.
096: * The result will be returned as the result of the map get(key) method.
097: *
098: * @param map the map to decorate, must not be null
099: * @param factory the factory to use, must not be null
100: * @throws IllegalArgumentException if map or factory is null
101: */
102: public static Map decorate(Map map, Factory factory) {
103: if (factory == null) {
104: throw new IllegalArgumentException(
105: "Factory must not be null");
106: }
107: return new DefaultedMap(map, FactoryTransformer
108: .getInstance(factory));
109: }
110:
111: /**
112: * Factory method to create a defaulting map.
113: * <p>
114: * The transformer specified is called when a missing key is found.
115: * The key is passed to the transformer as the input, and the result
116: * will be returned as the result of the map get(key) method.
117: *
118: * @param map the map to decorate, must not be null
119: * @param factory the factory to use, must not be null
120: * @throws IllegalArgumentException if map or factory is null
121: */
122: public static Map decorate(Map map, Transformer factory) {
123: if (factory == null) {
124: throw new IllegalArgumentException(
125: "Transformer must not be null");
126: }
127: return new DefaultedMap(map, factory);
128: }
129:
130: //-----------------------------------------------------------------------
131: /**
132: * Constructs a new empty <code>DefaultedMap</code> that decorates
133: * a <code>HashMap</code>.
134: * <p>
135: * The object passed in will be returned by the map whenever an
136: * unknown key is requested.
137: *
138: * @param defaultValue the default value to return when the key is not found
139: */
140: public DefaultedMap(Object defaultValue) {
141: super (new HashMap());
142: if (defaultValue instanceof Transformer) {
143: defaultValue = ConstantTransformer
144: .getInstance(defaultValue);
145: }
146: this .value = defaultValue;
147: }
148:
149: /**
150: * Constructor that wraps (not copies).
151: *
152: * @param map the map to decorate, must not be null
153: * @param value the value to use
154: * @throws IllegalArgumentException if map or transformer is null
155: */
156: protected DefaultedMap(Map map, Object value) {
157: super (map);
158: this .value = value;
159: }
160:
161: //-----------------------------------------------------------------------
162: /**
163: * Write the map out using a custom routine.
164: *
165: * @param out the output stream
166: * @throws IOException
167: */
168: private void writeObject(ObjectOutputStream out) throws IOException {
169: out.defaultWriteObject();
170: out.writeObject(map);
171: }
172:
173: /**
174: * Read the map in using a custom routine.
175: *
176: * @param in the input stream
177: * @throws IOException
178: * @throws ClassNotFoundException
179: */
180: private void readObject(ObjectInputStream in) throws IOException,
181: ClassNotFoundException {
182: in.defaultReadObject();
183: map = (Map) in.readObject();
184: }
185:
186: //-----------------------------------------------------------------------
187: public Object get(Object key) {
188: // create value for key if key is not currently in the map
189: if (map.containsKey(key) == false) {
190: if (value instanceof Transformer) {
191: return ((Transformer) value).transform(key);
192: }
193: return value;
194: }
195: return map.get(key);
196: }
197:
198: // no need to wrap keySet, entrySet or values as they are views of
199: // existing map entries - you can't do a map-style get on them.
200: }
|