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 * For instance: 037 * <pre> 038 * Factory<Date> factory = new Factory<Date>() { 039 * public Date create() { 040 * return new Date(); 041 * } 042 * } 043 * Map<String, Date> lazy = LazyMap.lazyMap(new HashMap<String, Date>(), factory); 044 * Date date = lazy.get("NOW"); 045 * </pre> 046 * 047 * After the above code is executed, <code>date</code> will refer to 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 * @param <K> the type of the keys in this map 060 * @param <V> the type of the values in this map 061 * @since 3.0 062 */ 063public class LazyMap<K, V> extends AbstractMapDecorator<K, V> implements Serializable { 064 065 /** Serialization version */ 066 private static final long serialVersionUID = 7990956402564206740L; 067 068 /** The factory to use to construct elements */ 069 protected final Transformer<? super K, ? extends V> factory; 070 071 /** 072 * Factory method to create a lazily instantiated map. 073 * 074 * @param <K> the key type 075 * @param <V> the value type 076 * @param map the map to decorate, must not be null 077 * @param factory the factory to use, must not be null 078 * @return a new lazy map 079 * @throws NullPointerException if map or factory is null 080 * @since 4.0 081 */ 082 public static <K, V> LazyMap<K, V> lazyMap(final Map<K, V> map, final Factory< ? extends V> factory) { 083 return new LazyMap<>(map, factory); 084 } 085 086 /** 087 * Factory method to create a lazily instantiated map. 088 * 089 * @param <K> the key type 090 * @param <V> the value type 091 * @param map the map to decorate, must not be null 092 * @param factory the factory to use, must not be null 093 * @return a new lazy map 094 * @throws NullPointerException if map or factory is null 095 * @since 4.0 096 */ 097 public static <V, K> LazyMap<K, V> lazyMap(final Map<K, V> map, final Transformer<? super K, ? extends V> factory) { 098 return new LazyMap<>(map, factory); 099 } 100 101 //----------------------------------------------------------------------- 102 /** 103 * Constructor that wraps (not copies). 104 * 105 * @param map the map to decorate, must not be null 106 * @param factory the factory to use, must not be null 107 * @throws NullPointerException if map or factory is null 108 */ 109 protected LazyMap(final Map<K,V> map, final Factory<? extends V> factory) { 110 super(map); 111 if (factory == null) { 112 throw new NullPointerException("Factory must not be null"); 113 } 114 this.factory = FactoryTransformer.factoryTransformer(factory); 115 } 116 117 /** 118 * Constructor that wraps (not copies). 119 * 120 * @param map the map to decorate, must not be null 121 * @param factory the factory to use, must not be null 122 * @throws NullPointerException if map or factory is null 123 */ 124 protected LazyMap(final Map<K,V> map, final Transformer<? super K, ? extends V> factory) { 125 super(map); 126 if (factory == null) { 127 throw new NullPointerException("Factory must not be null"); 128 } 129 this.factory = factory; 130 } 131 132 //----------------------------------------------------------------------- 133 /** 134 * Write the map out using a custom routine. 135 * 136 * @param out the output stream 137 * @throws IOException if an error occurs while writing to the stream 138 * @since 3.1 139 */ 140 private void writeObject(final ObjectOutputStream out) throws IOException { 141 out.defaultWriteObject(); 142 out.writeObject(map); 143 } 144 145 /** 146 * Read 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 can not 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 @Override 161 public V get(final Object key) { 162 // create value for key if key is not currently in the map 163 if (map.containsKey(key) == false) { 164 @SuppressWarnings("unchecked") 165 final K castKey = (K) key; 166 final V value = factory.transform(castKey); 167 map.put(castKey, value); 168 return value; 169 } 170 return map.get(key); 171 } 172 173 // no need to wrap keySet, entrySet or values as they are views of 174 // existing map entries - you can't do a map-style get on them. 175}