1 /* 2 * Copyright (c) 2002, 2021, 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.security.util; 27 28 import java.util.*; 29 import java.lang.ref.*; 30 31 /** 32 * Abstract base class and factory for caches. A cache is a key-value mapping. 33 * It has properties that make it more suitable for caching than a Map. 34 * 35 * The factory methods can be used to obtain two different implementations. 36 * They have the following properties: 37 * 38 * . keys and values reside in memory 39 * 40 * . keys and values must be non-null 41 * 42 * . maximum size. Replacements are made in LRU order. 43 * 44 * . optional lifetime, specified in seconds. 45 * 46 * . safe for concurrent use by multiple threads 47 * 48 * . values are held by either standard references or via SoftReferences. 49 * SoftReferences have the advantage that they are automatically cleared 50 * by the garbage collector in response to memory demand. This makes it 51 * possible to simple set the maximum size to a very large value and let 52 * the GC automatically size the cache dynamically depending on the 53 * amount of available memory. 54 * 55 * However, note that because of the way SoftReferences are implemented in 56 * HotSpot at the moment, this may not work perfectly as it clears them fairly 57 * eagerly. Performance may be improved if the Java heap size is set to larger 58 * value using e.g. java -ms64M -mx128M foo.Test 59 * 60 * Cache sizing: the memory cache is implemented on top of a LinkedHashMap. 61 * In its current implementation, the number of buckets (NOT entries) in 62 * (Linked)HashMaps is always a power of two. It is recommended to set the 63 * maximum cache size to value that uses those buckets fully. For example, 64 * if a cache with somewhere between 500 and 1000 entries is desired, a 65 * maximum size of 750 would be a good choice: try 1024 buckets, with a 66 * load factor of 0.75f, the number of entries can be calculated as 67 * buckets / 4 * 3. As mentioned above, with a SoftReference cache, it is 68 * generally reasonable to set the size to a fairly large value. 69 * 70 * @author Andreas Sterbenz 71 */ 72 public abstract class Cache<K,V> { 73 Cache()74 protected Cache() { 75 // empty 76 } 77 78 /** 79 * Return the number of currently valid entries in the cache. 80 */ size()81 public abstract int size(); 82 83 /** 84 * Remove all entries from the cache. 85 */ clear()86 public abstract void clear(); 87 88 /** 89 * Add an entry to the cache. 90 */ put(K key, V value)91 public abstract void put(K key, V value); 92 93 /** 94 * Get a value from the cache. 95 */ get(Object key)96 public abstract V get(Object key); 97 98 /** 99 * Remove an entry from the cache. 100 */ remove(Object key)101 public abstract void remove(Object key); 102 103 /** 104 * Pull an entry from the cache. 105 */ pull(Object key)106 public abstract V pull(Object key); 107 108 /** 109 * Set the maximum size. 110 */ setCapacity(int size)111 public abstract void setCapacity(int size); 112 113 /** 114 * Set the timeout(in seconds). 115 */ setTimeout(int timeout)116 public abstract void setTimeout(int timeout); 117 118 /** 119 * accept a visitor 120 */ accept(CacheVisitor<K,V> visitor)121 public abstract void accept(CacheVisitor<K,V> visitor); 122 123 /** 124 * Return a new memory cache with the specified maximum size, unlimited 125 * lifetime for entries, with the values held by SoftReferences. 126 */ newSoftMemoryCache(int size)127 public static <K,V> Cache<K,V> newSoftMemoryCache(int size) { 128 return new MemoryCache<>(true, size); 129 } 130 131 /** 132 * Return a new memory cache with the specified maximum size, the 133 * specified maximum lifetime (in seconds), with the values held 134 * by SoftReferences. 135 */ newSoftMemoryCache(int size, int timeout)136 public static <K,V> Cache<K,V> newSoftMemoryCache(int size, int timeout) { 137 return new MemoryCache<>(true, size, timeout); 138 } 139 140 /** 141 * Return a new memory cache with the specified maximum size, unlimited 142 * lifetime for entries, with the values held by standard references. 143 */ newHardMemoryCache(int size)144 public static <K,V> Cache<K,V> newHardMemoryCache(int size) { 145 return new MemoryCache<>(false, size); 146 } 147 148 /** 149 * Return a dummy cache that does nothing. 150 */ 151 @SuppressWarnings("unchecked") newNullCache()152 public static <K,V> Cache<K,V> newNullCache() { 153 return (Cache<K,V>) NullCache.INSTANCE; 154 } 155 156 /** 157 * Return a new memory cache with the specified maximum size, the 158 * specified maximum lifetime (in seconds), with the values held 159 * by standard references. 160 */ newHardMemoryCache(int size, int timeout)161 public static <K,V> Cache<K,V> newHardMemoryCache(int size, int timeout) { 162 return new MemoryCache<>(false, size, timeout); 163 } 164 165 /** 166 * Utility class that wraps a byte array and implements the equals() 167 * and hashCode() contract in a way suitable for Maps and caches. 168 */ 169 public static class EqualByteArray { 170 171 private final byte[] b; 172 private int hash; 173 EqualByteArray(byte[] b)174 public EqualByteArray(byte[] b) { 175 this.b = b; 176 } 177 hashCode()178 public int hashCode() { 179 int h = hash; 180 if (h == 0 && b.length > 0) { 181 hash = h = Arrays.hashCode(b); 182 } 183 return h; 184 } 185 equals(Object obj)186 public boolean equals(Object obj) { 187 if (this == obj) { 188 return true; 189 } 190 if (obj instanceof EqualByteArray == false) { 191 return false; 192 } 193 EqualByteArray other = (EqualByteArray)obj; 194 return Arrays.equals(this.b, other.b); 195 } 196 } 197 198 public interface CacheVisitor<K,V> { visit(Map<K,V> map)199 public void visit(Map<K,V> map); 200 } 201 202 } 203 204 class NullCache<K,V> extends Cache<K,V> { 205 206 static final Cache<Object,Object> INSTANCE = new NullCache<>(); 207 NullCache()208 private NullCache() { 209 // empty 210 } 211 size()212 public int size() { 213 return 0; 214 } 215 clear()216 public void clear() { 217 // empty 218 } 219 put(K key, V value)220 public void put(K key, V value) { 221 // empty 222 } 223 get(Object key)224 public V get(Object key) { 225 return null; 226 } 227 remove(Object key)228 public void remove(Object key) { 229 // empty 230 } 231 pull(Object key)232 public V pull(Object key) { 233 return null; 234 } 235 setCapacity(int size)236 public void setCapacity(int size) { 237 // empty 238 } 239 setTimeout(int timeout)240 public void setTimeout(int timeout) { 241 // empty 242 } 243 accept(CacheVisitor<K,V> visitor)244 public void accept(CacheVisitor<K,V> visitor) { 245 // empty 246 } 247 248 } 249 250 class MemoryCache<K,V> extends Cache<K,V> { 251 252 private static final float LOAD_FACTOR = 0.75f; 253 254 // XXXX 255 private static final boolean DEBUG = false; 256 257 private final Map<K, CacheEntry<K,V>> cacheMap; 258 private int maxSize; 259 private long lifetime; 260 private long nextExpirationTime = Long.MAX_VALUE; 261 262 // ReferenceQueue is of type V instead of Cache<K,V> 263 // to allow SoftCacheEntry to extend SoftReference<V> 264 private final ReferenceQueue<V> queue; 265 MemoryCache(boolean soft, int maxSize)266 public MemoryCache(boolean soft, int maxSize) { 267 this(soft, maxSize, 0); 268 } 269 MemoryCache(boolean soft, int maxSize, int lifetime)270 public MemoryCache(boolean soft, int maxSize, int lifetime) { 271 this.maxSize = maxSize; 272 this.lifetime = lifetime * 1000; 273 if (soft) 274 this.queue = new ReferenceQueue<>(); 275 else 276 this.queue = null; 277 278 cacheMap = new LinkedHashMap<>(1, LOAD_FACTOR, true); 279 } 280 281 /** 282 * Empty the reference queue and remove all corresponding entries 283 * from the cache. 284 * 285 * This method should be called at the beginning of each public 286 * method. 287 */ emptyQueue()288 private void emptyQueue() { 289 if (queue == null) { 290 return; 291 } 292 int startSize = cacheMap.size(); 293 while (true) { 294 @SuppressWarnings("unchecked") 295 CacheEntry<K,V> entry = (CacheEntry<K,V>)queue.poll(); 296 if (entry == null) { 297 break; 298 } 299 K key = entry.getKey(); 300 if (key == null) { 301 // key is null, entry has already been removed 302 continue; 303 } 304 CacheEntry<K,V> currentEntry = cacheMap.remove(key); 305 // check if the entry in the map corresponds to the expired 306 // entry. If not, readd the entry 307 if ((currentEntry != null) && (entry != currentEntry)) { 308 cacheMap.put(key, currentEntry); 309 } 310 } 311 if (DEBUG) { 312 int endSize = cacheMap.size(); 313 if (startSize != endSize) { 314 System.out.println("*** Expunged " + (startSize - endSize) 315 + " entries, " + endSize + " entries left"); 316 } 317 } 318 } 319 320 /** 321 * Scan all entries and remove all expired ones. 322 */ expungeExpiredEntries()323 private void expungeExpiredEntries() { 324 emptyQueue(); 325 if (lifetime == 0) { 326 return; 327 } 328 int cnt = 0; 329 long time = System.currentTimeMillis(); 330 if (nextExpirationTime > time) { 331 return; 332 } 333 nextExpirationTime = Long.MAX_VALUE; 334 for (Iterator<CacheEntry<K,V>> t = cacheMap.values().iterator(); 335 t.hasNext(); ) { 336 CacheEntry<K,V> entry = t.next(); 337 if (entry.isValid(time) == false) { 338 t.remove(); 339 cnt++; 340 } else if (nextExpirationTime > entry.getExpirationTime()) { 341 nextExpirationTime = entry.getExpirationTime(); 342 } 343 } 344 if (DEBUG) { 345 if (cnt != 0) { 346 System.out.println("Removed " + cnt 347 + " expired entries, remaining " + cacheMap.size()); 348 } 349 } 350 } 351 size()352 public synchronized int size() { 353 expungeExpiredEntries(); 354 return cacheMap.size(); 355 } 356 clear()357 public synchronized void clear() { 358 if (queue != null) { 359 // if this is a SoftReference cache, first invalidate() all 360 // entries so that GC does not have to enqueue them 361 for (CacheEntry<K,V> entry : cacheMap.values()) { 362 entry.invalidate(); 363 } 364 while (queue.poll() != null) { 365 // empty 366 } 367 } 368 cacheMap.clear(); 369 } 370 put(K key, V value)371 public synchronized void put(K key, V value) { 372 emptyQueue(); 373 long expirationTime = (lifetime == 0) ? 0 : 374 System.currentTimeMillis() + lifetime; 375 if (expirationTime < nextExpirationTime) { 376 nextExpirationTime = expirationTime; 377 } 378 CacheEntry<K,V> newEntry = newEntry(key, value, expirationTime, queue); 379 CacheEntry<K,V> oldEntry = cacheMap.put(key, newEntry); 380 if (oldEntry != null) { 381 oldEntry.invalidate(); 382 return; 383 } 384 if (maxSize > 0 && cacheMap.size() > maxSize) { 385 expungeExpiredEntries(); 386 if (cacheMap.size() > maxSize) { // still too large? 387 Iterator<CacheEntry<K,V>> t = cacheMap.values().iterator(); 388 CacheEntry<K,V> lruEntry = t.next(); 389 if (DEBUG) { 390 System.out.println("** Overflow removal " 391 + lruEntry.getKey() + " | " + lruEntry.getValue()); 392 } 393 t.remove(); 394 lruEntry.invalidate(); 395 } 396 } 397 } 398 get(Object key)399 public synchronized V get(Object key) { 400 emptyQueue(); 401 CacheEntry<K,V> entry = cacheMap.get(key); 402 if (entry == null) { 403 return null; 404 } 405 long time = (lifetime == 0) ? 0 : System.currentTimeMillis(); 406 if (entry.isValid(time) == false) { 407 if (DEBUG) { 408 System.out.println("Ignoring expired entry"); 409 } 410 cacheMap.remove(key); 411 return null; 412 } 413 return entry.getValue(); 414 } 415 remove(Object key)416 public synchronized void remove(Object key) { 417 emptyQueue(); 418 CacheEntry<K,V> entry = cacheMap.remove(key); 419 if (entry != null) { 420 entry.invalidate(); 421 } 422 } 423 pull(Object key)424 public synchronized V pull(Object key) { 425 emptyQueue(); 426 CacheEntry<K,V> entry = cacheMap.remove(key); 427 if (entry == null) { 428 return null; 429 } 430 431 long time = (lifetime == 0) ? 0 : System.currentTimeMillis(); 432 if (entry.isValid(time)) { 433 V value = entry.getValue(); 434 entry.invalidate(); 435 return value; 436 } else { 437 if (DEBUG) { 438 System.out.println("Ignoring expired entry"); 439 } 440 return null; 441 } 442 } 443 setCapacity(int size)444 public synchronized void setCapacity(int size) { 445 expungeExpiredEntries(); 446 if (size > 0 && cacheMap.size() > size) { 447 Iterator<CacheEntry<K,V>> t = cacheMap.values().iterator(); 448 for (int i = cacheMap.size() - size; i > 0; i--) { 449 CacheEntry<K,V> lruEntry = t.next(); 450 if (DEBUG) { 451 System.out.println("** capacity reset removal " 452 + lruEntry.getKey() + " | " + lruEntry.getValue()); 453 } 454 t.remove(); 455 lruEntry.invalidate(); 456 } 457 } 458 459 maxSize = size > 0 ? size : 0; 460 461 if (DEBUG) { 462 System.out.println("** capacity reset to " + size); 463 } 464 } 465 setTimeout(int timeout)466 public synchronized void setTimeout(int timeout) { 467 emptyQueue(); 468 lifetime = timeout > 0 ? timeout * 1000L : 0L; 469 470 if (DEBUG) { 471 System.out.println("** lifetime reset to " + timeout); 472 } 473 } 474 475 // it is a heavyweight method. accept(CacheVisitor<K,V> visitor)476 public synchronized void accept(CacheVisitor<K,V> visitor) { 477 expungeExpiredEntries(); 478 Map<K,V> cached = getCachedEntries(); 479 480 visitor.visit(cached); 481 } 482 getCachedEntries()483 private Map<K,V> getCachedEntries() { 484 Map<K,V> kvmap = new HashMap<>(cacheMap.size()); 485 486 for (CacheEntry<K,V> entry : cacheMap.values()) { 487 kvmap.put(entry.getKey(), entry.getValue()); 488 } 489 490 return kvmap; 491 } 492 newEntry(K key, V value, long expirationTime, ReferenceQueue<V> queue)493 protected CacheEntry<K,V> newEntry(K key, V value, 494 long expirationTime, ReferenceQueue<V> queue) { 495 if (queue != null) { 496 return new SoftCacheEntry<>(key, value, expirationTime, queue); 497 } else { 498 return new HardCacheEntry<>(key, value, expirationTime); 499 } 500 } 501 502 private static interface CacheEntry<K,V> { 503 isValid(long currentTime)504 boolean isValid(long currentTime); 505 invalidate()506 void invalidate(); 507 getKey()508 K getKey(); 509 getValue()510 V getValue(); 511 getExpirationTime()512 long getExpirationTime(); 513 } 514 515 private static class HardCacheEntry<K,V> implements CacheEntry<K,V> { 516 517 private K key; 518 private V value; 519 private long expirationTime; 520 HardCacheEntry(K key, V value, long expirationTime)521 HardCacheEntry(K key, V value, long expirationTime) { 522 this.key = key; 523 this.value = value; 524 this.expirationTime = expirationTime; 525 } 526 getKey()527 public K getKey() { 528 return key; 529 } 530 getValue()531 public V getValue() { 532 return value; 533 } 534 getExpirationTime()535 public long getExpirationTime() { 536 return expirationTime; 537 } 538 isValid(long currentTime)539 public boolean isValid(long currentTime) { 540 boolean valid = (currentTime <= expirationTime); 541 if (valid == false) { 542 invalidate(); 543 } 544 return valid; 545 } 546 invalidate()547 public void invalidate() { 548 key = null; 549 value = null; 550 expirationTime = -1; 551 } 552 } 553 554 private static class SoftCacheEntry<K,V> 555 extends SoftReference<V> 556 implements CacheEntry<K,V> { 557 558 private K key; 559 private long expirationTime; 560 SoftCacheEntry(K key, V value, long expirationTime, ReferenceQueue<V> queue)561 SoftCacheEntry(K key, V value, long expirationTime, 562 ReferenceQueue<V> queue) { 563 super(value, queue); 564 this.key = key; 565 this.expirationTime = expirationTime; 566 } 567 getKey()568 public K getKey() { 569 return key; 570 } 571 getValue()572 public V getValue() { 573 return get(); 574 } 575 getExpirationTime()576 public long getExpirationTime() { 577 return expirationTime; 578 } 579 isValid(long currentTime)580 public boolean isValid(long currentTime) { 581 boolean valid = (currentTime <= expirationTime) && (get() != null); 582 if (valid == false) { 583 invalidate(); 584 } 585 return valid; 586 } 587 invalidate()588 public void invalidate() { 589 clear(); 590 key = null; 591 expirationTime = -1; 592 } 593 } 594 595 } 596