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    }