View Javadoc
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 }