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