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.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 }