1 /* 2 * Copyright (c) 1998, 2014, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package sun.misc; 27 28 import java.lang.ref.SoftReference; 29 import java.lang.ref.ReferenceQueue; 30 31 import java.util.Iterator; 32 import java.util.Map; 33 import java.util.AbstractMap; 34 import java.util.HashMap; 35 import java.util.Set; 36 import java.util.AbstractSet; 37 import java.util.NoSuchElementException; 38 39 40 /** 41 * A memory-sensitive implementation of the <code>Map</code> interface. 42 * 43 * <p> A <code>SoftCache</code> object uses {@link java.lang.ref.SoftReference 44 * soft references} to implement a memory-sensitive hash map. If the garbage 45 * collector determines at a certain point in time that a value object in a 46 * <code>SoftCache</code> entry is no longer strongly reachable, then it may 47 * remove that entry in order to release the memory occupied by the value 48 * object. All <code>SoftCache</code> objects are guaranteed to be completely 49 * cleared before the virtual machine will throw an 50 * <code>OutOfMemoryError</code>. Because of this automatic clearing feature, 51 * the behavior of this class is somewhat different from that of other 52 * <code>Map</code> implementations. 53 * 54 * <p> Both null values and the null key are supported. This class has the 55 * same performance characteristics as the <code>HashMap</code> class, and has 56 * the same efficiency parameters of <em>initial capacity</em> and <em>load 57 * factor</em>. 58 * 59 * <p> Like most collection classes, this class is not synchronized. A 60 * synchronized <code>SoftCache</code> may be constructed using the 61 * <code>Collections.synchronizedMap</code> method. 62 * 63 * <p> In typical usage this class will be subclassed and the <code>fill</code> 64 * method will be overridden. When the <code>get</code> method is invoked on a 65 * key for which there is no mapping in the cache, it will in turn invoke the 66 * <code>fill</code> method on that key in an attempt to construct a 67 * corresponding value. If the <code>fill</code> method returns such a value 68 * then the cache will be updated and the new value will be returned. Thus, 69 * for example, a simple URL-content cache can be constructed as follows: 70 * 71 * <pre> 72 * public class URLCache extends SoftCache { 73 * protected Object fill(Object key) { 74 * return ((URL)key).getContent(); 75 * } 76 * } 77 * </pre> 78 * 79 * <p> The behavior of the <code>SoftCache</code> class depends in part upon 80 * the actions of the garbage collector, so several familiar (though not 81 * required) <code>Map</code> invariants do not hold for this class. <p> 82 * Because entries are removed from a <code>SoftCache</code> in response to 83 * dynamic advice from the garbage collector, a <code>SoftCache</code> may 84 * behave as though an unknown thread is silently removing entries. In 85 * particular, even if you synchronize on a <code>SoftCache</code> instance and 86 * invoke none of its mutator methods, it is possible for the <code>size</code> 87 * method to return smaller values over time, for the <code>isEmpty</code> 88 * method to return <code>false</code> and then <code>true</code>, for the 89 * <code>containsKey</code> method to return <code>true</code> and later 90 * <code>false</code> for a given key, for the <code>get</code> method to 91 * return a value for a given key but later return <code>null</code>, for the 92 * <code>put</code> method to return <code>null</code> and the 93 * <code>remove</code> method to return <code>false</code> for a key that 94 * previously appeared to be in the map, and for successive examinations of the 95 * key set, the value set, and the entry set to yield successively smaller 96 * numbers of elements. 97 * 98 * @author Mark Reinhold 99 * @since 1.2 100 * @see java.util.HashMap 101 * @see java.lang.ref.SoftReference 102 * @deprecated No direct replacement; {@link java.util.WeakHashMap} 103 * addresses a related by different use-case. 104 */ 105 106 @Deprecated 107 public class SoftCache extends AbstractMap<Object, Object> implements Map<Object, Object> { 108 109 /* The basic idea of this implementation is to maintain an internal HashMap 110 that maps keys to soft references whose referents are the keys' values; 111 the various accessor methods dereference these soft references before 112 returning values. Because we don't have access to the innards of the 113 HashMap, each soft reference must contain the key that maps to it so 114 that the processQueue method can remove keys whose values have been 115 discarded. Thus the HashMap actually maps keys to instances of the 116 ValueCell class, which is a simple extension of the SoftReference class. 117 */ 118 119 120 private static class ValueCell extends SoftReference<Object> { 121 private static Object INVALID_KEY = new Object(); 122 private static int dropped = 0; 123 private Object key; 124 125 private ValueCell(Object key, Object value, ReferenceQueue<Object> queue) { 126 super(value, queue); 127 this.key = key; 128 } 129 130 private static ValueCell create(Object key, Object value, 131 ReferenceQueue<Object> queue) 132 { 133 if (value == null) return null; 134 return new ValueCell(key, value, queue); 135 } 136 137 private static Object strip(Object val, boolean drop) { 138 if (val == null) return null; 139 ValueCell vc = (ValueCell)val; 140 Object o = vc.get(); 141 if (drop) vc.drop(); 142 return o; 143 } 144 145 private boolean isValid() { 146 return (key != INVALID_KEY); 147 } 148 149 private void drop() { 150 super.clear(); 151 key = INVALID_KEY; 152 dropped++; 153 } 154 155 } 156 157 158 /* Hash table mapping keys to ValueCells */ 159 private Map<Object, Object> hash; 160 161 /* Reference queue for cleared ValueCells */ 162 private ReferenceQueue<Object> queue = new ReferenceQueue<>(); 163 164 165 /* Process any ValueCells that have been cleared and enqueued by the 166 garbage collector. This method should be invoked once by each public 167 mutator in this class. We don't invoke this method in public accessors 168 because that can lead to surprising ConcurrentModificationExceptions. 169 */ 170 private void processQueue() { 171 ValueCell vc; 172 while ((vc = (ValueCell)queue.poll()) != null) { 173 if (vc.isValid()) hash.remove(vc.key); 174 else ValueCell.dropped--; 175 } 176 } 177 178 179 /* -- Constructors -- */ 180 181 /** 182 * Construct a new, empty <code>SoftCache</code> with the given 183 * initial capacity and the given load factor. 184 * 185 * @param initialCapacity The initial capacity of the cache 186 * 187 * @param loadFactor A number between 0.0 and 1.0 188 * 189 * @throws IllegalArgumentException If the initial capacity is less than 190 * or equal to zero, or if the load 191 * factor is less than zero 192 */ 193 public SoftCache(int initialCapacity, float loadFactor) { 194 hash = new HashMap<>(initialCapacity, loadFactor); 195 } 196 197 /** 198 * Construct a new, empty <code>SoftCache</code> with the given 199 * initial capacity and the default load factor. 200 * 201 * @param initialCapacity The initial capacity of the cache 202 * 203 * @throws IllegalArgumentException If the initial capacity is less than 204 * or equal to zero 205 */ 206 public SoftCache(int initialCapacity) { 207 hash = new HashMap<>(initialCapacity); 208 } 209 210 /** 211 * Construct a new, empty <code>SoftCache</code> with the default 212 * capacity and the default load factor. 213 */ 214 public SoftCache() { 215 hash = new HashMap<>(); 216 } 217 218 219 /* -- Simple queries -- */ 220 221 /** 222 * Return the number of key-value mappings in this cache. The time 223 * required by this operation is linear in the size of the map. 224 */ 225 public int size() { 226 return entrySet().size(); 227 } 228 229 /** 230 * Return <code>true</code> if this cache contains no key-value mappings. 231 */ 232 public boolean isEmpty() { 233 return entrySet().isEmpty(); 234 } 235 236 /** 237 * Return <code>true</code> if this cache contains a mapping for the 238 * specified key. If there is no mapping for the key, this method will not 239 * attempt to construct one by invoking the <code>fill</code> method. 240 * 241 * @param key The key whose presence in the cache is to be tested 242 */ 243 public boolean containsKey(Object key) { 244 return ValueCell.strip(hash.get(key), false) != null; 245 } 246 247 248 /* -- Lookup and modification operations -- */ 249 250 /** 251 * Create a value object for the given <code>key</code>. This method is 252 * invoked by the <code>get</code> method when there is no entry for 253 * <code>key</code>. If this method returns a non-<code>null</code> value, 254 * then the cache will be updated to map <code>key</code> to that value, 255 * and that value will be returned by the <code>get</code> method. 256 * 257 * <p> The default implementation of this method simply returns 258 * <code>null</code> for every <code>key</code> value. A subclass may 259 * override this method to provide more useful behavior. 260 * 261 * @param key The key for which a value is to be computed 262 * 263 * @return A value for <code>key</code>, or <code>null</code> if one 264 * could not be computed 265 * @see #get 266 */ 267 protected Object fill(Object key) { 268 return null; 269 } 270 271 /** 272 * Return the value to which this cache maps the specified 273 * <code>key</code>. If the cache does not presently contain a value for 274 * this key, then invoke the <code>fill</code> method in an attempt to 275 * compute such a value. If that method returns a non-<code>null</code> 276 * value, then update the cache and return the new value. Otherwise, 277 * return <code>null</code>. 278 * 279 * <p> Note that because this method may update the cache, it is considered 280 * a mutator and may cause <code>ConcurrentModificationException</code>s to 281 * be thrown if invoked while an iterator is in use. 282 * 283 * @param key The key whose associated value, if any, is to be returned 284 * 285 * @see #fill 286 */ 287 public Object get(Object key) { 288 processQueue(); 289 Object v = hash.get(key); 290 if (v == null) { 291 v = fill(key); 292 if (v != null) { 293 hash.put(key, ValueCell.create(key, v, queue)); 294 return v; 295 } 296 } 297 return ValueCell.strip(v, false); 298 } 299 300 /** 301 * Update this cache so that the given <code>key</code> maps to the given 302 * <code>value</code>. If the cache previously contained a mapping for 303 * <code>key</code> then that mapping is replaced and the old value is 304 * returned. 305 * 306 * @param key The key that is to be mapped to the given 307 * <code>value</code> 308 * @param value The value to which the given <code>key</code> is to be 309 * mapped 310 * 311 * @return The previous value to which this key was mapped, or 312 * <code>null</code> if there was no mapping for the key 313 */ 314 public Object put(Object key, Object value) { 315 processQueue(); 316 ValueCell vc = ValueCell.create(key, value, queue); 317 return ValueCell.strip(hash.put(key, vc), true); 318 } 319 320 /** 321 * Remove the mapping for the given <code>key</code> from this cache, if 322 * present. 323 * 324 * @param key The key whose mapping is to be removed 325 * 326 * @return The value to which this key was mapped, or <code>null</code> if 327 * there was no mapping for the key 328 */ 329 public Object remove(Object key) { 330 processQueue(); 331 return ValueCell.strip(hash.remove(key), true); 332 } 333 334 /** 335 * Remove all mappings from this cache. 336 */ 337 public void clear() { 338 processQueue(); 339 hash.clear(); 340 } 341 342 343 /* -- Views -- */ 344 345 private static boolean valEquals(Object o1, Object o2) { 346 return (o1 == null) ? (o2 == null) : o1.equals(o2); 347 } 348 349 350 /* Internal class for entries. 351 Because it uses SoftCache.this.queue, this class cannot be static. 352 */ 353 private class Entry implements Map.Entry<Object, Object> { 354 private Map.Entry<Object, Object> ent; 355 private Object value; /* Strong reference to value, to prevent the GC 356 from flushing the value while this Entry 357 exists */ 358 359 Entry(Map.Entry<Object, Object> ent, Object value) { 360 this.ent = ent; 361 this.value = value; 362 } 363 364 public Object getKey() { 365 return ent.getKey(); 366 } 367 368 public Object getValue() { 369 return value; 370 } 371 372 public Object setValue(Object value) { 373 return ent.setValue(ValueCell.create(ent.getKey(), value, queue)); 374 } 375 376 @SuppressWarnings("unchecked") 377 public boolean equals(Object o) { 378 if (! (o instanceof Map.Entry)) return false; 379 Map.Entry<Object, Object> e = (Map.Entry<Object, Object>)o; 380 return (valEquals(ent.getKey(), e.getKey()) 381 && valEquals(value, e.getValue())); 382 } 383 384 public int hashCode() { 385 Object k; 386 return ((((k = getKey()) == null) ? 0 : k.hashCode()) 387 ^ ((value == null) ? 0 : value.hashCode())); 388 } 389 390 } 391 392 393 /* Internal class for entry sets */ 394 private class EntrySet extends AbstractSet<Map.Entry<Object, Object>> { 395 Set<Map.Entry<Object, Object>> hashEntries = hash.entrySet(); 396 397 public Iterator<Map.Entry<Object, Object>> iterator() { 398 399 return new Iterator<Map.Entry<Object, Object>>() { 400 Iterator<Map.Entry<Object, Object>> hashIterator = hashEntries.iterator(); 401 Entry next = null; 402 403 public boolean hasNext() { 404 while (hashIterator.hasNext()) { 405 Map.Entry<Object, Object> ent = hashIterator.next(); 406 ValueCell vc = (ValueCell)ent.getValue(); 407 Object v = null; 408 if ((vc != null) && ((v = vc.get()) == null)) { 409 /* Value has been flushed by GC */ 410 continue; 411 } 412 next = new Entry(ent, v); 413 return true; 414 } 415 return false; 416 } 417 418 public Map.Entry<Object, Object> next() { 419 if ((next == null) && !hasNext()) 420 throw new NoSuchElementException(); 421 Entry e = next; 422 next = null; 423 return e; 424 } 425 426 public void remove() { 427 hashIterator.remove(); 428 } 429 430 }; 431 } 432 433 public boolean isEmpty() { 434 return !(iterator().hasNext()); 435 } 436 437 public int size() { 438 int j = 0; 439 for (Iterator<Map.Entry<Object, Object>> i = iterator(); i.hasNext(); i.next()) j++; 440 return j; 441 } 442 443 public boolean remove(Object o) { 444 processQueue(); 445 if (o instanceof Entry) return hashEntries.remove(((Entry)o).ent); 446 else return false; 447 } 448 449 } 450 451 452 private Set<Map.Entry<Object, Object>> entrySet = null; 453 454 /** 455 * Return a <code>Set</code> view of the mappings in this cache. 456 */ 457 public Set<Map.Entry<Object, Object>> entrySet() { 458 if (entrySet == null) entrySet = new EntrySet(); 459 return entrySet; 460 } 461 462 }