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