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.Map;
24  import java.util.Objects;
25  
26  import org.apache.commons.collections4.Factory;
27  import org.apache.commons.collections4.Transformer;
28  import org.apache.commons.collections4.functors.FactoryTransformer;
29  
30  /**
31   * Decorates another {@code Map} to create objects in the map on demand.
32   * <p>
33   * When the {@link #get(Object)} method is called with a key that does not
34   * exist in the map, the factory is used to create the object. The created
35   * object will be added to the map using the requested key.
36   * </p>
37   * <p>
38   * For instance:
39   * </p>
40   * <pre>
41   * Factory&lt;Date&gt; factory = new Factory&lt;Date&gt;() {
42   *     public Date create() {
43   *         return new Date();
44   *     }
45   * }
46   * Map&lt;String, Date&gt; lazy = LazyMap.lazyMap(new HashMap&lt;String, Date&gt;(), factory);
47   * Date date = lazy.get("NOW");
48   * </pre>
49   *
50   * <p>
51   * After the above code is executed, {@code date} will refer to
52   * a new {@code Date} instance. Furthermore, that {@code Date}
53   * instance is mapped to the "NOW" key in the map.
54   * </p>
55   * <p>
56   * <strong>Note that LazyMap is not synchronized and is not thread-safe.</strong>
57   * If you wish to use this map from multiple threads concurrently, you must use
58   * appropriate synchronization. The simplest approach is to wrap this map
59   * using {@link java.util.Collections#synchronizedMap(Map)}. This class may throw
60   * exceptions when accessed by concurrent threads without synchronization.
61   * </p>
62   * <p>
63   * This class is Serializable from Commons Collections 3.1.
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.0
69   */
70  public class LazyMap<K, V> extends AbstractMapDecorator<K, V> implements Serializable {
71  
72      /** Serialization version */
73      private static final long serialVersionUID = 7990956402564206740L;
74  
75      /**
76       * Factory method to create a lazily instantiated map.
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 factory  the factory to use, must not be null
82       * @return a new lazy map
83       * @throws NullPointerException if map or factory is null
84       * @since 4.0
85       */
86      public static <K, V> LazyMap<K, V> lazyMap(final Map<K, V> map, final Factory<? extends V> factory) {
87          return new LazyMap<>(map, factory);
88      }
89  
90      /**
91       * Factory method to create a lazily instantiated map.
92       *
93       * @param <K>  the key type
94       * @param <V>  the value type
95       * @param map  the map to decorate, must not be null
96       * @param factory  the factory to use, must not be null
97       * @return a new lazy map
98       * @throws NullPointerException if map or factory is null
99       * @since 4.0
100      */
101     public static <V, K> LazyMap<K, V> lazyMap(final Map<K, V> map, final Transformer<? super K, ? extends V> factory) {
102         return new LazyMap<>(map, factory);
103     }
104 
105     /** The factory to use to construct elements */
106     protected final Transformer<? super K, ? extends V> factory;
107 
108     /**
109      * Constructor that wraps (not copies).
110      *
111      * @param map  the map to decorate, must not be null
112      * @param factory  the factory to use, must not be null
113      * @throws NullPointerException if map or factory is null
114      */
115     protected LazyMap(final Map<K, V> map, final Factory<? extends V> factory) {
116         super(map);
117         this.factory = FactoryTransformer.factoryTransformer(Objects.requireNonNull(factory, "factory"));
118     }
119 
120     /**
121      * Constructor that wraps (not copies).
122      *
123      * @param map  the map to decorate, must not be null
124      * @param factory  the factory to use, must not be null
125      * @throws NullPointerException if map or factory is null
126      */
127     protected LazyMap(final Map<K, V> map, final Transformer<? super K, ? extends V> factory) {
128         super(map);
129         this.factory = Objects.requireNonNull(factory, "factory");
130     }
131 
132     @Override
133     public V get(final Object key) {
134         // create value for key if key is not currently in the map
135         if (!map.containsKey(key)) {
136             @SuppressWarnings("unchecked")
137             final K castKey = (K) key;
138             final V value = factory.transform(castKey);
139             map.put(castKey, value);
140             return value;
141         }
142         return map.get(key);
143     }
144 
145     /**
146      * Read the map in using a custom routine.
147      *
148      * @param in  the input stream
149      * @throws IOException if an error occurs while reading from the stream
150      * @throws ClassNotFoundException if an object read from the stream can not be loaded
151      * @since 3.1
152      */
153     @SuppressWarnings("unchecked")
154     private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
155         in.defaultReadObject();
156         map = (Map<K, V>) in.readObject();
157     }
158 
159     /**
160      * Write the map out using a custom routine.
161      *
162      * @param out  the output stream
163      * @throws IOException if an error occurs while writing to the stream
164      * @since 3.1
165      */
166     private void writeObject(final ObjectOutputStream out) throws IOException {
167         out.defaultWriteObject();
168         out.writeObject(map);
169     }
170 
171     // no need to wrap keySet, entrySet or values as they are views of
172     // existing map entries - you can't do a map-style get on them.
173 }