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   *
69   * @since 3.2
70   * @see LazyMap
71   */
72  public class DefaultedMap<K, V> extends AbstractMapDecorator<K, V> implements Serializable {
73  
74      /** Serialization version */
75      private static final long serialVersionUID = 19698628745827L;
76  
77      /**
78       * Factory method to create a defaulting map.
79       * <p>
80       * The factory specified is called when a missing key is found.
81       * The result will be returned as the result of the map get(key) method.
82       *
83       * @param <K>  the key type
84       * @param <V>  the value type
85       * @param map  the map to decorate, must not be null
86       * @param factory  the factory to use to create entries, must not be null
87       * @return a new defaulting map
88       * @throws NullPointerException if map or factory is null
89       * @since 4.0
90       */
91      public static <K, V> DefaultedMap<K, V> defaultedMap(final Map<K, V> map, final Factory<? extends V> factory) {
92          return new DefaultedMap<>(map, FactoryTransformer.factoryTransformer(
93                  Objects.requireNonNull(factory, "Factory must not be null")));
94      }
95  
96      /**
97       * Factory method to create a defaulting map.
98       * <p>
99       * The transformer specified is called when a missing key is found.
100      * The key is passed to the transformer as the input, and the result
101      * will be returned as the result of the map get(key) method.
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      *
121      * @param <K>  the key type
122      * @param <V>  the value type
123      * @param map  the map to decorate, must not be null
124      * @param defaultValue  the default value to return when the key is not found
125      * @return a new defaulting map
126      * @throws NullPointerException if map is null
127      * @since 4.0
128      */
129     public static <K, V> DefaultedMap<K, V> defaultedMap(final Map<K, V> map, final V defaultValue) {
130         return new DefaultedMap<>(map, ConstantTransformer.constantTransformer(defaultValue));
131     }
132 
133     /** The transformer to use if the map does not contain a key */
134     private final Transformer<? super K, ? extends V> value;
135 
136     /**
137      * Constructor that wraps (not copies).
138      *
139      * @param map  the map to decorate, must not be null
140      * @param defaultValueTransformer  the value transformer to use
141      * @throws NullPointerException if map or transformer is null
142      */
143     protected DefaultedMap(final Map<K, V> map, final Transformer<? super K, ? extends V> defaultValueTransformer) {
144         super(map);
145         this.value = Objects.requireNonNull(defaultValueTransformer, "defaultValueTransformer");
146     }
147 
148     /**
149      * Constructs a new empty {@code DefaultedMap} that decorates a {@code HashMap}.
150      *
151      * @param defaultValueTransformer transformer to use to generate missing values.
152      */
153     public DefaultedMap(final Transformer<? super K, ? extends V> defaultValueTransformer) {
154         this(new HashMap<>(), defaultValueTransformer);
155     }
156 
157     /**
158      * Constructs a new empty {@code DefaultedMap} that decorates
159      * a {@code HashMap}.
160      * <p>
161      * The object passed in will be returned by the map whenever an
162      * unknown key is requested.
163      *
164      * @param defaultValue  the default value to return when the key is not found
165      */
166     public DefaultedMap(final V defaultValue) {
167         this(ConstantTransformer.constantTransformer(defaultValue));
168     }
169 
170     @Override
171     @SuppressWarnings("unchecked")
172     public V get(final Object key) {
173         final V v;
174         return (v = map.get(key)) != null || map.containsKey(key)
175             ? v
176             : value.transform((K) key);
177     }
178 
179     /**
180      * Read the map in using a custom routine.
181      *
182      * @param in  the input stream
183      * @throws IOException if an error occurs while reading from the stream
184      * @throws ClassNotFoundException if an object read from the stream can not be loaded
185      */
186     @SuppressWarnings("unchecked")
187     private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
188         in.defaultReadObject();
189         map = (Map<K, V>) in.readObject();
190     }
191 
192     /**
193      * Write the map out using a custom routine.
194      *
195      * @param out  the output stream
196      * @throws IOException if an error occurs while writing to the stream
197      */
198     private void writeObject(final ObjectOutputStream out) throws IOException {
199         out.defaultWriteObject();
200         out.writeObject(map);
201     }
202 
203     // no need to wrap keySet, entrySet or values as they are views of
204     // existing map entries - you can't do a map-style get on them.
205 }