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