1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.commons.collections.map;
18
19 import java.io.IOException;
20 import java.io.ObjectInputStream;
21 import java.io.ObjectOutputStream;
22 import java.io.Serializable;
23 import java.util.HashMap;
24 import java.util.Map;
25
26 import org.apache.commons.collections.Factory;
27 import org.apache.commons.collections.Transformer;
28 import org.apache.commons.collections.functors.ConstantTransformer;
29 import org.apache.commons.collections.functors.FactoryTransformer;
30
31 /**
32 * Decorates another <code>Map</code> returning a default value if the map
33 * does not contain the requested key.
34 * <p>
35 * When the {@link #get(Object)} method is called with a key that does not
36 * exist in the map, this map will return the default value specified in
37 * the constructor/factory. Only the get method is altered, so the
38 * {@link Map#containsKey(Object)} can be used to determine if a key really
39 * is in the map or not.
40 * <p>
41 * The defaulted value is not added to the map.
42 * Compare this behaviour with {@link LazyMap}, which does add the value
43 * to the map (via a Transformer).
44 * <p>
45 * For instance:
46 * <pre>
47 * Map map = new DefaultedMap("NULL");
48 * Object obj = map.get("Surname");
49 * // obj == "NULL"
50 * </pre>
51 * After the above code is executed the map is still empty.
52 * <p>
53 * <strong>Note that DefaultedMap is not synchronized and is not thread-safe.</strong>
54 * If you wish to use this map from multiple threads concurrently, you must use
55 * appropriate synchronization. The simplest approach is to wrap this map
56 * using {@link java.util.Collections#synchronizedMap(Map)}. This class may throw
57 * exceptions when accessed by concurrent threads without synchronization.
58 *
59 * @since 3.2
60 * @version $Id: DefaultedMap.java 1435824 2013-01-20 11:41:17Z tn $
61 *
62 * @see LazyMap
63 */
64 public class DefaultedMap<K, V> extends AbstractMapDecorator<K, V> implements Serializable {
65
66 /** Serialization version */
67 private static final long serialVersionUID = 19698628745827L;
68
69 /** The transformer to use if the map does not contain a key */
70 private final Transformer<? super K, ? extends V> value;
71
72 //-----------------------------------------------------------------------
73 /**
74 * Factory method to create a defaulting map.
75 * <p>
76 * The value specified is returned when a missing key is found.
77 *
78 * @param <K> the key type
79 * @param <V> the value type
80 * @param map the map to decorate, must not be null
81 * @param defaultValue the default value to return when the key is not found
82 * @return a new defaulting map
83 * @throws IllegalArgumentException if map is null
84 */
85 public static <K, V> DefaultedMap<K, V> defaultedMap(final Map<K, V> map, final V defaultValue) {
86 return new DefaultedMap<K, V>(map, ConstantTransformer.constantTransformer(defaultValue));
87 }
88
89 /**
90 * Factory method to create a defaulting map.
91 * <p>
92 * The factory specified is called when a missing key is found.
93 * The result will be returned as the result of the map get(key) method.
94 *
95 * @param <K> the key type
96 * @param <V> the value type
97 * @param map the map to decorate, must not be null
98 * @param factory the factory to use to create entries, must not be null
99 * @return a new defaulting map
100 * @throws IllegalArgumentException if map or factory is null
101 */
102 public static <K, V> DefaultedMap<K, V> defaultedMap(final Map<K, V> map, final Factory<? extends V> factory) {
103 if (factory == null) {
104 throw new IllegalArgumentException("Factory must not be null");
105 }
106 return new DefaultedMap<K, V>(map, FactoryTransformer.factoryTransformer(factory));
107 }
108
109 /**
110 * Factory method to create a defaulting map.
111 * <p>
112 * The transformer specified is called when a missing key is found.
113 * The key is passed to the transformer as the input, and the result
114 * will be returned as the result of the map get(key) method.
115 *
116 * @param <K> the key type
117 * @param <V> the value type
118 * @param map the map to decorate, must not be null
119 * @param transformer the transformer to use as a factory to create entries, must not be null
120 * @return a new defaulting map
121 * @throws IllegalArgumentException if map or factory is null
122 */
123 public static <K, V> Map<K, V> defaultedMap(final Map<K, V> map,
124 final Transformer<? super K, ? extends V> transformer) {
125 if (transformer == null) {
126 throw new IllegalArgumentException("Transformer must not be null");
127 }
128 return new DefaultedMap<K, V>(map, transformer);
129 }
130
131 //-----------------------------------------------------------------------
132 /**
133 * Constructs a new empty <code>DefaultedMap</code> that decorates
134 * a <code>HashMap</code>.
135 * <p>
136 * The object passed in will be returned by the map whenever an
137 * unknown key is requested.
138 *
139 * @param defaultValue the default value to return when the key is not found
140 */
141 public DefaultedMap(final V defaultValue) {
142 this(ConstantTransformer.constantTransformer(defaultValue));
143 }
144
145 /**
146 * Constructs a new empty <code>DefaultedMap</code> that decorates a <code>HashMap</code>.
147 *
148 * @param defaultValueTransformer transformer to use to generate missing values.
149 */
150 public DefaultedMap(final Transformer<? super K, ? extends V> defaultValueTransformer) {
151 this(new HashMap<K, V>(), defaultValueTransformer);
152 }
153
154 /**
155 * Constructor that wraps (not copies).
156 *
157 * @param map the map to decorate, must not be null
158 * @param defaultValueTransformer the value transformer to use
159 * @throws IllegalArgumentException if map or transformer is null
160 */
161 protected DefaultedMap(final Map<K, V> map, final Transformer<? super K, ? extends V> defaultValueTransformer) {
162 super(map);
163 if (defaultValueTransformer == null) {
164 throw new IllegalArgumentException("transformer must not be null");
165 }
166 this.value = defaultValueTransformer;
167 }
168
169 //-----------------------------------------------------------------------
170 /**
171 * Write the map out using a custom routine.
172 *
173 * @param out the output stream
174 * @throws IOException
175 */
176 private void writeObject(final ObjectOutputStream out) throws IOException {
177 out.defaultWriteObject();
178 out.writeObject(map);
179 }
180
181 /**
182 * Read the map in using a custom routine.
183 *
184 * @param in the input stream
185 * @throws IOException
186 * @throws ClassNotFoundException
187 */
188 @SuppressWarnings("unchecked")
189 private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
190 in.defaultReadObject();
191 map = (Map<K, V>) in.readObject();
192 }
193
194 //-----------------------------------------------------------------------
195 @Override
196 @SuppressWarnings("unchecked")
197 public V get(final Object key) {
198 // create value for key if key is not currently in the map
199 if (map.containsKey(key) == false) {
200 return value.transform((K) key);
201 }
202 return map.get(key);
203 }
204
205 // no need to wrap keySet, entrySet or values as they are views of
206 // existing map entries - you can't do a map-style get on them.
207 }