001 package edu.rice.cs.cunit.util; 002 003 import java.util.*; 004 import java.lang.ref.SoftReference; 005 006 /** 007 * A hash map with soft references. 008 * @author Mathias Ricken 009 */ 010 public class SoftHashMap<K, V> implements Map<K,V> { 011 /** 012 * The wrapped map. 013 */ 014 private HashMap<K, SoftReference<V>> _map; 015 016 /** 017 * The default initial capacity - MUST be a power of two. 018 */ 019 static final int DEFAULT_INITIAL_CAPACITY = 16; 020 021 /** 022 * The maximum capacity, used if a higher value is implicitly specified 023 * by either of the constructors with arguments. 024 * MUST be a power of two <= 1<<30. 025 */ 026 static final int MAXIMUM_CAPACITY = 1 << 30; 027 028 /** 029 * The load factor used when none specified in constructor. 030 **/ 031 static final float DEFAULT_LOAD_FACTOR = 0.75f; 032 033 /** 034 * Constructs an empty <tt>SoftHashMap</tt> with the specified initial 035 * capacity and load factor. 036 * 037 * @param initialCapacity The initial capacity. 038 * @param loadFactor The load factor. 039 * @throws IllegalArgumentException if the initial capacity is negative 040 * or the load factor is nonpositive. 041 */ 042 public SoftHashMap(int initialCapacity, float loadFactor) { 043 if (initialCapacity < 0) 044 throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); 045 if (initialCapacity > MAXIMUM_CAPACITY) 046 initialCapacity = MAXIMUM_CAPACITY; 047 if (loadFactor <= 0 || Float.isNaN(loadFactor)) 048 throw new IllegalArgumentException("Illegal load factor: " + loadFactor); 049 050 _map = new HashMap<K, SoftReference<V>>(initialCapacity, loadFactor); 051 } 052 053 /** 054 * Constructs an empty <tt>SoftHashMap</tt> with the specified initial 055 * capacity and the default load factor (0.75). 056 * 057 * @param initialCapacity the initial capacity. 058 * @throws IllegalArgumentException if the initial capacity is negative. 059 */ 060 public SoftHashMap(int initialCapacity) { 061 this(initialCapacity, DEFAULT_LOAD_FACTOR); 062 } 063 064 /** 065 * Constructs an empty <tt>SoftHashMap</tt> with the default initial capacity 066 * (16) and the default load factor (0.75). 067 */ 068 public SoftHashMap() { 069 _map = new HashMap<K, SoftReference<V>>(); 070 } 071 072 /** 073 * Constructs a new <tt>SoftHashMap</tt> with the same mappings as the 074 * specified <tt>Map</tt>. The <tt>SoftHashMap</tt> is created with 075 * default load factor (0.75) and an initial capacity sufficient to 076 * hold the mappings in the specified <tt>Map</tt>. 077 * 078 * @param m the map whose mappings are to be placed in this map. 079 * @throws NullPointerException if the specified map is null. 080 */ 081 public SoftHashMap(Map<? extends K, ? extends V> m) { 082 _map = new HashMap<K, SoftReference<V>>(m.size(), DEFAULT_LOAD_FACTOR); 083 for(K key: m.keySet()) { 084 V value = m.get(key); 085 if (value!=null) { 086 _map.put(key, new SoftReference<V>(value)); 087 } 088 } 089 } 090 091 /** 092 * Returns the value to which this map maps the specified key. Returns <tt>null</tt> if the map contains no mapping 093 * for this key. A return value of <tt>null</tt> does not <i>necessarily</i> indicate that the map contains no mapping 094 * for the key; it's also possible that the map explicitly maps the key to <tt>null</tt>. The containsKey operation 095 * may be used to distinguish these two cases. <p> 096 * 097 * @param key key whose associated value is to be returned. 098 * @return the value to which this map maps the specified key. 099 * 100 * @throws NullPointerException if the key is <tt>null</tt> and this map does not permit <tt>null</tt> keys. 101 * @see #containsKey(Object) 102 */ 103 public V get(Object key) { 104 SoftReference<V> sr = _map.get(key); 105 if (sr==null) { 106 return null; 107 } 108 V value = sr.get(); 109 if (value==null) { 110 _map.remove(key); 111 return null; 112 } 113 return value; 114 } 115 116 /** 117 * Remove keys whose values have been removed. 118 */ 119 public void compact() { 120 for(K key: _map.keySet()) { 121 SoftReference<V> sr = _map.get(key); 122 if (sr!=null) { 123 V value = sr.get(); 124 if (value==null) { 125 _map.remove(key); 126 } 127 } 128 } 129 } 130 131 /** 132 * Returns the number of key-value mappings in this map. If the map contains more than <tt>Integer.MAX_VALUE</tt> 133 * elements, returns <tt>Integer.MAX_VALUE</tt>.<p> 134 * <p/> 135 * This implementation returns <tt>entrySet().size()</tt>. 136 * 137 * @return the number of key-value mappings in this map. 138 */ 139 public int size() { 140 compact(); 141 return _map.size(); 142 } 143 144 145 /** 146 * Returns <tt>true</tt> if this map contains no key-value mappings. 147 * 148 * @return <tt>true</tt> if this map contains no key-value mappings. 149 */ 150 public boolean isEmpty() { 151 compact(); 152 return _map.isEmpty(); 153 } 154 155 156 /** 157 * Returns <tt>true</tt> if this map contains a mapping for the specified key. More formally, returns <tt>true</tt> 158 * if and only if this map contains a mapping for a key <tt>k</tt> such that <tt>(key==null ? k==null : 159 * key.equals(k))</tt>. (There can be at most one such mapping.) 160 * 161 * @param key key whose presence in this map is to be tested. 162 * 163 * @return <tt>true</tt> if this map contains a mapping for the specified key. 164 * 165 * @throws ClassCastException if the key is of an inappropriate type for this map (optional). 166 * @throws NullPointerException if the key is <tt>null</tt> and this map does not permit <tt>null</tt> keys 167 * (optional). 168 */ 169 public boolean containsKey(Object key) { 170 compact(); 171 return _map.containsKey(key); 172 } 173 174 175 /** 176 * Returns <tt>true</tt> if this map maps one or more keys to the specified value. More formally, returns 177 * <tt>true</tt> if and only if this map contains at least one mapping to a value <tt>v</tt> such that 178 * <tt>(value==null ? v==null : value.equals(v))</tt>. This operation will probably require time linear in the map 179 * size for most implementations of the <tt>Map</tt> interface. 180 * 181 * @param value value whose presence in this map is to be tested. 182 * 183 * @return <tt>true</tt> if this map maps one or more keys to the specified value. 184 * 185 * @throws ClassCastException if the value is of an inappropriate type for this map (optional). 186 * @throws NullPointerException if the value is <tt>null</tt> and this map does not permit <tt>null</tt> values 187 * (optional). 188 */ 189 public boolean containsValue(Object value) { 190 for(K key: _map.keySet()) { 191 SoftReference<V> sr = _map.get(key); 192 if (sr!=null) { 193 V v = sr.get(); 194 if (value.equals(v)) { 195 return true; 196 } 197 else if (v==null) { 198 _map.remove(key); 199 } 200 } 201 } 202 return false; 203 } 204 205 206 /** 207 * Associates the specified value with the specified key in this map (optional operation). If the map previously 208 * contained a mapping for this key, the old value is replaced by the specified value. (A map <tt>m</tt> is said to 209 * contain a mapping for a key <tt>k</tt> if and only if {@link #containsKey(Object) m.containsKey(k)} would return 210 * <tt>true</tt>.)) 211 * 212 * @param k key with which the specified value is to be associated. 213 * @param v value to be associated with the specified key. 214 * 215 * @return previous value associated with specified key, or <tt>null</tt> if there was no mapping for key. A 216 * <tt>null</tt> return can also indicate that the map previously associated <tt>null</tt> with the specified 217 * key, if the implementation supports <tt>null</tt> values. 218 * 219 * @throws UnsupportedOperationException if the <tt>put</tt> operation is not supported by this map. 220 * @throws ClassCastException if the class of the specified key or value prevents it from being stored in 221 * this map. 222 * @throws IllegalArgumentException if some aspect of this key or value prevents it from being stored in this 223 * map. 224 * @throws NullPointerException if this map does not permit <tt>null</tt> keys or values, and the specified 225 * key or value is <tt>null</tt>. 226 */ 227 public V put(K k, V v) { 228 SoftReference<V> sr = _map.get(k); 229 _map.put(k, new SoftReference<V>(v)); 230 if (sr==null) { return null; } 231 return sr.get(); 232 } 233 234 235 /** 236 * Removes the mapping for this key from this map if it is present (optional operation). More formally, if this map 237 * contains a mapping from key <tt>k</tt> to value <tt>v</tt> such that <code>(key==null ? k==null : 238 * key.equals(k))</code>, that mapping is removed. (The map can contain at most one such mapping.) 239 * <p/> 240 * <p>Returns the value to which the map previously associated the key, or <tt>null</tt> if the map contained no 241 * mapping for this key. (A <tt>null</tt> return can also indicate that the map previously associated <tt>null</tt> 242 * with the specified key if the implementation supports <tt>null</tt> values.) The map will not contain a mapping for 243 * the specified key once the call returns. 244 * 245 * @param key key whose mapping is to be removed from the map. 246 * 247 * @return previous value associated with specified key, or <tt>null</tt> if there was no mapping for key. 248 * 249 * @throws ClassCastException if the key is of an inappropriate type for this map (optional). 250 * @throws NullPointerException if the key is <tt>null</tt> and this map does not permit <tt>null</tt> keys 251 * (optional). 252 * @throws UnsupportedOperationException if the <tt>remove</tt> method is not supported by this map. 253 */ 254 public V remove(Object key) { 255 SoftReference<V> sr = _map.get(key); 256 _map.remove(key); 257 if (sr==null) { return null; } 258 return sr.get(); 259 } 260 261 262 /** 263 * Copies all of the mappings from the specified map to this map (optional operation). The effect of this call is 264 * equivalent to that of calling {@link #put(Object,Object) put(k, v)} on this map once for each mapping from key 265 * <tt>k</tt> to value <tt>v</tt> in the specified map. The behavior of this operation is unspecified if the specified 266 * map is modified while the operation is in progress. 267 * 268 * @param map Mappings to be stored in this map. 269 * 270 * @throws UnsupportedOperationException if the <tt>putAll</tt> method is not supported by this map. 271 * @throws ClassCastException if the class of a key or value in the specified map prevents it from being 272 * stored in this map. 273 * @throws IllegalArgumentException some aspect of a key or value in the specified map prevents it from being 274 * stored in this map. 275 * @throws NullPointerException if the specified map is <tt>null</tt>, or if this map does not permit 276 * <tt>null</tt> keys or values, and the specified map contains <tt>null</tt> 277 * keys or values. 278 */ 279 public void putAll(Map<? extends K, ? extends V> map) { 280 compact(); 281 for(K key: map.keySet()) { 282 V value = map.get(key); 283 if (value!=null) { 284 _map.put(key, new SoftReference<V>(value)); 285 } 286 } 287 } 288 289 /** 290 * Removes all mappings from this map (optional operation). 291 * 292 * @throws UnsupportedOperationException clear is not supported by this map. 293 */ 294 public void clear() { 295 _map.clear(); 296 } 297 298 /** 299 * Returns a set view of the keys contained in this map. The set is backed by the map, so changes to the map are 300 * reflected in the set, and vice-versa. If the map is modified while an iteration over the set is in progress (except 301 * through the iterator's own <tt>remove</tt> operation), the results of the iteration are undefined. The set supports 302 * element removal, which removes the corresponding mapping from the map, via the <tt>Iterator.remove</tt>, 303 * <tt>Set.remove</tt>, <tt>removeAll</tt> <tt>retainAll</tt>, and <tt>clear</tt> operations. It does not support the 304 * add or <tt>addAll</tt> operations. 305 * 306 * @return a set view of the keys contained in this map. 307 */ 308 public Set<K> keySet() { 309 compact(); 310 return _map.keySet(); 311 } 312 313 314 /** 315 * Not supported. 316 * @throws UnsupportedOperationException 317 */ 318 public Collection<V> values() { 319 throw new UnsupportedOperationException("entrySet not supported by SoftHashMap"); 320 } 321 322 /** 323 * Not supported. 324 * @throws UnsupportedOperationException 325 */ 326 public Set<Entry<K, V>> entrySet() { 327 throw new UnsupportedOperationException("entrySet not supported by SoftHashMap"); 328 } 329 }