001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017 package org.apache.commons.collections.map;
018
019 import java.io.IOException;
020 import java.io.ObjectInputStream;
021 import java.io.ObjectOutputStream;
022 import java.io.Serializable;
023 import java.util.Map;
024
025 import org.apache.commons.collections.Factory;
026 import org.apache.commons.collections.Transformer;
027 import org.apache.commons.collections.functors.FactoryTransformer;
028
029 /**
030 * Decorates another <code>Map</code> to create objects in the map on demand.
031 * <p>
032 * When the {@link #get(Object)} method is called with a key that does not
033 * exist in the map, the factory is used to create the object. The created
034 * object will be added to the map using the requested key.
035 * <p>
036 * For instance:
037 * <pre>
038 * Factory factory = new Factory() {
039 * public Object create() {
040 * return new Date();
041 * }
042 * }
043 * Map lazy = Lazy.map(new HashMap(), factory);
044 * Object obj = lazy.get("NOW");
045 * </pre>
046 *
047 * After the above code is executed, <code>obj</code> will contain
048 * a new <code>Date</code> instance. Furthermore, that <code>Date</code>
049 * instance is mapped to the "NOW" key in the map.
050 * <p>
051 * <strong>Note that LazyMap is not synchronized and is not thread-safe.</strong>
052 * If you wish to use this map from multiple threads concurrently, you must use
053 * appropriate synchronization. The simplest approach is to wrap this map
054 * using {@link java.util.Collections#synchronizedMap(Map)}. This class may throw
055 * exceptions when accessed by concurrent threads without synchronization.
056 * <p>
057 * This class is Serializable from Commons Collections 3.1.
058 *
059 * @since Commons Collections 3.0
060 * @version $Revision: 646777 $ $Date: 2008-04-10 13:33:15 +0100 (Thu, 10 Apr 2008) $
061 *
062 * @author Stephen Colebourne
063 * @author Paul Jack
064 */
065 public class LazyMap
066 extends AbstractMapDecorator
067 implements Map, Serializable {
068
069 /** Serialization version */
070 private static final long serialVersionUID = 7990956402564206740L;
071
072 /** The factory to use to construct elements */
073 protected final Transformer factory;
074
075 /**
076 * Factory method to create a lazily instantiated map.
077 *
078 * @param map the map to decorate, must not be null
079 * @param factory the factory to use, must not be null
080 * @throws IllegalArgumentException if map or factory is null
081 */
082 public static Map decorate(Map map, Factory factory) {
083 return new LazyMap(map, factory);
084 }
085
086 /**
087 * Factory method to create a lazily instantiated map.
088 *
089 * @param map the map to decorate, must not be null
090 * @param factory the factory to use, must not be null
091 * @throws IllegalArgumentException if map or factory is null
092 */
093 public static Map decorate(Map map, Transformer factory) {
094 return new LazyMap(map, factory);
095 }
096
097 //-----------------------------------------------------------------------
098 /**
099 * Constructor that wraps (not copies).
100 *
101 * @param map the map to decorate, must not be null
102 * @param factory the factory to use, must not be null
103 * @throws IllegalArgumentException if map or factory is null
104 */
105 protected LazyMap(Map map, Factory factory) {
106 super(map);
107 if (factory == null) {
108 throw new IllegalArgumentException("Factory must not be null");
109 }
110 this.factory = FactoryTransformer.getInstance(factory);
111 }
112
113 /**
114 * Constructor that wraps (not copies).
115 *
116 * @param map the map to decorate, must not be null
117 * @param factory the factory to use, must not be null
118 * @throws IllegalArgumentException if map or factory is null
119 */
120 protected LazyMap(Map map, Transformer factory) {
121 super(map);
122 if (factory == null) {
123 throw new IllegalArgumentException("Factory must not be null");
124 }
125 this.factory = factory;
126 }
127
128 //-----------------------------------------------------------------------
129 /**
130 * Write the map out using a custom routine.
131 *
132 * @param out the output stream
133 * @throws IOException
134 * @since Commons Collections 3.1
135 */
136 private void writeObject(ObjectOutputStream out) throws IOException {
137 out.defaultWriteObject();
138 out.writeObject(map);
139 }
140
141 /**
142 * Read the map in using a custom routine.
143 *
144 * @param in the input stream
145 * @throws IOException
146 * @throws ClassNotFoundException
147 * @since Commons Collections 3.1
148 */
149 private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
150 in.defaultReadObject();
151 map = (Map) in.readObject();
152 }
153
154 //-----------------------------------------------------------------------
155 public Object get(Object key) {
156 // create value for key if key is not currently in the map
157 if (map.containsKey(key) == false) {
158 Object value = factory.transform(key);
159 map.put(key, value);
160 return value;
161 }
162 return map.get(key);
163 }
164
165 // no need to wrap keySet, entrySet or values as they are views of
166 // existing map entries - you can't do a map-style get on them.
167 }