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.collections4.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 import java.util.Objects;
26
27 import org.apache.commons.collections4.Factory;
28 import org.apache.commons.collections4.Transformer;
29 import org.apache.commons.collections4.functors.ConstantTransformer;
30 import org.apache.commons.collections4.functors.FactoryTransformer;
31
32 /**
33 * Decorates another {@code Map} returning a default value if the map
34 * does not contain the requested key.
35 * <p>
36 * When the {@link #get(Object)} method is called with a key that does not
37 * exist in the map, this map will return the default value specified in
38 * the constructor/factory. Only the get method is altered, so the
39 * {@link Map#containsKey(Object)} can be used to determine if a key really
40 * is in the map or not.
41 * </p>
42 * <p>
43 * The defaulted value is not added to the map.
44 * Compare this behavior with {@link LazyMap}, which does add the value
45 * to the map (via a Transformer).
46 * </p>
47 * <p>
48 * For instance:
49 * </p>
50 * <pre>
51 * Map map = new DefaultedMap("NULL");
52 * Object obj = map.get("Surname");
53 * // obj == "NULL"
54 * </pre>
55 * <p>
56 * After the above code is executed the map is still empty.
57 * </p>
58 * <p>
59 * <strong>Note that DefaultedMap is not synchronized and is not thread-safe.</strong>
60 * If you wish to use this map from multiple threads concurrently, you must use
61 * appropriate synchronization. The simplest approach is to wrap this map
62 * using {@link java.util.Collections#synchronizedMap(Map)}. This class may throw
63 * exceptions when accessed by concurrent threads without synchronization.
64 * </p>
65 *
66 * @param <K> the type of the keys in this map
67 * @param <V> the type of the values in this map
68 * @since 3.2
69 * @see LazyMap
70 */
71 public class DefaultedMap<K, V> extends AbstractMapDecorator<K, V> implements Serializable {
72
73 /** Serialization version */
74 private static final long serialVersionUID = 19698628745827L;
75
76 /**
77 * Factory method to create a defaulting map.
78 * <p>
79 * The factory specified is called when a missing key is found.
80 * The result will be returned as the result of the map get(key) method.
81 *
82 * @param <K> the key type
83 * @param <V> the value type
84 * @param map the map to decorate, must not be null
85 * @param factory the factory to use to create entries, must not be null
86 * @return a new defaulting map
87 * @throws NullPointerException if map or factory is null
88 * @since 4.0
89 */
90 public static <K, V> DefaultedMap<K, V> defaultedMap(final Map<K, V> map, final Factory<? extends V> factory) {
91 return new DefaultedMap<>(map, FactoryTransformer.factoryTransformer(
92 Objects.requireNonNull(factory, "Factory must not be null")));
93 }
94
95 /**
96 * Factory method to create a defaulting map.
97 * <p>
98 * The transformer specified is called when a missing key is found.
99 * The key is passed to the transformer as the input, and the result
100 * will be returned as the result of the map get(key) method.
101 * </p>
102 *
103 * @param <K> the key type
104 * @param <V> the value type
105 * @param map the map to decorate, must not be null
106 * @param transformer the transformer to use as a factory to create entries, must not be null
107 * @return a new defaulting map
108 * @throws NullPointerException if map or transformer is null
109 * @since 4.0
110 */
111 public static <K, V> Map<K, V> defaultedMap(final Map<K, V> map,
112 final Transformer<? super K, ? extends V> transformer) {
113 return new DefaultedMap<>(map, Objects.requireNonNull(transformer, "Transformer must not be null"));
114 }
115
116 /**
117 * Factory method to create a defaulting map.
118 * <p>
119 * The value specified is returned when a missing key is found.
120 * </p>
121 *
122 * @param <K> the key type
123 * @param <V> the value type
124 * @param map the map to decorate, must not be null
125 * @param defaultValue the default value to return when the key is not found
126 * @return a new defaulting map
127 * @throws NullPointerException if map is null
128 * @since 4.0
129 */
130 public static <K, V> DefaultedMap<K, V> defaultedMap(final Map<K, V> map, final V defaultValue) {
131 return new DefaultedMap<>(map, ConstantTransformer.constantTransformer(defaultValue));
132 }
133
134 /** The transformer to use if the map does not contain a key */
135 private final Transformer<? super K, ? extends V> value;
136
137 /**
138 * Constructor that wraps (not copies).
139 *
140 * @param map the map to decorate, must not be null
141 * @param defaultValueTransformer the value transformer to use
142 * @throws NullPointerException if map or transformer is null
143 */
144 protected DefaultedMap(final Map<K, V> map, final Transformer<? super K, ? extends V> defaultValueTransformer) {
145 super(map);
146 this.value = Objects.requireNonNull(defaultValueTransformer, "defaultValueTransformer");
147 }
148
149 /**
150 * Constructs a new empty {@code DefaultedMap} that decorates a {@code HashMap}.
151 *
152 * @param defaultValueTransformer transformer to use to generate missing values.
153 */
154 public DefaultedMap(final Transformer<? super K, ? extends V> defaultValueTransformer) {
155 this(new HashMap<>(), defaultValueTransformer);
156 }
157
158 /**
159 * Constructs a new empty {@code DefaultedMap} that decorates
160 * a {@code HashMap}.
161 * <p>
162 * The object passed in will be returned by the map whenever an
163 * unknown key is requested.
164 * </p>
165 *
166 * @param defaultValue the default value to return when the key is not found
167 */
168 public DefaultedMap(final V defaultValue) {
169 this(ConstantTransformer.constantTransformer(defaultValue));
170 }
171
172 @Override
173 @SuppressWarnings("unchecked")
174 public V get(final Object key) {
175 final V v;
176 return (v = map.get(key)) != null || map.containsKey(key)
177 ? v
178 : value.apply((K) key);
179 }
180
181 /**
182 * Deserializes the map in using a custom routine.
183 *
184 * @param in the input stream
185 * @throws IOException if an error occurs while reading from the stream
186 * @throws ClassNotFoundException if an object read from the stream cannot be loaded
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 * Serializes this object to an ObjectOutputStream.
196 *
197 * @param out the target ObjectOutputStream.
198 * @throws IOException thrown when an I/O errors occur writing to the target stream.
199 */
200 private void writeObject(final ObjectOutputStream out) throws IOException {
201 out.defaultWriteObject();
202 out.writeObject(map);
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 }