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<Date> factory = new Factory<Date>() {
42 * public Date create() {
43 * return new Date();
44 * }
45 * }
46 * Map<String, Date> lazy = LazyMap.lazyMap(new HashMap<String, Date>(), 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.apply(castKey);
139 map.put(castKey, value);
140 return value;
141 }
142 return map.get(key);
143 }
144
145 /**
146 * Deserializes 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 cannot 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 * Serializes this object to an ObjectOutputStream.
161 *
162 * @param out the target ObjectOutputStream.
163 * @throws IOException thrown when an I/O errors occur writing to the target 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 }