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 */
017package org.apache.commons.collections4.map;
018
019import java.io.IOException;
020import java.io.ObjectInputStream;
021import java.io.ObjectOutputStream;
022import java.io.Serializable;
023import java.util.Map;
024
025import org.apache.commons.collections4.Factory;
026import org.apache.commons.collections4.Transformer;
027import org.apache.commons.collections4.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 * <p>
037 * For instance:
038 * </p>
039 * <pre>
040 * Factory&lt;Date&gt; factory = new Factory&lt;Date&gt;() {
041 *     public Date create() {
042 *         return new Date();
043 *     }
044 * }
045 * Map&lt;String, Date&gt; lazy = LazyMap.lazyMap(new HashMap&lt;String, Date&gt;(), factory);
046 * Date date = lazy.get("NOW");
047 * </pre>
048 *
049 * <p>
050 * After the above code is executed, <code>date</code> will refer to
051 * a new <code>Date</code> instance. Furthermore, that <code>Date</code>
052 * instance is mapped to the "NOW" key in the map.
053 * </p>
054 * <p>
055 * <strong>Note that LazyMap is not synchronized and is not thread-safe.</strong>
056 * If you wish to use this map from multiple threads concurrently, you must use
057 * appropriate synchronization. The simplest approach is to wrap this map
058 * using {@link java.util.Collections#synchronizedMap(Map)}. This class may throw
059 * exceptions when accessed by concurrent threads without synchronization.
060 * </p>
061 * <p>
062 * This class is Serializable from Commons Collections 3.1.
063 * </p>
064 *
065 * @param <K> the type of the keys in this map
066 * @param <V> the type of the values in this map
067 * @since 3.0
068 */
069public class LazyMap<K, V> extends AbstractMapDecorator<K, V> implements Serializable {
070
071    /** Serialization version */
072    private static final long serialVersionUID = 7990956402564206740L;
073
074    /** The factory to use to construct elements */
075    protected final Transformer<? super K, ? extends V> factory;
076
077    /**
078     * Factory method to create a lazily instantiated map.
079     *
080     * @param <K>  the key type
081     * @param <V>  the value type
082     * @param map  the map to decorate, must not be null
083     * @param factory  the factory to use, must not be null
084     * @return a new lazy map
085     * @throws NullPointerException if map or factory is null
086     * @since 4.0
087     */
088    public static <K, V> LazyMap<K, V> lazyMap(final Map<K, V> map, final Factory< ? extends V> factory) {
089        return new LazyMap<>(map, factory);
090    }
091
092    /**
093     * Factory method to create a lazily instantiated map.
094     *
095     * @param <K>  the key type
096     * @param <V>  the value type
097     * @param map  the map to decorate, must not be null
098     * @param factory  the factory to use, must not be null
099     * @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}